feat: Android-pyApi v1.0 - 全面翻译 Android SDK API 为 Python
52 个模块覆盖 Android + Java + org.json 标准库
This commit is contained in:
commit
bfe27fdc08
104 files changed
+58750
No files matched your search
@@ -0,0 +1,258 @@
|
||||
"""
|
||||
Android-pyApi — Android API 全面 Python 翻译
|
||||
==============================================
|
||||
方法签名完全对齐 Android SDK,跨平台运行于 Windows/macOS/Linux。
|
||||
|
||||
用法:
|
||||
from android_pyapi import Intent, Context, Uri, Bitmap, View
|
||||
from android_pyapi.java.io import File, FileInputStream
|
||||
from android_pyapi.java.util import ArrayList, HashMap
|
||||
from android_pyapi.org.json import JSONObject, JSONArray
|
||||
|
||||
设计原则:
|
||||
1. 方法名、参数名、返回类型完全对齐 Android SDK
|
||||
2. 能桌面实现的 → 真实实现 (基于 PIL/sqlite3/socket 等)
|
||||
3. 无法桌面实现的 → 返回合理默认值或抛出 AndroidOnlyError
|
||||
4. 所有常量值与 Android SDK 一致
|
||||
"""
|
||||
|
||||
from ._core import (
|
||||
AndroidOnlyError,
|
||||
Stub,
|
||||
android_context,
|
||||
get_context,
|
||||
_sdcard_root,
|
||||
_app_data_dir,
|
||||
_package_name,
|
||||
)
|
||||
|
||||
# android.* 包
|
||||
from .android.app import *
|
||||
from .android.content import *
|
||||
from .android.content_pm import *
|
||||
from .android.os import *
|
||||
from .android.view import *
|
||||
from .android.view_accessibility import *
|
||||
from .android.view_animation import *
|
||||
from .android.view_inputmethod import *
|
||||
from .android.widget import *
|
||||
from .android.graphics import *
|
||||
from .android.graphics_drawable import *
|
||||
from .android.graphics_pdf import *
|
||||
from .android.net import *
|
||||
from .android.net_wifi import *
|
||||
from .android.media import *
|
||||
from .android.hardware import *
|
||||
from .android.hardware_camera2 import *
|
||||
from .android.hardware_biometrics import *
|
||||
from .android.location import *
|
||||
from .android.database import *
|
||||
from .android.database_sqlite import *
|
||||
from .android.provider import *
|
||||
from .android.telephony import *
|
||||
from .android.bluetooth import *
|
||||
from .android.bluetooth_le import *
|
||||
from .android.text import *
|
||||
from .android.util import *
|
||||
from .android.preference import *
|
||||
from .android.webkit import *
|
||||
from .android.nfc import *
|
||||
from .android.speech_tts import *
|
||||
from .android.inputmethod import *
|
||||
|
||||
# java.* 包
|
||||
from .java.io import *
|
||||
from .java.net import *
|
||||
from .java.util import *
|
||||
from .java.util_regex import *
|
||||
from .java.util_zip import *
|
||||
from .java.util_concurrent import *
|
||||
from .java.lang import *
|
||||
from .java.math import *
|
||||
from .java.text import *
|
||||
from .java.time import *
|
||||
from .java.nio import *
|
||||
from .java.nio_file import *
|
||||
from .java.security import *
|
||||
from .java.crypto import *
|
||||
|
||||
# org.* 包
|
||||
from .org.json import *
|
||||
|
||||
# Base64 (标准 Android API)
|
||||
import base64 as _base64
|
||||
|
||||
class Base64:
|
||||
DEFAULT = 0
|
||||
NO_PADDING = 1
|
||||
NO_WRAP = 2
|
||||
CRLF = 4
|
||||
URL_SAFE = 8
|
||||
NO_CLOSE = 16
|
||||
|
||||
@staticmethod
|
||||
def encodeToString(input_data, flags=0):
|
||||
if isinstance(input_data, str):
|
||||
input_data = input_data.encode('utf-8')
|
||||
return _base64.b64encode(input_data).decode('ascii')
|
||||
|
||||
@staticmethod
|
||||
def decode(str_val, flags=0):
|
||||
return _base64.b64decode(str_val)
|
||||
|
||||
@staticmethod
|
||||
def encode(input_data, flags=0):
|
||||
if isinstance(input_data, str):
|
||||
input_data = input_data.encode('utf-8')
|
||||
return _base64.b64encode(input_data)
|
||||
|
||||
# Scanner (java.util)
|
||||
class Scanner:
|
||||
def __init__(self, source):
|
||||
if isinstance(source, str):
|
||||
self._source = source
|
||||
else:
|
||||
self._source = str(source)
|
||||
self._pos = 0
|
||||
|
||||
def next(self):
|
||||
while self._pos < len(self._source) and self._source[self._pos].isspace():
|
||||
self._pos += 1
|
||||
start = self._pos
|
||||
while self._pos < len(self._source) and not self._source[self._pos].isspace():
|
||||
self._pos += 1
|
||||
if start == self._pos:
|
||||
raise Exception("NoSuchElementException")
|
||||
return self._source[start:self._pos]
|
||||
|
||||
def hasNext(self):
|
||||
pos = self._pos
|
||||
while pos < len(self._source) and self._source[pos].isspace():
|
||||
pos += 1
|
||||
return pos < len(self._source)
|
||||
|
||||
def nextInt(self):
|
||||
return int(self.next())
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
# Timer (java.util)
|
||||
class Timer:
|
||||
def __init__(self, name=None, daemon=False):
|
||||
import threading
|
||||
self._thread = None
|
||||
self._tasks = []
|
||||
|
||||
def schedule(self, task, delay=None, period=None):
|
||||
import threading
|
||||
if delay is None:
|
||||
self._run_once(task)
|
||||
elif period is None:
|
||||
self._run_delayed(task, delay)
|
||||
else:
|
||||
self._run_periodic(task, delay, period)
|
||||
|
||||
def _run_once(self, task):
|
||||
t = threading.Thread(target=task.run)
|
||||
t.daemon = True
|
||||
self._tasks.append(t)
|
||||
t.start()
|
||||
|
||||
def _run_delayed(self, task, delay_ms):
|
||||
import threading
|
||||
def _run():
|
||||
import time
|
||||
time.sleep(delay_ms / 1000.0)
|
||||
task.run()
|
||||
t = threading.Thread(target=_run)
|
||||
t.daemon = True
|
||||
self._tasks.append(t)
|
||||
t.start()
|
||||
|
||||
def _run_periodic(self, task, delay_ms, period_ms):
|
||||
import threading
|
||||
def _run():
|
||||
import time
|
||||
time.sleep(delay_ms / 1000.0)
|
||||
while True:
|
||||
task.run()
|
||||
time.sleep(period_ms / 1000.0)
|
||||
t = threading.Thread(target=_run)
|
||||
t.daemon = True
|
||||
self._tasks.append(t)
|
||||
t.start()
|
||||
|
||||
def cancel(self):
|
||||
pass
|
||||
|
||||
def purge(self):
|
||||
return 0
|
||||
|
||||
|
||||
class TimerTask:
|
||||
def __init__(self):
|
||||
self._cancelled = False
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def cancel(self):
|
||||
self._cancelled = True
|
||||
|
||||
def scheduledExecutionTime(self):
|
||||
return 0
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__all__ = [
|
||||
# android
|
||||
"Intent", "Context", "Uri", "ContentValues", "ContentResolver",
|
||||
"BroadcastReceiver", "IntentFilter", "SharedPreferences",
|
||||
"PackageManager", "PackageInfo", "ActivityManager",
|
||||
"Build", "Environment", "PowerManager", "BatteryManager",
|
||||
"Bundle", "Handler", "Looper", "Message",
|
||||
"View", "ViewGroup", "TextView", "EditText", "Button",
|
||||
"ImageView", "ScrollView", "ListView", "Spinner",
|
||||
"CheckBox", "Switch", "ProgressBar", "Toast",
|
||||
"Bitmap", "BitmapFactory", "Canvas", "Color", "Paint",
|
||||
"Rect", "RectF", "Matrix", "Typeface",
|
||||
"PdfRenderer", "Drawable", "BitmapDrawable",
|
||||
"ConnectivityManager", "NetworkInfo", "WifiManager",
|
||||
"MediaPlayer", "AudioManager", "MediaRecorder",
|
||||
"SensorManager", "CameraManager", "BiometricPrompt",
|
||||
"LocationManager", "Location", "Geocoder",
|
||||
"SQLiteDatabase", "SQLiteOpenHelper", "Cursor",
|
||||
"Settings", "MediaStore", "ContactsContract", "CalendarContract",
|
||||
"TelephonyManager", "SmsManager",
|
||||
"BluetoothAdapter", "BluetoothDevice",
|
||||
"TextUtils", "Html", "SpannableString",
|
||||
"Log", "Base64", "TypedValue",
|
||||
"AccessibilityNodeInfo", "AccessibilityService",
|
||||
"Animation", "AlphaAnimation", "TranslateAnimation",
|
||||
"InputMethodManager",
|
||||
"WebView",
|
||||
"TextToSpeech",
|
||||
# java
|
||||
"File", "FileInputStream", "FileOutputStream",
|
||||
"BufferedReader", "BufferedWriter",
|
||||
"InputStreamReader", "OutputStreamWriter",
|
||||
"URL", "HttpURLConnection", "Socket", "ServerSocket",
|
||||
"ArrayList", "HashMap", "HashSet", "LinkedList",
|
||||
"Collections", "Arrays", "Calendar", "Date", "Random",
|
||||
"UUID", "Scanner", "Timer", "TimerTask",
|
||||
"Pattern", "Matcher",
|
||||
"ZipFile", "GZIPInputStream", "GZIPOutputStream",
|
||||
"ExecutorService", "Executors", "Future", "Callable",
|
||||
"String", "StringBuilder", "StringBuffer",
|
||||
"Integer", "Math", "System", "Thread",
|
||||
"BigInteger", "BigDecimal",
|
||||
"SimpleDateFormat", "DecimalFormat",
|
||||
"LocalDateTime", "LocalDate", "LocalTime", "Instant", "Duration", "Period",
|
||||
"ChronoUnit", "ChronoField", "DayOfWeek", "Month", "ZoneId", "ZoneOffset",
|
||||
"DateTimeFormatter",
|
||||
"ByteBuffer", "Files", "Path", "Paths",
|
||||
"MessageDigest", "SecureRandom",
|
||||
"Cipher", "Mac", "KeyGenerator",
|
||||
# org
|
||||
"JSONObject", "JSONArray", "JSONException",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Android-pyApi 核心基础设施
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import tempfile
|
||||
import json
|
||||
import hashlib
|
||||
import random
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Callable
|
||||
|
||||
|
||||
class AndroidOnlyError(Exception):
|
||||
"""当调用仅 Android 可用的 API 时抛出"""
|
||||
pass
|
||||
|
||||
|
||||
class Stub:
|
||||
"""
|
||||
通用 Android API 存根基类。
|
||||
子类可以覆盖具体方法实现真实逻辑;
|
||||
未覆盖的方法调用会返回合理默认值。
|
||||
"""
|
||||
|
||||
_android_only = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
if name.startswith("_"):
|
||||
raise AttributeError(name)
|
||||
|
||||
def _stub_method(*args, **kwargs):
|
||||
if self._android_only:
|
||||
raise AndroidOnlyError(
|
||||
f"{self.__class__.__name__}.{name}() 仅在 Android 设备上可用"
|
||||
)
|
||||
return None
|
||||
|
||||
return _stub_method
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"{self.__class__.__name__}@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self) & 0x7FFFFFFF
|
||||
|
||||
def getClass(self) -> type:
|
||||
return self.__class__
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 全局路径和环境
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
_package_name = "com.ai.assistance.operit"
|
||||
_sdcard_root = os.path.join(os.path.expanduser("~"), "Android-compat", "sdcard")
|
||||
_app_data_dir = os.path.join(os.path.expanduser("~"), "Android-compat", "app-data")
|
||||
|
||||
|
||||
def _ensure_dirs():
|
||||
dirs = [
|
||||
_sdcard_root,
|
||||
os.path.join(_sdcard_root, "Download"),
|
||||
os.path.join(_sdcard_root, "DCIM"),
|
||||
os.path.join(_sdcard_root, "DCIM", "Screenshots"),
|
||||
os.path.join(_sdcard_root, "Documents"),
|
||||
os.path.join(_sdcard_root, "Music"),
|
||||
os.path.join(_sdcard_root, "Movies"),
|
||||
os.path.join(_sdcard_root, "Pictures"),
|
||||
os.path.join(_sdcard_root, "Android", "data"),
|
||||
os.path.join(_sdcard_root, "Android", "data", _package_name, "files"),
|
||||
os.path.join(_sdcard_root, "Android", "data", _package_name, "cache"),
|
||||
_app_data_dir,
|
||||
os.path.join(_app_data_dir, "files"),
|
||||
os.path.join(_app_data_dir, "cache"),
|
||||
os.path.join(_app_data_dir, "shared_prefs"),
|
||||
]
|
||||
for d in dirs:
|
||||
os.makedirs(d, exist_ok=True)
|
||||
|
||||
|
||||
_ensure_dirs()
|
||||
|
||||
|
||||
def resolve_android_path(path: str) -> str:
|
||||
"""将 Android 路径解析为桌面路径"""
|
||||
if path.startswith("/sdcard"):
|
||||
return os.path.join(_sdcard_root, path[7:].lstrip("/"))
|
||||
if path.startswith("/data/data/"):
|
||||
return os.path.join(_app_data_dir, path[11:].lstrip("/"))
|
||||
if path.startswith("content://"):
|
||||
# 简单解析 content URI
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(path)
|
||||
return parsed.path or path
|
||||
if path.startswith("file://"):
|
||||
from urllib.parse import unquote
|
||||
return unquote(path[7:])
|
||||
return path
|
||||
|
||||
|
||||
def to_android_path(desktop_path: str) -> str:
|
||||
"""将桌面路径映射回 Android 路径"""
|
||||
if desktop_path.startswith(_sdcard_root):
|
||||
rel = os.path.relpath(desktop_path, _sdcard_root)
|
||||
return f"/sdcard/{rel}"
|
||||
if desktop_path.startswith(_app_data_dir):
|
||||
rel = os.path.relpath(desktop_path, _app_data_dir)
|
||||
return f"/data/data/{_package_name}/{rel}"
|
||||
return desktop_path
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 全局 Context 单例
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class _GlobalContext:
|
||||
"""全局应用上下文占位符,在 android.content 模块中被替换为真实 Context"""
|
||||
|
||||
@property
|
||||
def packageName(self):
|
||||
return _package_name
|
||||
|
||||
def getPackageName(self):
|
||||
return _package_name
|
||||
|
||||
def getFilesDir(self):
|
||||
return os.path.join(_app_data_dir, "files")
|
||||
|
||||
def getCacheDir(self):
|
||||
return os.path.join(_app_data_dir, "cache")
|
||||
|
||||
def getExternalFilesDir(self, type_=None):
|
||||
base = os.path.join(_sdcard_root, "Android", "data", _package_name, "files")
|
||||
if type_:
|
||||
base = os.path.join(base, type_)
|
||||
os.makedirs(base, exist_ok=True)
|
||||
return base
|
||||
|
||||
def getExternalStorageDirectory(self):
|
||||
return _sdcard_root
|
||||
|
||||
def getSystemService(self, name):
|
||||
return None # 由具体模块填充
|
||||
|
||||
|
||||
android_context = _GlobalContext()
|
||||
|
||||
|
||||
def get_context():
|
||||
"""获取全局 Android Context"""
|
||||
return android_context
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 可选依赖检测
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def _check_optional(name: str) -> bool:
|
||||
"""检查可选依赖是否可用"""
|
||||
try:
|
||||
__import__(name)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
HAS_PIL = _check_optional("PIL")
|
||||
HAS_REQUESTS = _check_optional("requests")
|
||||
HAS_HTTPX = _check_optional("httpx")
|
||||
HAS_PYAUTOGUI = _check_optional("pyautogui")
|
||||
HAS_SCREENINFO = _check_optional("screeninfo")
|
||||
HAS_PSUTIL = _check_optional("psutil")
|
||||
HAS_PYMUPDF = _check_optional("fitz")
|
||||
HAS_PDF2IMAGE = _check_optional("pdf2image")
|
||||
HAS_CRYPTOGRAPHY = _check_optional("cryptography")
|
||||
HAS_PYPERCLIP = _check_optional("pyperclip")
|
||||
HAS_JPYPE = _check_optional("jpype")
|
||||
Whitespace-only changes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,791 @@
|
||||
"""
|
||||
Android Bluetooth 组件 — BluetoothAdapter, BluetoothDevice, BluetoothGatt 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothProfile
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothProfile(Stub):
|
||||
"""BluetoothProfile — 蓝牙配置档案"""
|
||||
|
||||
A2DP = 2
|
||||
HEALTH = 3
|
||||
HEADSET = 1
|
||||
GATT = 7
|
||||
GATT_SERVER = 8
|
||||
A2DP_SINK = 11
|
||||
AVRC = 12
|
||||
HEARING_AID = 21
|
||||
LE_AUDIO = 22
|
||||
LE_AUDIO_BROADCAST = 26
|
||||
|
||||
STATE_DISCONNECTED = 0
|
||||
STATE_CONNECTING = 1
|
||||
STATE_CONNECTED = 2
|
||||
STATE_DISCONNECTING = 3
|
||||
|
||||
def getConnectedDevices(self) -> List[Any]:
|
||||
return []
|
||||
|
||||
def getDevicesMatchingConnectionState(self, state: int) -> List[Any]:
|
||||
return []
|
||||
|
||||
def getConnectionState(self, device: Any) -> int:
|
||||
return self.STATE_DISCONNECTED
|
||||
|
||||
|
||||
class BluetoothProfileServiceListener(Stub):
|
||||
"""BluetoothProfile.ServiceListener"""
|
||||
|
||||
def onServiceConnected(self, profile_id: int, proxy: Any):
|
||||
pass
|
||||
|
||||
def onServiceDisconnected(self, profile_id: int):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothClass
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothClass(Stub):
|
||||
"""BluetoothClass — 蓝牙设备类别"""
|
||||
|
||||
class Device(Stub):
|
||||
COMPUTER_DESKTOP = 0x100
|
||||
COMPUTER_LAPTOP = 0x10C
|
||||
PHONE_SMART = 0x200
|
||||
PHONE_UNCATEGORIZED = 0x204
|
||||
AUDIO_VIDEO_WEARABLE_HEADSET = 0x404
|
||||
AUDIO_VIDEO_HANDSFREE = 0x408
|
||||
|
||||
class Major(Stub):
|
||||
MISC = 0x0000
|
||||
COMPUTER = 0x0100
|
||||
PHONE = 0x0200
|
||||
NETWORKING = 0x0300
|
||||
AUDIO_VIDEO = 0x0400
|
||||
PERIPHERAL = 0x0500
|
||||
IMAGING = 0x0600
|
||||
WEARABLE = 0x0700
|
||||
TOY = 0x0800
|
||||
HEALTH = 0x0900
|
||||
UNCATEGORIZED = 0x1F00
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._device_class = 0
|
||||
self._major_class = 0
|
||||
|
||||
def getDeviceClass(self) -> int:
|
||||
return self._device_class
|
||||
|
||||
def getMajorDeviceClass(self) -> int:
|
||||
return self._major_class
|
||||
|
||||
def hasService(self, service: int) -> bool:
|
||||
return False
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BluetoothClass(device=0x{self._device_class:04X})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothSocket
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothSocket(Stub):
|
||||
"""BluetoothSocket — 蓝牙套接字"""
|
||||
|
||||
TYPE_RFCOMM = 1
|
||||
TYPE_L2CAP = 3
|
||||
TYPE_SCO = 2
|
||||
|
||||
def __init__(self, type_: int = TYPE_RFCOMM):
|
||||
super().__init__()
|
||||
self._type = type_
|
||||
self._closed = False
|
||||
self._connected = False
|
||||
self._device: Optional["BluetoothDevice"] = None
|
||||
|
||||
def connect(self):
|
||||
self._connected = True
|
||||
|
||||
def bind(self):
|
||||
pass
|
||||
|
||||
def getInputStream(self) -> Any:
|
||||
return None
|
||||
|
||||
def getOutputStream(self) -> Any:
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
self._connected = False
|
||||
|
||||
def getRemoteDevice(self) -> Optional["BluetoothDevice"]:
|
||||
return self._device
|
||||
|
||||
def getMaxReceivePacketSize(self) -> int:
|
||||
return 0
|
||||
|
||||
def isConnected(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
def isClosed(self) -> bool:
|
||||
return self._closed
|
||||
|
||||
|
||||
class BluetoothServerSocket(Stub):
|
||||
"""BluetoothServerSocket — 蓝牙服务端套接字"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._closed = False
|
||||
|
||||
def accept(self, timeout: int = 0) -> Optional[BluetoothSocket]:
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothDevice
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothDevice(Stub):
|
||||
"""BluetoothDevice — 蓝牙设备"""
|
||||
|
||||
BOND_NONE = 10
|
||||
BOND_BONDING = 11
|
||||
BOND_BONDED = 12
|
||||
|
||||
DEVICE_TYPE_DUAL = 3
|
||||
DEVICE_TYPE_LE = 2
|
||||
DEVICE_TYPE_BREDR = 1
|
||||
DEVICE_TYPE_UNKNOWN = 0
|
||||
|
||||
ACTION_FOUND = "android.bluetooth.device.action.FOUND"
|
||||
ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED"
|
||||
ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED"
|
||||
EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE"
|
||||
EXTRA_NAME = "android.bluetooth.device.extra.NAME"
|
||||
EXTRA_RSSI = "android.bluetooth.device.extra.RSSI"
|
||||
EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE"
|
||||
EXTRA_PREVIOUS_BOND_STATE = "android.bluetooth.device.extra.PREVIOUS_BOND_STATE"
|
||||
|
||||
def __init__(self, address: str = "00:00:00:00:00:00"):
|
||||
super().__init__()
|
||||
self._address = address
|
||||
self._name = "Unknown"
|
||||
self._bond_state = self.BOND_NONE
|
||||
self._device_type = self.DEVICE_TYPE_UNKNOWN
|
||||
|
||||
def getAddress(self) -> str:
|
||||
return self._address
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def setName(self, name: str):
|
||||
self._name = name
|
||||
|
||||
def getBondState(self) -> int:
|
||||
return self._bond_state
|
||||
|
||||
def setBondState(self, state: int):
|
||||
self._bond_state = state
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._device_type
|
||||
|
||||
def untergehende(self) -> bool:
|
||||
return self._bond_state == self.BOND_BONDED
|
||||
|
||||
def createRfcommSocketToServiceRecord(self, uuid_str: str) -> BluetoothSocket:
|
||||
sock = BluetoothSocket(BluetoothSocket.TYPE_RFCOMM)
|
||||
sock._device = self
|
||||
return sock
|
||||
|
||||
def createInsecureRfcommSocketToServiceRecord(self, uuid_str: str) -> BluetoothSocket:
|
||||
sock = BluetoothSocket(BluetoothSocket.TYPE_RFCOMM)
|
||||
sock._device = self
|
||||
return sock
|
||||
|
||||
def createL2capChannel(self, psm: int) -> BluetoothSocket:
|
||||
sock = BluetoothSocket(BluetoothSocket.TYPE_L2CAP)
|
||||
sock._device = self
|
||||
return sock
|
||||
|
||||
def createInsecureL2capChannel(self, psm: int) -> BluetoothSocket:
|
||||
sock = BluetoothSocket(BluetoothSocket.TYPE_L2CAP)
|
||||
sock._device = self
|
||||
return sock
|
||||
|
||||
def fetchUuidsWithSdp(self) -> bool:
|
||||
return False
|
||||
|
||||
def uuidEquals(self, uuid_str: str) -> bool:
|
||||
return False
|
||||
|
||||
def setPairingConfirmation(self, accept: bool):
|
||||
pass
|
||||
|
||||
def setPin(self, pin: bytes) -> bool:
|
||||
return True
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BluetoothDevice(address={self._address}, name={self._name})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothGattDescriptor
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothGattDescriptor(Stub):
|
||||
"""BluetoothGattDescriptor — GATT 描述符"""
|
||||
|
||||
ENABLE_NOTIFICATION_VALUE = bytes([0x01, 0x00])
|
||||
ENABLE_INDICATION_VALUE = bytes([0x02, 0x00])
|
||||
DISABLE_NOTIFICATION_VALUE = bytes([0x00, 0x00])
|
||||
|
||||
def __init__(self, uuid_str: str, permissions: int = 0):
|
||||
super().__init__()
|
||||
self._uuid = uuid_str
|
||||
self._value: Optional[bytes] = None
|
||||
self._permissions = permissions
|
||||
self._characteristic: Optional["BluetoothGattCharacteristic"] = None
|
||||
|
||||
def getUuid(self) -> str:
|
||||
return self._uuid
|
||||
|
||||
def getValue(self) -> Optional[bytes]:
|
||||
return self._value
|
||||
|
||||
def setValue(self, value: bytes):
|
||||
self._value = value
|
||||
|
||||
def getCharacteristic(self) -> Optional["BluetoothGattCharacteristic"]:
|
||||
return self._characteristic
|
||||
|
||||
def getPermissions(self) -> int:
|
||||
return self._permissions
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothGattCharacteristic
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothGattCharacteristic(Stub):
|
||||
"""BluetoothGattCharacteristic — GATT 特征"""
|
||||
|
||||
PROPERTY_BROADCAST = 1
|
||||
PROPERTY_READ = 2
|
||||
PROPERTY_WRITE_NO_RESPONSE = 4
|
||||
PROPERTY_WRITE = 8
|
||||
PROPERTY_NOTIFY = 16
|
||||
PROPERTY_INDICATE = 32
|
||||
PROPERTY_SIGNED_WRITE = 64
|
||||
PROPERTY_EXTENDED_PROPS = 128
|
||||
|
||||
WRITE_TYPE_NO_RESPONSE = 1
|
||||
WRITE_TYPE_DEFAULT = 2
|
||||
WRITE_TYPE_SIGNED = 4
|
||||
|
||||
PERMISSION_READ = 1
|
||||
PERMISSION_READ_ENCRYPTED = 2
|
||||
PERMISSION_READ_ENCRYPTED_MITM = 4
|
||||
PERMISSION_WRITE = 16
|
||||
PERMISSION_WRITE_ENCRYPTED = 32
|
||||
PERMISSION_WRITE_ENCRYPTED_MITM = 64
|
||||
|
||||
FORMAT_FLOAT = 0x20
|
||||
FORMAT_SFLOAT = 0x10
|
||||
FORMAT_SINT16 = 0x02
|
||||
FORMAT_SINT32 = 0x04
|
||||
FORMAT_SINT8 = 0x01
|
||||
FORMAT_UINT16 = 0x12
|
||||
FORMAT_UINT32 = 0x14
|
||||
FORMAT_UINT8 = 0x11
|
||||
|
||||
def __init__(self, uuid_str: str, properties: int = 0, permissions: int = 0):
|
||||
super().__init__()
|
||||
self._uuid = uuid_str
|
||||
self._properties = properties
|
||||
self._permissions = permissions
|
||||
self._write_type = self.WRITE_TYPE_DEFAULT
|
||||
self._value: Optional[bytes] = None
|
||||
self._service: Optional["BluetoothGattService"] = None
|
||||
self._descriptors: List[BluetoothGattDescriptor] = []
|
||||
|
||||
def getUuid(self) -> str:
|
||||
return self._uuid
|
||||
|
||||
def getService(self) -> Optional["BluetoothGattService"]:
|
||||
return self._service
|
||||
|
||||
def getPermissions(self) -> int:
|
||||
return self._permissions
|
||||
|
||||
def getProperties(self) -> int:
|
||||
return self._properties
|
||||
|
||||
def getWriteType(self) -> int:
|
||||
return self._write_type
|
||||
|
||||
def setWriteType(self, write_type: int):
|
||||
self._write_type = write_type
|
||||
|
||||
def getValue(self) -> Optional[bytes]:
|
||||
return self._value
|
||||
|
||||
def setValue(self, value: bytes):
|
||||
self._value = value
|
||||
|
||||
def getDescriptors(self) -> List[BluetoothGattDescriptor]:
|
||||
return list(self._descriptors)
|
||||
|
||||
def getDescriptor(self, uuid_str: str) -> Optional[BluetoothGattDescriptor]:
|
||||
for d in self._descriptors:
|
||||
if d.getUuid() == uuid_str:
|
||||
return d
|
||||
return None
|
||||
|
||||
def addDescriptor(self, descriptor: BluetoothGattDescriptor):
|
||||
self._descriptors.append(descriptor)
|
||||
descriptor._characteristic = self
|
||||
|
||||
def getIntValue(self, format_type: int, offset: int) -> Optional[int]:
|
||||
if self._value is None:
|
||||
return None
|
||||
import struct
|
||||
try:
|
||||
fmt_map = {
|
||||
self.FORMAT_UINT8: ("B", 1), self.FORMAT_SINT8: ("b", 1),
|
||||
self.FORMAT_UINT16: ("H", 2), self.FORMAT_SINT16: ("h", 2),
|
||||
self.FORMAT_UINT32: ("I", 4), self.FORMAT_SINT32: ("i", 4),
|
||||
self.FORMAT_FLOAT: ("f", 4), self.FORMAT_SFLOAT: ("e", 2),
|
||||
}
|
||||
fmt, size = fmt_map.get(format_type, ("B", 1))
|
||||
if offset + size > len(self._value):
|
||||
return None
|
||||
val = struct.unpack(fmt, self._value[offset:offset + size])[0]
|
||||
return int(val)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothGattService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothGattService(Stub):
|
||||
"""BluetoothGattService — GATT 服务"""
|
||||
|
||||
SERVICE_TYPE_PRIMARY = 0
|
||||
SERVICE_TYPE_SECONDARY = 1
|
||||
|
||||
def __init__(self, uuid_str: str, service_type: int = SERVICE_TYPE_PRIMARY):
|
||||
super().__init__()
|
||||
self._uuid = uuid_str
|
||||
self._type = service_type
|
||||
self._characteristics: List[BluetoothGattCharacteristic] = []
|
||||
self._included_services: List["BluetoothGattService"] = []
|
||||
self._device: Optional[BluetoothDevice] = None
|
||||
|
||||
def getUuid(self) -> str:
|
||||
return self._uuid
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._type
|
||||
|
||||
def getCharacteristics(self) -> List[BluetoothGattCharacteristic]:
|
||||
return list(self._characteristics)
|
||||
|
||||
def getCharacteristic(self, uuid_str: str) -> Optional[BluetoothGattCharacteristic]:
|
||||
for c in self._characteristics:
|
||||
if c.getUuid() == uuid_str:
|
||||
return c
|
||||
return None
|
||||
|
||||
def addCharacteristic(self, characteristic: BluetoothGattCharacteristic):
|
||||
self._characteristics.append(characteristic)
|
||||
characteristic._service = self
|
||||
|
||||
def getIncludedServices(self) -> List["BluetoothGattService"]:
|
||||
return list(self._included_services)
|
||||
|
||||
def addIncludedService(self, service: "BluetoothGattService"):
|
||||
self._included_services.append(service)
|
||||
|
||||
def getDevice(self) -> Optional[BluetoothDevice]:
|
||||
return self._device
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothGattCallback
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothGattCallback(Stub):
|
||||
"""BluetoothGattCallback — GATT 回调"""
|
||||
|
||||
def onConnectionStateChange(self, gatt: "BluetoothGatt", status: int, new_state: int):
|
||||
pass
|
||||
|
||||
def onServicesDiscovered(self, gatt: "BluetoothGatt", status: int):
|
||||
pass
|
||||
|
||||
def onCharacteristicRead(self, gatt: "BluetoothGatt",
|
||||
characteristic: BluetoothGattCharacteristic, value: bytes, status: int):
|
||||
pass
|
||||
|
||||
def onCharacteristicWrite(self, gatt: "BluetoothGatt",
|
||||
characteristic: BluetoothGattCharacteristic, status: int):
|
||||
pass
|
||||
|
||||
def onDescriptorRead(self, gatt: "BluetoothGatt",
|
||||
descriptor: BluetoothGattDescriptor, status: int):
|
||||
pass
|
||||
|
||||
def onDescriptorWrite(self, gatt: "BluetoothGatt",
|
||||
descriptor: BluetoothGattDescriptor, status: int):
|
||||
pass
|
||||
|
||||
def onCharacteristicChanged(self, gatt: "BluetoothGatt",
|
||||
characteristic: BluetoothGattCharacteristic, value: bytes):
|
||||
pass
|
||||
|
||||
def onReadRemoteRssy(self, gatt: "BluetoothGatt", rssi: int, status: int):
|
||||
pass
|
||||
|
||||
def onMtuChanged(self, gatt: "BluetoothGatt", mtu: int, status: int):
|
||||
pass
|
||||
|
||||
def onPhyUpdate(self, gatt: "BluetoothGatt", tx_phy: int, rx_phy: int, status: int):
|
||||
pass
|
||||
|
||||
def onPhyRead(self, gatt: "BluetoothGatt", tx_phy: int, rx_phy: int, status: int):
|
||||
pass
|
||||
|
||||
def onServiceChanged(self, gatt: "BluetoothGatt"):
|
||||
pass
|
||||
|
||||
def onNotificationSent(self, gatt: "BluetoothGatt", status: int):
|
||||
pass
|
||||
|
||||
def onConnectionUpdated(self, gatt: "BluetoothGatt", interval: int,
|
||||
latency: int, timeout: int, status: int):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothGatt
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothGatt(Stub):
|
||||
"""BluetoothGatt — GATT 客户端"""
|
||||
|
||||
CONNECTION_STATE_DISCONNECTED = 0
|
||||
CONNECTION_STATE_CONNECTING = 1
|
||||
CONNECTION_STATE_CONNECTED = 2
|
||||
CONNECTION_STATE_DISCONNECTING = 3
|
||||
|
||||
GATT_SUCCESS = 0
|
||||
GATT_FAILURE = 1
|
||||
GATT_CONNECTION_CONGESTED = 143
|
||||
GATT_INSUFFICIENT_AUTHENTICATION = 5
|
||||
GATT_INSUFFICIENT_ENCRYPTION = 15
|
||||
GATT_INSUFFICIENT_KEY_SIZE = 12
|
||||
GATT_INVALID_ATTRIBUTE_LENGTH = 13
|
||||
GATT_REQUEST_NOT_SUPPORTED = 6
|
||||
GATT_WRITE_NOT_PERMITTED = 3
|
||||
GATT_READ_NOT_PERMITTED = 2
|
||||
GATT_WRITE_NO_RESPONSE = 1
|
||||
GATT_WRITE_DEFAULT = 0
|
||||
|
||||
def __init__(self, device: BluetoothDevice):
|
||||
super().__init__()
|
||||
self._device = device
|
||||
self._services: List[BluetoothGattService] = []
|
||||
self._connected = False
|
||||
self._mtu = 23
|
||||
|
||||
def connect(self) -> bool:
|
||||
self._connected = True
|
||||
return True
|
||||
|
||||
def disconnect(self):
|
||||
self._connected = False
|
||||
|
||||
def close(self):
|
||||
self._connected = False
|
||||
self._services.clear()
|
||||
|
||||
def discoverServices(self) -> bool:
|
||||
return True
|
||||
|
||||
def getServices(self) -> List[BluetoothGattService]:
|
||||
return list(self._services)
|
||||
|
||||
def getService(self, uuid_str: str) -> Optional[BluetoothGattService]:
|
||||
for s in self._services:
|
||||
if s.getUuid() == uuid_str:
|
||||
return s
|
||||
return None
|
||||
|
||||
def getConnectedDevices(self) -> List[BluetoothDevice]:
|
||||
return [self._device] if self._connected else []
|
||||
|
||||
def getDevicesMatchingConnectionState(self, state: int) -> List[BluetoothDevice]:
|
||||
if state == self.CONNECTION_STATE_CONNECTED and self._connected:
|
||||
return [self._device]
|
||||
return []
|
||||
|
||||
def readCharacteristic(self, characteristic: BluetoothGattCharacteristic) -> bool:
|
||||
return True
|
||||
|
||||
def writeCharacteristic(self, characteristic: BluetoothGattCharacteristic) -> bool:
|
||||
return True
|
||||
|
||||
def readDescriptor(self, descriptor: BluetoothGattDescriptor) -> bool:
|
||||
return True
|
||||
|
||||
def writeDescriptor(self, descriptor: BluetoothGattDescriptor) -> bool:
|
||||
return True
|
||||
|
||||
def setCharacteristicNotification(self, characteristic: BluetoothGattCharacteristic,
|
||||
enable: bool) -> bool:
|
||||
return True
|
||||
|
||||
def readRemoteRssy(self) -> bool:
|
||||
return True
|
||||
|
||||
def requestMtu(self, mtu: int) -> bool:
|
||||
self._mtu = mtu
|
||||
return True
|
||||
|
||||
def getMtu(self) -> int:
|
||||
return self._mtu
|
||||
|
||||
def requestConnectionPriority(self, connection_priority: int) -> bool:
|
||||
return True
|
||||
|
||||
def getDevice(self) -> BluetoothDevice:
|
||||
return self._device
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothManager(Stub):
|
||||
"""BluetoothManager — 蓝牙管理器"""
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
|
||||
def getAdapter(self) -> Optional["BluetoothAdapter"]:
|
||||
return BluetoothAdapter.getDefaultAdapter()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothAdapter
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothAdapter(Stub):
|
||||
"""BluetoothAdapter — 蓝牙适配器"""
|
||||
|
||||
STATE_OFF = 10
|
||||
STATE_TURNING_ON = 11
|
||||
STATE_ON = 12
|
||||
STATE_TURNING_OFF = 13
|
||||
|
||||
ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE"
|
||||
ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"
|
||||
ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED"
|
||||
ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED"
|
||||
ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED"
|
||||
|
||||
SCAN_MODE_NONE = 20
|
||||
SCAN_MODE_CONNECTABLE = 21
|
||||
SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23
|
||||
|
||||
_default_adapter: Optional["BluetoothAdapter"] = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._enabled = True
|
||||
self._state = self.STATE_ON
|
||||
self._address = "00:1A:2B:3C:4D:5E"
|
||||
self._name = "MockBluetooth"
|
||||
self._discovering = False
|
||||
self._bonded_devices: List[BluetoothDevice] = []
|
||||
|
||||
@staticmethod
|
||||
def getDefaultAdapter() -> Optional["BluetoothAdapter"]:
|
||||
if BluetoothAdapter._default_adapter is None:
|
||||
BluetoothAdapter._default_adapter = BluetoothAdapter()
|
||||
return BluetoothAdapter._default_adapter
|
||||
|
||||
def isEnabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
def enable(self) -> bool:
|
||||
self._enabled = True
|
||||
self._state = self.STATE_ON
|
||||
return True
|
||||
|
||||
def disable(self) -> bool:
|
||||
self._enabled = False
|
||||
self._state = self.STATE_OFF
|
||||
return True
|
||||
|
||||
def getAddress(self) -> str:
|
||||
return self._address
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def setName(self, name: str) -> bool:
|
||||
self._name = name
|
||||
return True
|
||||
|
||||
def getBondedDevices(self) -> Set[BluetoothDevice]:
|
||||
return set(self._bonded_devices)
|
||||
|
||||
def startDiscovery(self) -> bool:
|
||||
self._discovering = True
|
||||
return True
|
||||
|
||||
def cancelDiscovery(self) -> bool:
|
||||
self._discovering = False
|
||||
return True
|
||||
|
||||
def isDiscovering(self) -> bool:
|
||||
return self._discovering
|
||||
|
||||
@staticmethod
|
||||
def checkBluetoothAddress(address: str) -> bool:
|
||||
import re
|
||||
return bool(re.match(r"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$", address))
|
||||
|
||||
def getProfileProxy(self, context: Any, listener: BluetoothProfileServiceListener,
|
||||
profile_id: int) -> bool:
|
||||
return True
|
||||
|
||||
def closeProfileProxy(self, profile_id: int, proxy: Any):
|
||||
pass
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def listenUsingRfcommWithServiceRecord(self, name: str, uuid_str: str) -> BluetoothServerSocket:
|
||||
return BluetoothServerSocket()
|
||||
|
||||
def listenUsingInsecureRfcommWithServiceRecord(self, name: str,
|
||||
uuid_str: str) -> BluetoothServerSocket:
|
||||
return BluetoothServerSocket()
|
||||
|
||||
def getRemoteDevice(self, address: str) -> Optional[BluetoothDevice]:
|
||||
if not BluetoothAdapter.checkBluetoothAddress(address):
|
||||
return None
|
||||
return BluetoothDevice(address)
|
||||
|
||||
def getLeScanner(self) -> Any:
|
||||
from .bluetooth_le import BluetoothLeScanner
|
||||
return BluetoothLeScanner()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BluetoothAdapter(name={self._name})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Additional profile stubs
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothHeadset(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothA2dp(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothHealth(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothHealthCallback(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothHealthAppConfiguration(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothCodecConfig(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothLeAudio(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothLeBroadcast(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothLeBroadcastMetadata(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothLeBroadcastSubgroup(Stub):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"BluetoothAdapter",
|
||||
"BluetoothDevice",
|
||||
"BluetoothServerSocket",
|
||||
"BluetoothSocket",
|
||||
"BluetoothClass",
|
||||
"BluetoothGatt",
|
||||
"BluetoothGattCallback",
|
||||
"BluetoothGattCharacteristic",
|
||||
"BluetoothGattDescriptor",
|
||||
"BluetoothGattService",
|
||||
"BluetoothHeadset",
|
||||
"BluetoothA2dp",
|
||||
"BluetoothHealth",
|
||||
"BluetoothHealthCallback",
|
||||
"BluetoothHealthAppConfiguration",
|
||||
"BluetoothProfile",
|
||||
"BluetoothProfileServiceListener",
|
||||
"BluetoothManager",
|
||||
"BluetoothCodecConfig",
|
||||
"BluetoothLeAudio",
|
||||
"BluetoothLeBroadcast",
|
||||
"BluetoothLeBroadcastMetadata",
|
||||
"BluetoothLeBroadcastSubgroup",
|
||||
]
|
||||
@@ -0,0 +1,512 @@
|
||||
"""
|
||||
Android BLE 组件 — BluetoothLeScanner, ScanCallback, ScanFilter 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScanRecord
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScanRecord(Stub):
|
||||
"""ScanRecord — 扫描记录"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._service_uuids: List[str] = []
|
||||
self._service_solicitation_uuids: List[str] = []
|
||||
self._service_data: Dict[str, bytes] = {}
|
||||
self._manufacturer_specific_data: Dict[int, bytes] = {}
|
||||
self._tx_power_level: int = -127
|
||||
self._device_name: str = ""
|
||||
self._advertise_flags: int = 0
|
||||
self._bytes_data: bytes = b""
|
||||
|
||||
def getServiceUuids(self) -> Optional[List[str]]:
|
||||
return list(self._service_uuids) if self._service_uuids else None
|
||||
|
||||
def getServiceSolicitationUuids(self) -> Optional[List[str]]:
|
||||
return list(self._service_solicitation_uuids) if self._service_solicitation_uuids else None
|
||||
|
||||
def getServiceData(self) -> Optional[Dict[str, bytes]]:
|
||||
return dict(self._service_data) if self._service_data else None
|
||||
|
||||
def getServiceDataForService(self, uuid_str: str) -> Optional[bytes]:
|
||||
return self._service_data.get(uuid_str)
|
||||
|
||||
def getManufacturerSpecificData(self) -> Optional[Dict[int, bytes]]:
|
||||
return dict(self._manufacturer_specific_data) if self._manufacturer_specific_data else None
|
||||
|
||||
def getTxPowerLevel(self) -> int:
|
||||
return self._tx_power_level
|
||||
|
||||
def getDeviceName(self) -> Optional[str]:
|
||||
return self._device_name or None
|
||||
|
||||
def getAdvertiseFlags(self) -> int:
|
||||
return self._advertise_flags
|
||||
|
||||
def getBytes(self) -> bytes:
|
||||
return self._bytes_data
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScanFilter
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScanFilter(Stub):
|
||||
"""ScanFilter — 扫描过滤器"""
|
||||
|
||||
class Builder(Stub):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._device_name: Optional[str] = None
|
||||
self._device_address: Optional[str] = None
|
||||
self._service_uuid: Optional[str] = None
|
||||
self._service_data: Optional[bytes] = None
|
||||
self._manufacturer_id: int = -1
|
||||
self._manufacturer_data: Optional[bytes] = None
|
||||
|
||||
def setDeviceName(self, name: str) -> "ScanFilter.Builder":
|
||||
self._device_name = name
|
||||
return self
|
||||
|
||||
def setDeviceAddress(self, address: str) -> "ScanFilter.Builder":
|
||||
self._device_address = address
|
||||
return self
|
||||
|
||||
def setServiceUuid(self, uuid_str: str) -> "ScanFilter.Builder":
|
||||
self._service_uuid = uuid_str
|
||||
return self
|
||||
|
||||
def setServiceData(self, data: bytes) -> "ScanFilter.Builder":
|
||||
self._service_data = data
|
||||
return self
|
||||
|
||||
def setManufacturerData(self, manufacturer_id: int, data: bytes) -> "ScanFilter.Builder":
|
||||
self._manufacturer_id = manufacturer_id
|
||||
self._manufacturer_data = data
|
||||
return self
|
||||
|
||||
def build(self) -> "ScanFilter":
|
||||
return ScanFilter(
|
||||
device_name=self._device_name,
|
||||
device_address=self._device_address,
|
||||
service_uuid=self._service_uuid,
|
||||
service_data=self._service_data,
|
||||
manufacturer_id=self._manufacturer_id,
|
||||
manufacturer_data=self._manufacturer_data,
|
||||
)
|
||||
|
||||
def __init__(self, device_name: Optional[str] = None,
|
||||
device_address: Optional[str] = None,
|
||||
service_uuid: Optional[str] = None,
|
||||
service_data: Optional[bytes] = None,
|
||||
manufacturer_id: int = -1,
|
||||
manufacturer_data: Optional[bytes] = None):
|
||||
super().__init__()
|
||||
self._device_name = device_name
|
||||
self._device_address = device_address
|
||||
self._service_uuid = service_uuid
|
||||
self._service_data = service_data
|
||||
self._manufacturer_id = manufacturer_id
|
||||
self._manufacturer_data = manufacturer_data
|
||||
|
||||
|
||||
class ScanFilterBuilder(ScanFilter.Builder):
|
||||
"""ScanFilter.Builder 别名"""
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScanSettings
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScanSettings(Stub):
|
||||
"""ScanSettings — 扫描设置"""
|
||||
|
||||
SCAN_MODE_OPPORTUNISTIC = -1
|
||||
SCAN_MODE_LOW_POWER = 0
|
||||
SCAN_MODE_BALANCED = 1
|
||||
SCAN_MODE_LOW_LATENCY = 2
|
||||
|
||||
CALLBACK_TYPE_ALL_MATCHES = 1
|
||||
CALLBACK_TYPE_FIRST_MATCH = 2
|
||||
CALLBACK_TYPE_MATCH_LOST = 4
|
||||
|
||||
MATCH_NUM_FEW_CFG = 2
|
||||
MATCH_NUM_MAX_CFG = 1
|
||||
MATCH_MODE_AGGRESSIVE = 0
|
||||
MATCH_MODE_STICKY = 1
|
||||
|
||||
class Builder(Stub):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._scan_mode = ScanSettings.SCAN_MODE_LOW_POWER
|
||||
self._callback_type = ScanSettings.CALLBACK_TYPE_ALL_MATCHES
|
||||
self._legacy = True
|
||||
self._phy = 1
|
||||
|
||||
def setScanMode(self, scan_mode: int) -> "ScanSettings.Builder":
|
||||
self._scan_mode = scan_mode
|
||||
return self
|
||||
|
||||
def setCallbackType(self, callback_type: int) -> "ScanSettings.Builder":
|
||||
self._callback_type = callback_type
|
||||
return self
|
||||
|
||||
def setLegacy(self, legacy: bool) -> "ScanSettings.Builder":
|
||||
self._legacy = legacy
|
||||
return self
|
||||
|
||||
def setPhy(self, phy: int) -> "ScanSettings.Builder":
|
||||
self._phy = phy
|
||||
return self
|
||||
|
||||
def build(self) -> "ScanSettings":
|
||||
return ScanSettings(
|
||||
scan_mode=self._scan_mode,
|
||||
callback_type=self._callback_type,
|
||||
legacy=self._legacy,
|
||||
phy=self._phy,
|
||||
)
|
||||
|
||||
def __init__(self, scan_mode: int = SCAN_MODE_LOW_POWER,
|
||||
callback_type: int = CALLBACK_TYPE_ALL_MATCHES,
|
||||
legacy: bool = True, phy: int = 1):
|
||||
super().__init__()
|
||||
self._scan_mode = scan_mode
|
||||
self._callback_type = callback_type
|
||||
self._legacy = legacy
|
||||
self._phy = phy
|
||||
|
||||
def getScanMode(self) -> int:
|
||||
return self._scan_mode
|
||||
|
||||
def getCallbackType(self) -> int:
|
||||
return self._callback_type
|
||||
|
||||
def getLegacy(self) -> bool:
|
||||
return self._legacy
|
||||
|
||||
def getPhy(self) -> int:
|
||||
return self._phy
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScanResult
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScanResult(Stub):
|
||||
"""ScanResult — BLE 扫描结果"""
|
||||
|
||||
DATA_COMPLETE = 0
|
||||
DATA_TRUNCATED = 2
|
||||
PHY_LE_1M = 1
|
||||
PHY_LE_2M = 2
|
||||
PHY_LE_CODED = 3
|
||||
PERIODIC_INTERVAL_NOT_PRESENT = 0
|
||||
|
||||
def __init__(self, device: Any = None, scan_record: Optional[ScanRecord] = None,
|
||||
rssi: int = -127, timestamp_nanos: int = 0):
|
||||
super().__init__()
|
||||
self._device = device
|
||||
self._scan_record = scan_record or ScanRecord()
|
||||
self._rssi = rssi
|
||||
self._timestamp_nanos = timestamp_nanos
|
||||
self._primary_phy = self.PHY_LE_1M
|
||||
self._secondary_phy = 0
|
||||
self._advertising_sid = -1
|
||||
self._tx_power = -127
|
||||
self._periodic_interval = self.PERIODIC_INTERVAL_NOT_PRESENT
|
||||
self._data_status = self.DATA_COMPLETE
|
||||
|
||||
def getDevice(self) -> Any:
|
||||
return self._device
|
||||
|
||||
def getScanRecord(self) -> Optional[ScanRecord]:
|
||||
return self._scan_record
|
||||
|
||||
def getRssi(self) -> int:
|
||||
return self._rssi
|
||||
|
||||
def getTimestampNanos(self) -> int:
|
||||
return self._timestamp_nanos
|
||||
|
||||
def getPrimaryPhy(self) -> int:
|
||||
return self._primary_phy
|
||||
|
||||
def getSecondaryPhy(self) -> int:
|
||||
return self._secondary_phy
|
||||
|
||||
def getAdvertisingSid(self) -> int:
|
||||
return self._advertising_sid
|
||||
|
||||
def getTxPower(self) -> int:
|
||||
return self._tx_power
|
||||
|
||||
def getPeriodicAdvertisingInterval(self) -> int:
|
||||
return self._periodic_interval
|
||||
|
||||
def getDataStatus(self) -> int:
|
||||
return self._data_status
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScanCallback
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScanCallback(Stub):
|
||||
"""ScanCallback — 扫描回调"""
|
||||
|
||||
SCAN_FAILED_ALREADY_STARTED = 1
|
||||
SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2
|
||||
SCAN_FAILED_INTERNAL_ERROR = 3
|
||||
SCAN_FAILED_FEATURE_UNSUPPORTED = 4
|
||||
SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES = 5
|
||||
SCAN_FAILED_SCANNING_TOO_FREQUENTLY = 6
|
||||
|
||||
def onScanResult(self, callback_type: int, result: ScanResult):
|
||||
pass
|
||||
|
||||
def onBatchScanResults(self, results: List[ScanResult]):
|
||||
pass
|
||||
|
||||
def onScanFailed(self, error_code: int):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothLeScanner
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothLeScanner(Stub):
|
||||
"""BluetoothLeScanner — BLE 扫描器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def startScan(self, filters: Optional[List[ScanFilter]] = None,
|
||||
settings: Optional[ScanSettings] = None,
|
||||
callback: Optional[ScanCallback] = None):
|
||||
pass
|
||||
|
||||
def startScanWithFilters(self, filters: List[ScanFilter],
|
||||
callback: ScanCallback):
|
||||
self.startScan(filters, None, callback)
|
||||
|
||||
def stopScan(self, callback: Optional[ScanCallback] = None):
|
||||
pass
|
||||
|
||||
def flushPendingScanResults(self, callback: ScanCallback):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AdvertiseData / AdvertiseSettings / AdvertiseCallback
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AdvertiseData(Stub):
|
||||
"""AdvertiseData — 广播数据"""
|
||||
|
||||
class Builder(Stub):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._include_device_name = False
|
||||
self._include_tx_power_level = False
|
||||
self._service_uuids: List[str] = []
|
||||
self._service_data: Dict[str, bytes] = {}
|
||||
self._manufacturer_data: Dict[int, bytes] = {}
|
||||
|
||||
def setIncludeDeviceName(self, include: bool) -> "AdvertiseData.Builder":
|
||||
self._include_device_name = include
|
||||
return self
|
||||
|
||||
def setIncludeTxPowerLevel(self, include: bool) -> "AdvertiseData.Builder":
|
||||
self._include_tx_power_level = include
|
||||
return self
|
||||
|
||||
def addServiceUuid(self, uuid_str: str) -> "AdvertiseData.Builder":
|
||||
self._service_uuids.append(uuid_str)
|
||||
return self
|
||||
|
||||
def addServiceData(self, uuid_str: str, data: bytes) -> "AdvertiseData.Builder":
|
||||
self._service_data[uuid_str] = data
|
||||
return self
|
||||
|
||||
def addManufacturerData(self, manufacturer_id: int, data: bytes) -> "AdvertiseData.Builder":
|
||||
self._manufacturer_data[manufacturer_id] = data
|
||||
return self
|
||||
|
||||
def build(self) -> "AdvertiseData":
|
||||
return AdvertiseData(
|
||||
include_device_name=self._include_device_name,
|
||||
include_tx_power_level=self._include_tx_power_level,
|
||||
service_uuids=self._service_uuids,
|
||||
service_data=self._service_data,
|
||||
manufacturer_data=self._manufacturer_data,
|
||||
)
|
||||
|
||||
def __init__(self, include_device_name: bool = False,
|
||||
include_tx_power_level: bool = False,
|
||||
service_uuids: Optional[List[str]] = None,
|
||||
service_data: Optional[Dict[str, bytes]] = None,
|
||||
manufacturer_data: Optional[Dict[int, bytes]] = None):
|
||||
super().__init__()
|
||||
self._include_device_name = include_device_name
|
||||
self._include_tx_power_level = include_tx_power_level
|
||||
self._service_uuids = service_uuids or []
|
||||
self._service_data = service_data or {}
|
||||
self._manufacturer_data = manufacturer_data or {}
|
||||
|
||||
|
||||
class AdvertiseDataBuilder(AdvertiseData.Builder):
|
||||
pass
|
||||
|
||||
|
||||
class AdvertiseSettings(Stub):
|
||||
"""AdvertiseSettings — 广播设置"""
|
||||
|
||||
ADVERTISE_MODE_LOW_POWER = 0
|
||||
ADVERTISE_MODE_BALANCED = 1
|
||||
ADVERTISE_MODE_LOW_LATENCY = 2
|
||||
|
||||
ADVERTISE_TX_POWER_ULTRA_LOW = 0
|
||||
ADVERTISE_TX_POWER_LOW = 1
|
||||
ADVERTISE_TX_POWER_MEDIUM = 2
|
||||
ADVERTISE_TX_POWER_HIGH = 3
|
||||
|
||||
class Builder(Stub):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._advertise_mode = AdvertiseSettings.ADVERTISE_MODE_LOW_POWER
|
||||
self._tx_power_level = AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM
|
||||
self._connectable = True
|
||||
self._timeout = 0
|
||||
|
||||
def setAdvertiseMode(self, mode: int) -> "AdvertiseSettings.Builder":
|
||||
self._advertise_mode = mode
|
||||
return self
|
||||
|
||||
def setTxPowerLevel(self, power: int) -> "AdvertiseSettings.Builder":
|
||||
self._tx_power_level = power
|
||||
return self
|
||||
|
||||
def setConnectable(self, connectable: bool) -> "AdvertiseSettings.Builder":
|
||||
self._connectable = connectable
|
||||
return self
|
||||
|
||||
def setTimeout(self, timeout: int) -> "AdvertiseSettings.Builder":
|
||||
self._timeout = timeout
|
||||
return self
|
||||
|
||||
def build(self) -> "AdvertiseSettings":
|
||||
return AdvertiseSettings(
|
||||
advertise_mode=self._advertise_mode,
|
||||
tx_power_level=self._tx_power_level,
|
||||
connectable=self._connectable,
|
||||
timeout=self._timeout,
|
||||
)
|
||||
|
||||
def __init__(self, advertise_mode: int = ADVERTISE_MODE_LOW_POWER,
|
||||
tx_power_level: int = ADVERTISE_TX_POWER_MEDIUM,
|
||||
connectable: bool = True, timeout: int = 0):
|
||||
super().__init__()
|
||||
self._advertise_mode = advertise_mode
|
||||
self._tx_power_level = tx_power_level
|
||||
self._connectable = connectable
|
||||
self._timeout = timeout
|
||||
|
||||
def getAdvertiseMode(self) -> int:
|
||||
return self._advertise_mode
|
||||
|
||||
def getTxPowerLevel(self) -> int:
|
||||
return self._tx_power_level
|
||||
|
||||
def isConnectable(self) -> bool:
|
||||
return self._connectable
|
||||
|
||||
def getTimeout(self) -> int:
|
||||
return self._timeout
|
||||
|
||||
|
||||
class AdvertiseCallback(Stub):
|
||||
"""AdvertiseCallback — 广播回调"""
|
||||
|
||||
ADVERTISE_FAILED_DATA_TOO_LARGE = 1
|
||||
ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2
|
||||
ADVERTISE_FAILED_ALREADY_STARTED = 3
|
||||
ADVERTISE_FAILED_INTERNAL_ERROR = 4
|
||||
ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5
|
||||
|
||||
def onStartSuccess(self, settings_in_effect: AdvertiseSettings):
|
||||
pass
|
||||
|
||||
def onStartFailure(self, error_code: int):
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothLeAdvertiser(Stub):
|
||||
"""BluetoothLeAdvertiser — BLE 广播器"""
|
||||
|
||||
def startAdvertising(self, settings: AdvertiseSettings,
|
||||
advertise_data: AdvertiseData,
|
||||
scan_response: Optional[AdvertiseData],
|
||||
callback: AdvertiseCallback):
|
||||
try:
|
||||
callback.onStartSuccess(settings)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def stopAdvertising(self, callback: AdvertiseCallback):
|
||||
pass
|
||||
|
||||
|
||||
class PeriodicAdvertisingManager(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class PeriodicAdvertisingCallback(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class DistanceMeasurementManager(Stub):
|
||||
pass
|
||||
|
||||
|
||||
class DistanceMeasurementResult(Stub):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"BluetoothLeScanner",
|
||||
"ScanCallback",
|
||||
"ScanResult",
|
||||
"ScanFilter",
|
||||
"ScanFilterBuilder",
|
||||
"ScanSettings",
|
||||
"ScanRecord",
|
||||
"BluetoothLeAdvertiser",
|
||||
"AdvertiseCallback",
|
||||
"AdvertiseData",
|
||||
"AdvertiseDataBuilder",
|
||||
"AdvertiseSettings",
|
||||
"PeriodicAdvertisingManager",
|
||||
"PeriodicAdvertisingCallback",
|
||||
"DistanceMeasurementManager",
|
||||
"DistanceMeasurementResult",
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,801 @@
|
||||
"""
|
||||
Android PackageManager 组件 — PackageManager, PackageInfo, ApplicationInfo 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 数据存储路径
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
_PACKAGE_DATA_DIR = os.path.join(_app_data_dir, "package_data")
|
||||
os.makedirs(_PACKAGE_DATA_DIR, exist_ok=True)
|
||||
|
||||
_DEFAULT_PACKAGE_INFO_PATH = os.path.join(_PACKAGE_DATA_DIR, f"{_package_name}.json")
|
||||
|
||||
|
||||
def _get_default_package_info() -> Dict[str, Any]:
|
||||
"""获取默认包信息"""
|
||||
return {
|
||||
"packageName": _package_name,
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": 1,
|
||||
"applicationInfo": {
|
||||
"name": _package_name,
|
||||
"packageName": _package_name,
|
||||
"className": f"{_package_name}.MainApplication",
|
||||
"icon": 0,
|
||||
"targetSdkVersion": 34,
|
||||
"sourceDir": os.path.join(_app_data_dir, "base.apk"),
|
||||
"dataDir": _app_data_dir,
|
||||
"enabled": True,
|
||||
"flags": 0,
|
||||
"theme": 0,
|
||||
"labelRes": 0,
|
||||
"descriptionRes": 0,
|
||||
"logo": 0,
|
||||
"banner": 0,
|
||||
"processName": _package_name,
|
||||
"uid": 10001,
|
||||
"manageSpaceActivityName": "",
|
||||
"taskAffinity": "",
|
||||
"permission": "",
|
||||
},
|
||||
"activities": [],
|
||||
"services": [],
|
||||
"receivers": [],
|
||||
"providers": [],
|
||||
"permissions": [],
|
||||
"requestedPermissions": [
|
||||
"android.permission.INTERNET",
|
||||
"android.permission.ACCESS_NETWORK_STATE",
|
||||
],
|
||||
"features": {
|
||||
"android.hardware.camera": True,
|
||||
"android.hardware.wifi": True,
|
||||
"android.hardware.bluetooth": True,
|
||||
"android.hardware.location": True,
|
||||
"android.hardware.sensor.accelerometer": True,
|
||||
"android.hardware.sensor.gyroscope": True,
|
||||
"android.hardware.touchscreen": True,
|
||||
"android.hardware.microphone": True,
|
||||
"android.hardware.nfc": False,
|
||||
"android.hardware.telephony": True,
|
||||
"android.software.picture_in_picture": True,
|
||||
"android.hardware.usb.host": False,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PackageItemInfo (基类)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PackageItemInfo(Stub):
|
||||
"""PackageItemInfo — 包条目信息基类"""
|
||||
|
||||
def __init__(self, data: Optional[Dict[str, Any]] = None):
|
||||
super().__init__()
|
||||
data = data or {}
|
||||
self.name = data.get("name", "")
|
||||
self.packageName = data.get("packageName", "")
|
||||
self.labelRes = data.get("labelRes", 0)
|
||||
self.nonLocalizedLabel = data.get("nonLocalizedLabel", None)
|
||||
self.icon = data.get("icon", 0)
|
||||
self.banner = data.get("banner", 0)
|
||||
self.logo = data.get("logo", 0)
|
||||
|
||||
def loadLabel(self, pm: "PackageManager") -> str:
|
||||
return self.nonLocalizedLabel or self.name.split(".")[-1] if self.name else ""
|
||||
|
||||
def loadIcon(self, pm: "PackageManager") -> Any:
|
||||
return None
|
||||
|
||||
def loadLogo(self, pm: "PackageManager") -> Any:
|
||||
return None
|
||||
|
||||
def loadBanner(self, pm: "PackageManager") -> Any:
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.packageName}:{self.name})"
|
||||
|
||||
|
||||
class ComponentInfo(PackageItemInfo):
|
||||
"""ComponentInfo — 组件信息基类"""
|
||||
|
||||
def __init__(self, data: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(data)
|
||||
data = data or {}
|
||||
self.processName = data.get("processName", self.packageName)
|
||||
self.splitName = data.get("splitName", None)
|
||||
self.enabled = data.get("enabled", True)
|
||||
self.exported = data.get("exported", False)
|
||||
self.directBootAware = data.get("directBootAware", False)
|
||||
|
||||
def getComponentName(self) -> Any:
|
||||
from .content import ComponentName
|
||||
return ComponentName(self.packageName, self.name)
|
||||
|
||||
def isEnabled(self) -> bool:
|
||||
return self.enabled
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ApplicationInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ApplicationInfo(ComponentInfo):
|
||||
"""ApplicationInfo — 应用信息"""
|
||||
|
||||
FLAG_SYSTEM = 1
|
||||
FLAG_DEBUGGABLE = 2
|
||||
FLAG_HAS_CODE = 4
|
||||
FLAG_ALLOW_CLEAR_USER_DATA = 0x00400000
|
||||
FLAG_ALLOW_BACKUP = 0x00008000
|
||||
FLAG_EXTERNAL_STORAGE = 0x00100000
|
||||
FLAG_LARGE_HEAP = 0x00100000
|
||||
FLAG_MULTIPROCESS = 0x00010000
|
||||
FLAG_TEST_ONLY = 0x00000100
|
||||
FLAG_STOPPED = 0x00200000
|
||||
FLAG_RESTART_ON_UPGRADE = 0x00000020
|
||||
|
||||
def __init__(self, data: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(data)
|
||||
data = data or {}
|
||||
self.taskAffinity = data.get("taskAffinity", "")
|
||||
self.permission = data.get("permission", "")
|
||||
self.processName = data.get("processName", self.packageName)
|
||||
self.className = data.get("className", "")
|
||||
self.descriptionRes = data.get("descriptionRes", 0)
|
||||
self.theme = data.get("theme", 0)
|
||||
self.flags = data.get("flags", 0)
|
||||
self.sourceDir = data.get("sourceDir", "")
|
||||
self.publicSourceDir = data.get("publicSourceDir", self.sourceDir)
|
||||
self.dataDir = data.get("dataDir", "")
|
||||
self.nativeLibraryDir = data.get("nativeLibraryDir", "")
|
||||
self.metaData = data.get("metaData", None)
|
||||
self.manageSpaceActivityName = data.get("manageSpaceActivityName", "")
|
||||
self.targetSdkVersion = data.get("targetSdkVersion", 34)
|
||||
self.uid = data.get("uid", 10001)
|
||||
self.backupAgentName = data.get("backupAgentName", "")
|
||||
self.enabled = data.get("enabled", True)
|
||||
|
||||
def loadDescription(self, pm: "PackageManager") -> Optional[str]:
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ApplicationInfo({self.packageName}, targetSdk={self.targetSdkVersion})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ActivityInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ActivityInfo(ComponentInfo):
|
||||
"""ActivityInfo — Activity 组件信息"""
|
||||
|
||||
CONFIG_MCC = 0x0001
|
||||
CONFIG_MNC = 0x0002
|
||||
CONFIG_LOCALE = 0x0004
|
||||
CONFIG_TOUCHSCREEN = 0x0008
|
||||
CONFIG_KEYBOARD = 0x0010
|
||||
CONFIG_KEYBOARD_HIDDEN = 0x0020
|
||||
CONFIG_NAVIGATION = 0x0040
|
||||
CONFIG_ORIENTATION = 0x0080
|
||||
CONFIG_SCREEN_LAYOUT = 0x0100
|
||||
CONFIG_UI_MODE = 0x0200
|
||||
CONFIG_SCREEN_SIZE = 0x0400
|
||||
CONFIG_SMALLEST_SCREEN_SIZE = 0x0800
|
||||
CONFIG_DENSITY = 0x1000
|
||||
CONFIG_LAYOUT_DIRECTION = 0x2000
|
||||
CONFIG_COLOR_MODE = 0x4000
|
||||
|
||||
LAUNCH_MULTIPLE = 0
|
||||
LAUNCH_SINGLE_TOP = 1
|
||||
LAUNCH_SINGLE_TASK = 2
|
||||
LAUNCH_SINGLE_INSTANCE = 3
|
||||
LAUNCH_SINGLE_INSTANCE_PER_TASK = 4
|
||||
|
||||
SCREEN_ORIENTATION_UNSPECIFIED = -1
|
||||
SCREEN_ORIENTATION_LANDSCAPE = 0
|
||||
SCREEN_ORIENTATION_PORTRAIT = 1
|
||||
SCREEN_ORIENTATION_USER = 2
|
||||
SCREEN_ORIENTATION_BEHIND = 3
|
||||
SCREEN_ORIENTATION_SENSOR = 4
|
||||
SCREEN_ORIENTATION_NOSENSOR = 5
|
||||
SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6
|
||||
SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7
|
||||
SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8
|
||||
SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9
|
||||
SCREEN_ORIENTATION_FULL_SENSOR = 10
|
||||
SCREEN_ORIENTATION_USER_LANDSCAPE = 11
|
||||
SCREEN_ORIENTATION_USER_PORTRAIT = 12
|
||||
SCREEN_ORIENTATION_FULL_USER = 13
|
||||
SCREEN_ORIENTATION_LOCKED = 14
|
||||
|
||||
def __init__(self, data: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(data)
|
||||
data = data or {}
|
||||
self.launchMode = data.get("launchMode", self.LAUNCH_MULTIPLE)
|
||||
self.screenOrientation = data.get("screenOrientation", self.SCREEN_ORIENTATION_UNSPECIFIED)
|
||||
self.configChanges = data.get("configChanges", 0)
|
||||
self.softInputMode = data.get("softInputMode", 0)
|
||||
self.theme = data.get("theme", 0)
|
||||
self.parentActivityName = data.get("parentActivityName", "")
|
||||
self.permission = data.get("permission", "")
|
||||
self.taskAffinity = data.get("taskAffinity", "")
|
||||
self.targetActivity = data.get("targetActivity", "")
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ActivityInfo({self.name}, launchMode={self.launchMode})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ServiceInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ServiceInfo(ComponentInfo):
|
||||
"""ServiceInfo — Service 组件信息"""
|
||||
|
||||
FLAG_STOP_WITH_TASK = 0x01
|
||||
FLAG_ISOLATED_PROCESS = 0x02
|
||||
FLAG_EXTERNAL_SERVICE = 0x04
|
||||
FLAG_SINGLE_USER = 0x40000000
|
||||
FLAG_USE_APP_OP = 0x08
|
||||
|
||||
def __init__(self, data: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(data)
|
||||
data = data or {}
|
||||
self.flags = data.get("flags", 0)
|
||||
self.permission = data.get("permission", "")
|
||||
self.foregroundServiceType = data.get("foregroundServiceType", 0)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ServiceInfo({self.name})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ProviderInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ProviderInfo(ComponentInfo):
|
||||
"""ProviderInfo — ContentProvider 组件信息"""
|
||||
|
||||
def __init__(self, data: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(data)
|
||||
data = data or {}
|
||||
self.authority = data.get("authority", "")
|
||||
self.readPermission = data.get("readPermission", "")
|
||||
self.writePermission = data.get("writePermission", "")
|
||||
self.grantUriPermissions = data.get("grantUriPermissions", False)
|
||||
self.multiprocess = data.get("multiprocess", False)
|
||||
self.initOrder = data.get("initOrder", 0)
|
||||
self.flags = data.get("flags", 0)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ProviderInfo({self.name}, authority={self.authority})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PermissionInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PermissionInfo(PackageItemInfo):
|
||||
"""PermissionInfo — 权限信息"""
|
||||
|
||||
PROTECTION_NORMAL = 0
|
||||
PROTECTION_DANGEROUS = 1
|
||||
PROTECTION_SIGNATURE = 2
|
||||
PROTECTION_SIGNATURE_OR_SYSTEM = 3
|
||||
PROTECTION_INTERNAL = 4
|
||||
|
||||
FLAG_COSTS_MONEY = 0x01
|
||||
FLAG_HARDWARE_RESTRICTED = 0x04
|
||||
FLAG_IMMUTABLY_RESTRICTED = 0x08
|
||||
FLAG_INSTALLED = 0x10
|
||||
|
||||
def __init__(self, data: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(data)
|
||||
data = data or {}
|
||||
self.group = data.get("group", "")
|
||||
self.descriptionRes = data.get("descriptionRes", 0)
|
||||
self.protectionLevel = data.get("protectionLevel", self.PROTECTION_NORMAL)
|
||||
self.flags = data.get("flags", 0)
|
||||
|
||||
def loadDescription(self, pm: "PackageManager") -> Optional[str]:
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PermissionInfo({self.name}, protectionLevel={self.protectionLevel})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ResolveInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ResolveInfo(Stub):
|
||||
"""ResolveInfo — 解析意图的结果"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.activityInfo: Optional[ActivityInfo] = None
|
||||
self.serviceInfo: Optional[ServiceInfo] = None
|
||||
self.providerInfo: Optional[ProviderInfo] = None
|
||||
self.filter: Optional[Any] = None
|
||||
self.priority = 0
|
||||
self.match = 0
|
||||
self.preferredOrder = 0
|
||||
self.isDefault = False
|
||||
self.labelRes = 0
|
||||
self.nonLocalizedLabel = None
|
||||
self.icon = 0
|
||||
self.resolvePackageName = ""
|
||||
self.specificIndex = -1
|
||||
self.system = False
|
||||
|
||||
def loadLabel(self, pm: "PackageManager") -> str:
|
||||
if self.activityInfo:
|
||||
return self.activityInfo.name.split(".")[-1]
|
||||
if self.serviceInfo:
|
||||
return self.serviceInfo.name.split(".")[-1]
|
||||
return ""
|
||||
|
||||
def loadIcon(self, pm: "PackageManager"):
|
||||
return None
|
||||
|
||||
def getIconResource(self) -> int:
|
||||
return self.icon
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ResolveInfo(priority={self.priority})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PackageInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PackageInfo(Stub):
|
||||
"""PackageInfo — 包信息"""
|
||||
|
||||
INSTALL_LOCATION_AUTO = 0
|
||||
INSTALL_LOCATION_INTERNAL_ONLY = 1
|
||||
INSTALL_LOCATION_PREFER_EXTERNAL = 2
|
||||
REQUESTED_PERMISSION_REQUIRED = 0x00100000
|
||||
REQUESTED_PERMISSION_GRANTED = 0x01000000
|
||||
REQUESTED_PERMISSION_DENIED = 0x02000000
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.packageName = ""
|
||||
self.versionName = ""
|
||||
self.versionCode = 0
|
||||
self.applicationInfo: Optional[ApplicationInfo] = None
|
||||
self.activities: List[ActivityInfo] = []
|
||||
self.services: List[ServiceInfo] = []
|
||||
self.receivers: List[ActivityInfo] = []
|
||||
self.providers: List[ProviderInfo] = []
|
||||
self.permissions: List[PermissionInfo] = []
|
||||
self.requestedPermissions: List[str] = []
|
||||
self.requestedPermissionsFlags: List[int] = []
|
||||
self.sharedUserId = ""
|
||||
self.sharedUserLabel = 0
|
||||
self.firstInstallTime = 0
|
||||
self.lastUpdateTime = 0
|
||||
self.gids: List[int] = []
|
||||
self.installLocation = self.INSTALL_LOCATION_AUTO
|
||||
self.configPreferences: List = []
|
||||
self.featureGroups: List = []
|
||||
self.packageName_ = ""
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"PackageInfo({self.packageName} v{self.versionName} "
|
||||
f"code={self.versionCode})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Signature
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Signature(Stub):
|
||||
"""Signature — APK 签名"""
|
||||
|
||||
def __init__(self, signature: str = ""):
|
||||
super().__init__()
|
||||
self._signature = signature
|
||||
|
||||
def toCharsString(self) -> str:
|
||||
return self._signature
|
||||
|
||||
def toByteArray(self) -> bytes:
|
||||
return self._signature.encode("utf-8") if self._signature else b""
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if isinstance(other, Signature):
|
||||
return self._signature == other._signature
|
||||
return False
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._signature) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return self._signature[:64] if len(self._signature) > 64 else self._signature
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PackageStats
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PackageStats(Stub):
|
||||
"""PackageStats — 包存储统计"""
|
||||
|
||||
def __init__(self, package_name: str = ""):
|
||||
super().__init__()
|
||||
self.packageName = package_name
|
||||
self.codeSize = 0
|
||||
self.dataSize = 0
|
||||
self.cacheSize = 0
|
||||
self.externalCodeSize = 0
|
||||
self.externalDataSize = 0
|
||||
self.externalCacheSize = 0
|
||||
self.externalMediaSize = 0
|
||||
self.externalObbSize = 0
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"PackageStats({self.packageName}, "
|
||||
f"code={self.codeSize}, data={self.dataSize})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PackageManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PackageManager(Stub):
|
||||
"""PackageManager — 包管理器"""
|
||||
|
||||
# Flag 常量
|
||||
GET_ACTIVITIES = 1
|
||||
GET_CONFIGURATIONS = 2
|
||||
GET_GIDS = 4
|
||||
GET_INSTRUMENTATION = 8
|
||||
GET_INTENT_FILTERS = 16
|
||||
GET_META_DATA = 128
|
||||
GET_PERMISSIONS = 4096
|
||||
GET_PROVIDERS = 8
|
||||
GET_RECEIVERS = 4
|
||||
GET_SERVICES = 2
|
||||
GET_SHARED_LIBRARY_FILES = 32
|
||||
GET_SIGNATURES = 64
|
||||
GET_UNINSTALLED_PACKAGES = 512
|
||||
GET_URI_PERMISSION_PATTERNS = 256
|
||||
MATCH_ALL = 0x10000
|
||||
MATCH_DISABLED_COMPONENTS = 0x200
|
||||
MATCH_DISABLED_UNTIL_USED_COMPONENTS = 0x8000
|
||||
MATCH_DEFAULT_ONLY = 0x10000
|
||||
MATCH_SYSTEM_ONLY = 0x800000
|
||||
MATCH_UNINSTALLED_PACKAGES = 0x2000000
|
||||
PERMISSION_DENIED = -1
|
||||
PERMISSION_GRANTED = 0
|
||||
|
||||
# 特性常量
|
||||
FEATURE_CAMERA = "android.hardware.camera"
|
||||
FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus"
|
||||
FEATURE_CAMERA_FLASH = "android.hardware.camera.flash"
|
||||
FEATURE_CAMERA_FRONT = "android.hardware.camera.front"
|
||||
FEATURE_LOCATION = "android.hardware.location"
|
||||
FEATURE_LOCATION_GPS = "android.hardware.location.gps"
|
||||
FEATURE_LOCATION_NETWORK = "android.hardware.location.network"
|
||||
FEATURE_MICROPHONE = "android.hardware.microphone"
|
||||
FEATURE_NFC = "android.hardware.nfc"
|
||||
FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer"
|
||||
FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer"
|
||||
FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass"
|
||||
FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope"
|
||||
FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light"
|
||||
FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity"
|
||||
FEATURE_TELEPHONY = "android.hardware.telephony"
|
||||
FEATURE_TOUCHSCREEN = "android.hardware.touchscreen"
|
||||
FEATURE_USB_HOST = "android.hardware.usb.host"
|
||||
FEATURE_WIFI = "android.hardware.wifi"
|
||||
FEATURE_BLUETOOTH = "android.hardware.bluetooth"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._packages: Dict[str, Dict[str, Any]] = {}
|
||||
self._load_packages()
|
||||
|
||||
def _load_packages(self):
|
||||
"""从 JSON 文件加载包信息"""
|
||||
try:
|
||||
if os.path.exists(_DEFAULT_PACKAGE_INFO_PATH):
|
||||
with open(_DEFAULT_PACKAGE_INFO_PATH, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if data.get("packageName"):
|
||||
self._packages[data["packageName"]] = data
|
||||
else:
|
||||
# 创建默认信息
|
||||
default_info = _get_default_package_info()
|
||||
self._packages[_package_name] = default_info
|
||||
os.makedirs(os.path.dirname(_DEFAULT_PACKAGE_INFO_PATH), exist_ok=True)
|
||||
with open(_DEFAULT_PACKAGE_INFO_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(default_info, f, indent=2, ensure_ascii=False)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
self._packages[_package_name] = _get_default_package_info()
|
||||
|
||||
def _make_app_info(self, data: Dict) -> ApplicationInfo:
|
||||
ai_data = data.get("applicationInfo", data)
|
||||
return ApplicationInfo(ai_data)
|
||||
|
||||
def _make_activity_info(self, data: Dict) -> ActivityInfo:
|
||||
return ActivityInfo(data)
|
||||
|
||||
def _make_service_info(self, data: Dict) -> ServiceInfo:
|
||||
return ServiceInfo(data)
|
||||
|
||||
def _make_provider_info(self, data: Dict) -> ProviderInfo:
|
||||
return ProviderInfo(data)
|
||||
|
||||
def getPackageInfo(self, package_name: str, flags: int) -> Optional[PackageInfo]:
|
||||
if package_name not in self._packages:
|
||||
return None
|
||||
raw = self._packages[package_name]
|
||||
pi = PackageInfo()
|
||||
pi.packageName = raw.get("packageName", package_name)
|
||||
pi.versionName = raw.get("versionName", "1.0.0")
|
||||
pi.versionCode = raw.get("versionCode", 1)
|
||||
pi.applicationInfo = self._make_app_info(raw)
|
||||
|
||||
if flags & self.GET_ACTIVITIES:
|
||||
pi.activities = [self._make_activity_info(a) for a in raw.get("activities", [])]
|
||||
if flags & self.GET_SERVICES:
|
||||
pi.services = [self._make_service_info(s) for s in raw.get("services", [])]
|
||||
if flags & self.GET_RECEIVERS:
|
||||
pi.receivers = [self._make_activity_info(r) for r in raw.get("receivers", [])]
|
||||
if flags & self.GET_PROVIDERS:
|
||||
pi.providers = [self._make_provider_info(p) for p in raw.get("providers", [])]
|
||||
if flags & self.GET_PERMISSIONS:
|
||||
pi.permissions = [PermissionInfo(p) for p in raw.get("permissions", [])]
|
||||
|
||||
pi.requestedPermissions = raw.get("requestedPermissions", [])
|
||||
return pi
|
||||
|
||||
def getApplicationInfo(self, package_name: str, flags: int) -> Optional[ApplicationInfo]:
|
||||
if package_name not in self._packages:
|
||||
return None
|
||||
return self._make_app_info(self._packages[package_name])
|
||||
|
||||
def getActivityInfo(self, component: Any, flags: int) -> Optional[ActivityInfo]:
|
||||
cn = component
|
||||
pkg = getattr(cn, "getPackageName", lambda: "")()
|
||||
cls = getattr(cn, "getClassName", lambda: "")()
|
||||
if pkg in self._packages:
|
||||
for a in self._packages[pkg].get("activities", []):
|
||||
if a.get("name") == cls:
|
||||
return ActivityInfo(a)
|
||||
return ActivityInfo({"name": cls, "packageName": pkg})
|
||||
|
||||
def getServiceInfo(self, component: Any, flags: int) -> Optional[ServiceInfo]:
|
||||
cn = component
|
||||
pkg = getattr(cn, "getPackageName", lambda: "")()
|
||||
cls = getattr(cn, "getClassName", lambda: "")()
|
||||
if pkg in self._packages:
|
||||
for s in self._packages[pkg].get("services", []):
|
||||
if s.get("name") == cls:
|
||||
return ServiceInfo(s)
|
||||
return ServiceInfo({"name": cls, "packageName": pkg})
|
||||
|
||||
def getInstalledPackages(self, flags: int) -> List[PackageInfo]:
|
||||
result = []
|
||||
for pkg_name in self._packages:
|
||||
pi = self.getPackageInfo(pkg_name, flags)
|
||||
if pi:
|
||||
result.append(pi)
|
||||
return result
|
||||
|
||||
def getInstalledApplications(self, flags: int) -> List[ApplicationInfo]:
|
||||
result = []
|
||||
for pkg_name in self._packages:
|
||||
ai = self.getApplicationInfo(pkg_name, flags)
|
||||
if ai:
|
||||
result.append(ai)
|
||||
return result
|
||||
|
||||
def queryIntentActivities(self, intent: Any, flags: int) -> List[ResolveInfo]:
|
||||
"""查询匹配 Intent 的 Activity 列表"""
|
||||
results = []
|
||||
action = getattr(intent, "getAction", lambda: None)()
|
||||
for pkg_name, raw in self._packages.items():
|
||||
for a in raw.get("activities", []):
|
||||
ri = ResolveInfo()
|
||||
ri.activityInfo = ActivityInfo(a)
|
||||
ri.priority = 0
|
||||
ri.match = 0
|
||||
results.append(ri)
|
||||
return results
|
||||
|
||||
def queryBroadcastReceivers(self, intent: Any, flags: int) -> List[ResolveInfo]:
|
||||
results = []
|
||||
for pkg_name, raw in self._packages.items():
|
||||
for r in raw.get("receivers", []):
|
||||
ri = ResolveInfo()
|
||||
ri.activityInfo = ActivityInfo(r)
|
||||
results.append(ri)
|
||||
return results
|
||||
|
||||
def queryIntentServices(self, intent: Any, flags: int) -> List[ResolveInfo]:
|
||||
results = []
|
||||
for pkg_name, raw in self._packages.items():
|
||||
for s in raw.get("services", []):
|
||||
ri = ResolveInfo()
|
||||
ri.serviceInfo = ServiceInfo(s)
|
||||
results.append(ri)
|
||||
return results
|
||||
|
||||
def queryIntentContentProviders(self, intent: Any, flags: int) -> List[ResolveInfo]:
|
||||
results = []
|
||||
for pkg_name, raw in self._packages.items():
|
||||
for p in raw.get("providers", []):
|
||||
ri = ResolveInfo()
|
||||
ri.providerInfo = ProviderInfo(p)
|
||||
results.append(ri)
|
||||
return results
|
||||
|
||||
def resolveActivity(self, intent: Any, flags: int) -> Optional[ResolveInfo]:
|
||||
activities = self.queryIntentActivities(intent, flags)
|
||||
return activities[0] if activities else None
|
||||
|
||||
def resolveService(self, intent: Any, flags: int) -> Optional[ResolveInfo]:
|
||||
services = self.queryIntentServices(intent, flags)
|
||||
return services[0] if services else None
|
||||
|
||||
def checkPermission(self, permission: str, package_name: str) -> int:
|
||||
if package_name in self._packages:
|
||||
perms = self._packages[package_name].get("requestedPermissions", [])
|
||||
if permission in perms:
|
||||
return self.PERMISSION_GRANTED
|
||||
return self.PERMISSION_DENIED if permission else self.PERMISSION_GRANTED
|
||||
|
||||
def getPermissionInfo(self, name: str, flags: int) -> Optional[PermissionInfo]:
|
||||
return PermissionInfo({"name": name, "packageName": "android"})
|
||||
|
||||
def getDefaultActivityIcon(self) -> Any:
|
||||
return None
|
||||
|
||||
def getApplicationLabel(self, info: ApplicationInfo) -> str:
|
||||
return info.name.split(".")[-1] if info.name else ""
|
||||
|
||||
def getApplicationIcon(self, package_name: str) -> Any:
|
||||
return None
|
||||
|
||||
def hasSystemFeature(self, feature: str) -> bool:
|
||||
for pkg_name, raw in self._packages.items():
|
||||
features = raw.get("features", {})
|
||||
if feature in features:
|
||||
return features[feature]
|
||||
return False
|
||||
|
||||
def getLaunchIntentForPackage(self, package_name: str) -> Any:
|
||||
from .app import Intent
|
||||
if package_name in self._packages:
|
||||
intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.setPackage(package_name)
|
||||
return intent
|
||||
return None
|
||||
|
||||
def getLeanbackLaunchIntentForPackage(self, package_name: str) -> Any:
|
||||
return self.getLaunchIntentForPackage(package_name)
|
||||
|
||||
def getPackageSizeInfo(self, package_name: str, observer: Any):
|
||||
pass
|
||||
|
||||
def getPackageGids(self, package_name: str) -> Optional[List[int]]:
|
||||
return []
|
||||
|
||||
def getPackageUid(self, package_name: str, flags: int) -> int:
|
||||
return 10001
|
||||
|
||||
def getNameForUid(self, uid: int) -> Optional[str]:
|
||||
return _package_name
|
||||
|
||||
def getPackagesForUid(self, uid: int) -> Optional[List[str]]:
|
||||
return [_package_name]
|
||||
|
||||
def getResourcesForActivity(self, component: Any) -> Any:
|
||||
return None
|
||||
|
||||
def getResourcesForApplication(self, app_info: ApplicationInfo) -> Any:
|
||||
return None
|
||||
|
||||
def isSafeMode(self) -> bool:
|
||||
return False
|
||||
|
||||
def addPackageToPreferred(self, package_name: str):
|
||||
pass
|
||||
|
||||
def removePackageFromPreferred(self, package_name: str):
|
||||
pass
|
||||
|
||||
def getPreferredPackages(self, flags: int) -> List[PackageInfo]:
|
||||
return []
|
||||
|
||||
def setApplicationEnabledSetting(self, package_name: str, new_state: int, flags: int):
|
||||
pass
|
||||
|
||||
def getApplicationEnabledSetting(self, package_name: str) -> int:
|
||||
return 0
|
||||
|
||||
def getComponentEnabledSetting(self, component_name: Any) -> int:
|
||||
return 1
|
||||
|
||||
def setComponentEnabledSetting(self, component_name: Any, new_state: int, flags: int):
|
||||
pass
|
||||
|
||||
def clearInstantAppCookie(self):
|
||||
pass
|
||||
|
||||
def getInstantAppCookie(self) -> bytes:
|
||||
return b""
|
||||
|
||||
def getInstantAppCookieMaxSize(self) -> int:
|
||||
return 4096
|
||||
|
||||
def getMimeGroup(self, mime_group: str) -> List[str]:
|
||||
return []
|
||||
|
||||
def isInstantApp(self) -> bool:
|
||||
return False
|
||||
|
||||
def isInstantAppPackageName(self, package_name: str) -> bool:
|
||||
return False
|
||||
|
||||
def getSyntheticAppDetailsActivityEnabled(self, package_name: str) -> int:
|
||||
return 1
|
||||
|
||||
def setSyntheticAppDetailsActivityEnabled(self, package_name: str, enabled: int):
|
||||
pass
|
||||
|
||||
def canRequestPackageInstalls(self) -> bool:
|
||||
return True
|
||||
|
||||
def currentToCanonicalPackageNames(self, names: List[str]) -> List[str]:
|
||||
return list(names)
|
||||
|
||||
def canonicalToCurrentPackageNames(self, names: List[str]) -> List[str]:
|
||||
return list(names)
|
||||
|
||||
def getInstallerPackageName(self, package_name: str) -> Optional[str]:
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PackageManager(packages={len(self._packages)})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"PackageManager",
|
||||
"PackageInfo",
|
||||
"ApplicationInfo",
|
||||
"ActivityInfo",
|
||||
"ServiceInfo",
|
||||
"ProviderInfo",
|
||||
"PermissionInfo",
|
||||
"ResolveInfo",
|
||||
"PackageItemInfo",
|
||||
"ComponentInfo",
|
||||
"Signature",
|
||||
"PackageStats",
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,965 @@
|
||||
"""
|
||||
Android SQLite 组件 — SQLiteDatabase, SQLiteOpenHelper, SQLiteStatement 等
|
||||
使用 Python sqlite3 模块作为后端
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import sqlite3 as _sqlite3
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
from .database import (
|
||||
Cursor, AbstractCursor, ContentObserver, DataSetObserver, SQLException as BaseSQLException,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteException hierarchy
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SQLiteException(BaseSQLException):
|
||||
"""SQLiteException — SQLite 异常基类"""
|
||||
|
||||
def __init__(self, message: str = ""):
|
||||
super().__init__(message)
|
||||
self._error_message = message
|
||||
|
||||
|
||||
class SQLiteAbortException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteAccessPermException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteBindOrColumnIndexOutOfRangeException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteBlobTooBigException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteCantOpenDatabaseException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteConstraintException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteDatabaseCorruptException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteDatabaseLockedException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteDatatypeMismatchException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteDiskIOException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteDoneException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteException_General(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteFullException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteMisuseException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteOutOfMemoryException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteReadOnlyDatabaseException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteTableLockedException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteUnsupportedOperationException(SQLiteException):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteClosable
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SQLiteClosable(Stub):
|
||||
"""SQLiteClosable — 可关闭资源基类"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._closed = False
|
||||
self._reference_count = 1
|
||||
|
||||
def acquireReference(self):
|
||||
if self._closed:
|
||||
raise IllegalStateException("already closed")
|
||||
self._reference_count += 1
|
||||
|
||||
def releaseReference(self):
|
||||
self._reference_count -= 1
|
||||
if self._reference_count <= 0:
|
||||
self.onAllReferencesReleased()
|
||||
|
||||
def releaseReferenceFromContainer(self):
|
||||
self.releaseReference()
|
||||
|
||||
def onAllReferencesReleased(self):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
def isClosed(self) -> bool:
|
||||
return self._closed
|
||||
|
||||
|
||||
class IllegalStateException(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteProgram
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SQLiteProgram(SQLiteClosable):
|
||||
"""SQLiteProgram — SQL 程序基类"""
|
||||
|
||||
def __init__(self, db: "SQLiteDatabase", sql: str):
|
||||
super().__init__()
|
||||
self._db = db
|
||||
self._sql = sql
|
||||
self._bindings: Dict[int, Any] = {}
|
||||
self._next_bind_index = 1
|
||||
|
||||
def getDatabase(self) -> "SQLiteDatabase":
|
||||
return self._db
|
||||
|
||||
def getSql(self) -> str:
|
||||
return self._sql
|
||||
|
||||
def bindNull(self, index: int):
|
||||
self._bindings[index] = None
|
||||
|
||||
def bindLong(self, index: int, value: int):
|
||||
self._bindings[index] = value
|
||||
|
||||
def bindDouble(self, index: int, value: float):
|
||||
self._bindings[index] = value
|
||||
|
||||
def bindString(self, index: int, value: str):
|
||||
self._bindings[index] = value
|
||||
|
||||
def bindBlob(self, index: int, value: bytes):
|
||||
self._bindings[index] = value
|
||||
|
||||
def clearBindings(self):
|
||||
self._bindings.clear()
|
||||
|
||||
def _bind_parameters(self, cursor: _sqlite3.Cursor):
|
||||
for idx, val in sorted(self._bindings.items()):
|
||||
cursor.execute(f"PRAGMA dummy={idx}")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteStatement
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SQLiteStatement(SQLiteProgram):
|
||||
"""SQLiteStatement — 预编译语句"""
|
||||
|
||||
def __init__(self, db: "SQLiteDatabase", sql: str):
|
||||
super().__init__(db, sql)
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
conn = self._db._acquire_connection()
|
||||
cursor = conn.execute(self._sql, list(self._bindings.values()))
|
||||
conn.commit()
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), self._sql)
|
||||
|
||||
def executeUpdateDelete(self) -> int:
|
||||
try:
|
||||
conn = self._db._acquire_connection()
|
||||
cursor = conn.execute(self._sql, list(self._bindings.values()))
|
||||
conn.commit()
|
||||
return cursor.rowcount
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), self._sql)
|
||||
|
||||
def executeInsert(self) -> int:
|
||||
try:
|
||||
conn = self._db._acquire_connection()
|
||||
cursor = conn.execute(self._sql, list(self._bindings.values()))
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), self._sql)
|
||||
|
||||
def simpleQueryForLong(self) -> int:
|
||||
try:
|
||||
conn = self._db._acquire_connection()
|
||||
cursor = conn.execute(self._sql, list(self._bindings.values()))
|
||||
row = cursor.fetchone()
|
||||
conn.commit()
|
||||
if row:
|
||||
return int(row[0]) if row[0] is not None else 0
|
||||
raise SQLiteDoneException("no results")
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), self._sql)
|
||||
|
||||
def simpleQueryForString(self) -> Optional[str]:
|
||||
try:
|
||||
conn = self._db._acquire_connection()
|
||||
cursor = conn.execute(self._sql, list(self._bindings.values()))
|
||||
row = cursor.fetchone()
|
||||
conn.commit()
|
||||
if row:
|
||||
return str(row[0]) if row[0] is not None else None
|
||||
raise SQLiteDoneException("no results")
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), self._sql)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteQuery
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SQLiteQuery(SQLiteProgram):
|
||||
"""SQLiteQuery — 查询对象"""
|
||||
|
||||
def __init__(self, db: "SQLiteDatabase", sql: str):
|
||||
super().__init__(db, sql)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SQLiteQuery: {self._sql}"
|
||||
|
||||
def sql(self) -> str:
|
||||
return self._sql
|
||||
|
||||
def bind(self, index: int, value: Any):
|
||||
self._bindings[index] = value
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteCursor
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SQLiteCursor(AbstractCursor):
|
||||
"""SQLiteCursor — SQLite 游标实现"""
|
||||
|
||||
def __init__(self, db: "SQLiteDatabase", query: SQLiteQuery):
|
||||
super().__init__()
|
||||
self._db = db
|
||||
self._query = query
|
||||
self._column_names: List[str] = []
|
||||
self._rows: List[List[Any]] = []
|
||||
self._current_row: Optional[List[Any]] = None
|
||||
self._loaded = False
|
||||
self._closed = False
|
||||
|
||||
def _load(self):
|
||||
if self._loaded or self._closed:
|
||||
return
|
||||
self._loaded = True
|
||||
try:
|
||||
conn = self._db._acquire_connection()
|
||||
cursor = conn.execute(self._query.getSql(), list(self._query._bindings.values()))
|
||||
self._column_names = [desc[0] for desc in cursor.description] if cursor.description else []
|
||||
self._rows = cursor.fetchall()
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), self._query.getSql())
|
||||
|
||||
def getCount(self) -> int:
|
||||
self._load()
|
||||
return len(self._rows)
|
||||
|
||||
def getColumnNames(self) -> List[str]:
|
||||
self._load()
|
||||
return list(self._column_names)
|
||||
|
||||
def getColumnIndex(self, column_name: str) -> int:
|
||||
self._load()
|
||||
try:
|
||||
return self._column_names.index(column_name)
|
||||
except ValueError:
|
||||
return -1
|
||||
|
||||
def moveToPosition(self, position: int) -> bool:
|
||||
self._load()
|
||||
if position < -1 or position >= len(self._rows):
|
||||
return False
|
||||
self._pos = position
|
||||
if 0 <= position < len(self._rows):
|
||||
self._current_row = self._rows[position]
|
||||
else:
|
||||
self._current_row = None
|
||||
return True
|
||||
|
||||
def getString(self, column_index: int) -> Optional[str]:
|
||||
if self._current_row is None or column_index < 0 or column_index >= len(self._current_row):
|
||||
return None
|
||||
v = self._current_row[column_index]
|
||||
return str(v) if v is not None else None
|
||||
|
||||
def getInt(self, column_index: int) -> int:
|
||||
if self._current_row is None or column_index < 0 or column_index >= len(self._current_row):
|
||||
return 0
|
||||
v = self._current_row[column_index]
|
||||
if v is None:
|
||||
return 0
|
||||
try:
|
||||
return int(v)
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
def getLong(self, column_index: int) -> int:
|
||||
return self.getInt(column_index)
|
||||
|
||||
def getFloat(self, column_index: int) -> float:
|
||||
if self._current_row is None or column_index < 0 or column_index >= len(self._current_row):
|
||||
return 0.0
|
||||
v = self._current_row[column_index]
|
||||
if v is None:
|
||||
return 0.0
|
||||
try:
|
||||
return float(v)
|
||||
except (ValueError, TypeError):
|
||||
return 0.0
|
||||
|
||||
def getDouble(self, column_index: int) -> float:
|
||||
return self.getFloat(column_index)
|
||||
|
||||
def getShort(self, column_index: int) -> int:
|
||||
return self.getInt(column_index)
|
||||
|
||||
def getBlob(self, column_index: int) -> Optional[bytes]:
|
||||
if self._current_row is None or column_index < 0 or column_index >= len(self._current_row):
|
||||
return None
|
||||
v = self._current_row[column_index]
|
||||
if isinstance(v, bytes):
|
||||
return v
|
||||
if v is not None:
|
||||
return bytes(str(v), "utf-8")
|
||||
return None
|
||||
|
||||
def getType(self, column_index: int) -> int:
|
||||
self._load()
|
||||
if self._current_row is None or column_index < 0 or column_index >= len(self._current_row):
|
||||
return Cursor.FIELD_TYPE_NULL
|
||||
v = self._current_row[column_index]
|
||||
if v is None:
|
||||
return Cursor.FIELD_TYPE_NULL
|
||||
if isinstance(v, int):
|
||||
return Cursor.FIELD_TYPE_INTEGER
|
||||
if isinstance(v, float):
|
||||
return Cursor.FIELD_TYPE_FLOAT
|
||||
if isinstance(v, bytes):
|
||||
return Cursor.FIELD_TYPE_BLOB
|
||||
return Cursor.FIELD_TYPE_STRING
|
||||
|
||||
def isNull(self, column_index: int) -> bool:
|
||||
return self.getType(column_index) == Cursor.FIELD_TYPE_NULL
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
self._rows.clear()
|
||||
self._current_row = None
|
||||
|
||||
def requery(self) -> bool:
|
||||
self._loaded = False
|
||||
self._rows.clear()
|
||||
self._current_row = None
|
||||
self._pos = -1
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteQueryBuilder
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SQLiteQueryBuilder(Stub):
|
||||
"""SQLiteQueryBuilder — 查询构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._tables: str = ""
|
||||
self._distinct: bool = False
|
||||
self._where: Optional[str] = None
|
||||
|
||||
def setTables(self, tables: str):
|
||||
self._tables = tables
|
||||
|
||||
def getTables(self) -> str:
|
||||
return self._tables
|
||||
|
||||
def appendWhere(self, where: str):
|
||||
if self._where:
|
||||
self._where = f"({self._where}) AND ({where})"
|
||||
else:
|
||||
self._where = where
|
||||
|
||||
def appendWhereStandalone(self, where: str):
|
||||
self._where = where
|
||||
|
||||
def setDistinct(self, distinct: bool):
|
||||
self._distinct = distinct
|
||||
|
||||
def buildQueryString(self, distinct: bool, tables: str,
|
||||
columns: Optional[List[str]], where: Optional[str],
|
||||
group_by: Optional[str], having: Optional[str],
|
||||
order_by: Optional[str], limit: Optional[str]) -> str:
|
||||
sql = "SELECT "
|
||||
if distinct:
|
||||
sql += "DISTINCT "
|
||||
if columns and len(columns) > 0:
|
||||
sql += ", ".join(columns)
|
||||
else:
|
||||
sql += "*"
|
||||
sql += f" FROM {tables}"
|
||||
if where:
|
||||
sql += f" WHERE ({where})"
|
||||
if group_by:
|
||||
sql += f" GROUP BY {group_by}"
|
||||
if having:
|
||||
sql += f" HAVING {having}"
|
||||
if order_by:
|
||||
sql += f" ORDER BY {order_by}"
|
||||
if limit:
|
||||
sql += f" LIMIT {limit}"
|
||||
return sql
|
||||
|
||||
def buildQuery(self, columns: Optional[List[str]], where: Optional[str],
|
||||
group_by: Optional[str], having: Optional[str],
|
||||
order_by: Optional[str], limit: Optional[str]) -> str:
|
||||
return self.buildQueryString(self._distinct, self._tables,
|
||||
columns, where or self._where,
|
||||
group_by, having, order_by, limit)
|
||||
|
||||
def query(self, db: "SQLiteDatabase", columns: Optional[List[str]],
|
||||
where: Optional[str], group_by: Optional[str],
|
||||
having: Optional[str], order_by: Optional[str],
|
||||
limit: Optional[str]) -> SQLiteCursor:
|
||||
sql = self.buildQuery(columns, where, group_by, having, order_by, limit)
|
||||
return db.rawQuery(sql, None)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteDatabase
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def _map_sqlite_error(msg: str, sql: str = "") -> SQLiteException:
|
||||
msg_lower = msg.lower()
|
||||
if "constraint" in msg_lower:
|
||||
return SQLiteConstraintException(msg)
|
||||
if "locked" in msg_lower:
|
||||
return SQLiteDatabaseLockedException(msg)
|
||||
if "corrupt" in msg_lower:
|
||||
return SQLiteDatabaseCorruptException(msg)
|
||||
if "readonly" in msg_lower or "read only" in msg_lower:
|
||||
return SQLiteReadOnlyDatabaseException(msg)
|
||||
if "disk" in msg_lower or "full" in msg_lower:
|
||||
return SQLiteDiskIOException(msg) if "disk" in msg_lower else SQLiteFullException(msg)
|
||||
if "abort" in msg_lower:
|
||||
return SQLiteAbortException(msg)
|
||||
if "permission" in msg_lower:
|
||||
return SQLiteAccessPermException(msg)
|
||||
if "out of memory" in msg_lower:
|
||||
return SQLiteOutOfMemoryException(msg)
|
||||
if "not a database" in msg_lower or "cantopen" in msg_lower:
|
||||
return SQLiteCantOpenDatabaseException(msg)
|
||||
if "misuse" in msg_lower:
|
||||
return SQLiteMisuseException(msg)
|
||||
return SQLiteException(msg)
|
||||
|
||||
|
||||
class SQLiteDatabase(SQLiteClosable):
|
||||
"""SQLiteDatabase — 数据库操作类"""
|
||||
|
||||
CONFLICT_ROLLBACK = 1
|
||||
CONFLICT_ABORT = 2
|
||||
CONFLICT_FAIL = 3
|
||||
CONFLICT_IGNORE = 4
|
||||
CONFLICT_REPLACE = 5
|
||||
|
||||
OPEN_READWRITE = 0
|
||||
OPEN_READONLY = 1
|
||||
CREATE_IF_NECESSARY = 268435456
|
||||
NO_LOCALIZED_COLLATORS = 16
|
||||
|
||||
_db_registry: Dict[str, "SQLiteDatabase"] = {}
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __init__(self, path: str, flags: int = 0):
|
||||
super().__init__()
|
||||
self._path = path
|
||||
self._flags = flags
|
||||
self._conn: Optional[_sqlite3.Connection] = None
|
||||
self._in_transaction = False
|
||||
self._transaction_successful = False
|
||||
self._version: int = 0
|
||||
self._max_size: int = 0
|
||||
self._page_size: int = 4096
|
||||
self._enable_wal = False
|
||||
self._attached_dbs: List[Dict[str, Any]] = []
|
||||
|
||||
@staticmethod
|
||||
def openDatabase(path: str, cursor_factory: Any = None,
|
||||
flags: int = 0, error_handler: Any = None) -> "SQLiteDatabase":
|
||||
db = SQLiteDatabase(path, flags)
|
||||
try:
|
||||
db._conn = _sqlite3.connect(path)
|
||||
db._conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn_cursor = db._conn.execute("PRAGMA schema_version")
|
||||
row = conn_cursor.fetchone()
|
||||
if row:
|
||||
db._version = row[0]
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e))
|
||||
SQLiteDatabase._db_registry[path] = db
|
||||
return db
|
||||
|
||||
@staticmethod
|
||||
def openOrCreateDatabase(path: str, cursor_factory: Any = None,
|
||||
error_handler: Any = None) -> "SQLiteDatabase":
|
||||
return SQLiteDatabase.openDatabase(path, cursor_factory,
|
||||
SQLiteDatabase.CREATE_IF_NECESSARY, error_handler)
|
||||
|
||||
@staticmethod
|
||||
def create(cursor_factory: Any = None, error_handler: Any = None) -> "SQLiteDatabase":
|
||||
import tempfile
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
||||
tmp.close()
|
||||
return SQLiteDatabase.openOrCreateDatabase(tmp.name, cursor_factory, error_handler)
|
||||
|
||||
@staticmethod
|
||||
def deleteDatabase(path: str) -> bool:
|
||||
try:
|
||||
if path in SQLiteDatabase._db_registry:
|
||||
db = SQLiteDatabase._db_registry[path]
|
||||
db.close()
|
||||
del SQLiteDatabase._db_registry[path]
|
||||
os.remove(path)
|
||||
wal_path = path + "-wal"
|
||||
if os.path.exists(wal_path):
|
||||
os.remove(wal_path)
|
||||
shm_path = path + "-shm"
|
||||
if os.path.exists(shm_path):
|
||||
os.remove(shm_path)
|
||||
return True
|
||||
except (OSError, Exception):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def databaseList() -> List[str]:
|
||||
return list(SQLiteDatabase._db_registry.keys())
|
||||
|
||||
def _acquire_connection(self) -> _sqlite3.Connection:
|
||||
if self._conn is None:
|
||||
raise SQLiteCantOpenDatabaseException("Database not open")
|
||||
return self._conn
|
||||
|
||||
def execSQL(self, sql: str, bind_args: Optional[List[Any]] = None):
|
||||
self._ensure_open()
|
||||
try:
|
||||
conn = self._acquire_connection()
|
||||
args = bind_args or []
|
||||
conn.execute(sql, args)
|
||||
if not self._in_transaction:
|
||||
conn.commit()
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), sql)
|
||||
|
||||
def rawQuery(self, sql: str, selection_args: Optional[List[Any]] = None) -> SQLiteCursor:
|
||||
self._ensure_open()
|
||||
query = SQLiteQuery(self, sql)
|
||||
if selection_args:
|
||||
for i, arg in enumerate(selection_args):
|
||||
query.bind(i + 1, arg)
|
||||
cursor = SQLiteCursor(self, query)
|
||||
return cursor
|
||||
|
||||
def insert(self, table: str, null_column_hack: Optional[str],
|
||||
values: Any) -> int:
|
||||
self._ensure_open()
|
||||
if values is None:
|
||||
return -1
|
||||
cols = []
|
||||
vals = []
|
||||
if hasattr(values, "valueSet"):
|
||||
items = list(values.valueSet())
|
||||
elif hasattr(values, "key_set"):
|
||||
items = [(k, getattr(values, f"get{k[0].upper()}{k[1:]}", lambda: None)())
|
||||
for k in values.key_set()]
|
||||
elif isinstance(values, dict):
|
||||
items = list(values.items())
|
||||
else:
|
||||
return -1
|
||||
for k, v in items:
|
||||
cols.append(str(k))
|
||||
vals.append(v)
|
||||
if len(cols) == 0:
|
||||
cols = ["_id"]
|
||||
vals = [None]
|
||||
col_str = ", ".join(cols)
|
||||
placeholders = ", ".join(["?"] * len(vals))
|
||||
sql = f"INSERT INTO {table} ({col_str}) VALUES ({placeholders})"
|
||||
try:
|
||||
conn = self._acquire_connection()
|
||||
cursor = conn.execute(sql, vals)
|
||||
if not self._in_transaction:
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), sql)
|
||||
|
||||
def update(self, table: str, values: Any,
|
||||
where_clause: Optional[str] = None,
|
||||
where_args: Optional[List[Any]] = None) -> int:
|
||||
self._ensure_open()
|
||||
if values is None:
|
||||
return 0
|
||||
set_pairs = []
|
||||
vals = []
|
||||
if isinstance(values, dict):
|
||||
items = list(values.items())
|
||||
elif hasattr(values, "valueSet"):
|
||||
items = list(values.valueSet())
|
||||
else:
|
||||
return 0
|
||||
for k, v in items:
|
||||
set_pairs.append(f"{k} = ?")
|
||||
vals.append(v)
|
||||
set_str = ", ".join(set_pairs)
|
||||
sql = f"UPDATE {table} SET {set_str}"
|
||||
if where_clause:
|
||||
sql += f" WHERE {where_clause}"
|
||||
if where_args:
|
||||
vals.extend(where_args)
|
||||
try:
|
||||
conn = self._acquire_connection()
|
||||
cursor = conn.execute(sql, vals)
|
||||
if not self._in_transaction:
|
||||
conn.commit()
|
||||
return cursor.rowcount
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), sql)
|
||||
|
||||
def delete(self, table: str, where_clause: Optional[str] = None,
|
||||
where_args: Optional[List[Any]] = None) -> int:
|
||||
self._ensure_open()
|
||||
sql = f"DELETE FROM {table}"
|
||||
vals: List[Any] = []
|
||||
if where_clause:
|
||||
sql += f" WHERE {where_clause}"
|
||||
if where_args:
|
||||
vals = list(where_args)
|
||||
try:
|
||||
conn = self._acquire_connection()
|
||||
cursor = conn.execute(sql, vals)
|
||||
if not self._in_transaction:
|
||||
conn.commit()
|
||||
return cursor.rowcount
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e), sql)
|
||||
|
||||
def query(self, table: str, columns: Optional[List[str]] = None,
|
||||
selection: Optional[str] = None,
|
||||
selection_args: Optional[List[Any]] = None,
|
||||
group_by: Optional[str] = None, having: Optional[str] = None,
|
||||
order_by: Optional[str] = None,
|
||||
limit: Optional[str] = None) -> SQLiteCursor:
|
||||
col_str = ", ".join(columns) if columns else "*"
|
||||
sql = f"SELECT {col_str} FROM {table}"
|
||||
if selection:
|
||||
sql += f" WHERE {selection}"
|
||||
if group_by:
|
||||
sql += f" GROUP BY {group_by}"
|
||||
if having:
|
||||
sql += f" HAVING {having}"
|
||||
if order_by:
|
||||
sql += f" ORDER BY {order_by}"
|
||||
if limit:
|
||||
sql += f" LIMIT {limit}"
|
||||
return self.rawQuery(sql, selection_args)
|
||||
|
||||
def compileStatement(self, sql: str) -> SQLiteStatement:
|
||||
return SQLiteStatement(self, sql)
|
||||
|
||||
def beginTransaction(self):
|
||||
self._ensure_open()
|
||||
try:
|
||||
conn = self._acquire_connection()
|
||||
conn.execute("BEGIN TRANSACTION")
|
||||
self._in_transaction = True
|
||||
self._transaction_successful = False
|
||||
except _sqlite3.DatabaseError as e:
|
||||
raise _map_sqlite_error(str(e))
|
||||
|
||||
def endTransaction(self):
|
||||
if self._in_transaction and self._conn:
|
||||
try:
|
||||
if not self._transaction_successful:
|
||||
self._conn.rollback()
|
||||
else:
|
||||
self._conn.commit()
|
||||
except _sqlite3.DatabaseError:
|
||||
pass
|
||||
self._in_transaction = False
|
||||
|
||||
def setTransactionSuccessful(self):
|
||||
if not self._in_transaction:
|
||||
raise IllegalStateException("Not in transaction")
|
||||
self._transaction_successful = True
|
||||
|
||||
def inTransaction(self) -> bool:
|
||||
return self._in_transaction
|
||||
|
||||
def isOpen(self) -> bool:
|
||||
return self._conn is not None
|
||||
|
||||
def getPath(self) -> str:
|
||||
return self._path
|
||||
|
||||
def getVersion(self) -> int:
|
||||
return self._version
|
||||
|
||||
def setVersion(self, version: int):
|
||||
self._version = version
|
||||
if self._conn:
|
||||
try:
|
||||
self._conn.execute(f"PRAGMA schema_version = {version}")
|
||||
except _sqlite3.DatabaseError:
|
||||
pass
|
||||
|
||||
def onCreate(self, db: "SQLiteDatabase"):
|
||||
pass
|
||||
|
||||
def onConfigure(self, db: "SQLiteDatabase"):
|
||||
pass
|
||||
|
||||
def enableWriteAheadLogging(self) -> bool:
|
||||
self._ensure_open()
|
||||
try:
|
||||
self._conn.execute("PRAGMA journal_mode=WAL")
|
||||
self._enable_wal = True
|
||||
return True
|
||||
except _sqlite3.DatabaseError:
|
||||
return False
|
||||
|
||||
def disableWriteAheadLogging(self):
|
||||
try:
|
||||
self._conn.execute("PRAGMA journal_mode=DELETE")
|
||||
self._enable_wal = False
|
||||
except _sqlite3.DatabaseError:
|
||||
pass
|
||||
|
||||
def isWriteAheadLoggingEnabled(self) -> bool:
|
||||
return self._enable_wal
|
||||
|
||||
def getMaximumSize(self) -> int:
|
||||
return self._max_size
|
||||
|
||||
def getPageSize(self) -> int:
|
||||
return self._page_size
|
||||
|
||||
def setMaximumSize(self, size: int) -> int:
|
||||
self._max_size = size
|
||||
return size
|
||||
|
||||
def setPageSize(self, size: int):
|
||||
self._page_size = size
|
||||
if self._conn:
|
||||
try:
|
||||
self._conn.execute(f"PRAGMA page_size = {size}")
|
||||
except _sqlite3.DatabaseError:
|
||||
pass
|
||||
|
||||
def getPageCount(self) -> int:
|
||||
if self._conn:
|
||||
try:
|
||||
cursor = self._conn.execute("PRAGMA page_count")
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row[0]
|
||||
except _sqlite3.DatabaseError:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def yieldIfContendedSafely(self, sleep_time: int = 0) -> bool:
|
||||
return True
|
||||
|
||||
def needUpgrade(self, new_version: int) -> bool:
|
||||
return new_version > self._version
|
||||
|
||||
def getAttachedDbs(self) -> Optional[List[Dict[str, Any]]]:
|
||||
return self._attached_dbs
|
||||
|
||||
def isReadOnly(self) -> bool:
|
||||
return bool(self._flags & self.OPEN_READONLY)
|
||||
|
||||
def close(self):
|
||||
if self._conn:
|
||||
try:
|
||||
self._conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._conn = None
|
||||
super().close()
|
||||
|
||||
def _ensure_open(self):
|
||||
if self._conn is None:
|
||||
raise SQLiteCantOpenDatabaseException("Database not open")
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SQLiteDatabase(path={self._path})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SQLiteOpenHelper
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SQLiteOpenHelper(Stub):
|
||||
"""SQLiteOpenHelper — 数据库打开辅助类"""
|
||||
|
||||
def __init__(self, context: Any, name: Optional[str],
|
||||
factory: Any, version: int,
|
||||
error_handler: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._name = name
|
||||
self._factory = factory
|
||||
self._version = version
|
||||
self._error_handler = error_handler
|
||||
self._readable_db: Optional[SQLiteDatabase] = None
|
||||
self._writable_db: Optional[SQLiteDatabase] = None
|
||||
self._database_path: Optional[str] = None
|
||||
self._is_initialized = False
|
||||
|
||||
def _get_database_path(self) -> str:
|
||||
if self._database_path:
|
||||
return self._database_path
|
||||
db_dir = os.path.join(_app_data_dir, "databases")
|
||||
os.makedirs(db_dir, exist_ok=True)
|
||||
if self._name:
|
||||
self._database_path = os.path.join(db_dir, self._name)
|
||||
else:
|
||||
import tempfile
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
||||
tmp.close()
|
||||
self._database_path = tmp.name
|
||||
return self._database_path
|
||||
|
||||
def getWritableDatabase(self) -> SQLiteDatabase:
|
||||
if self._writable_db and self._writable_db.isOpen():
|
||||
return self._writable_db
|
||||
path = self._get_database_path()
|
||||
db = SQLiteDatabase.openOrCreateDatabase(path, self._factory, self._error_handler)
|
||||
if not self._is_initialized:
|
||||
self._is_initialized = True
|
||||
current_version = db.getVersion()
|
||||
if current_version == 0:
|
||||
self.onCreate(db)
|
||||
db.setVersion(self._version)
|
||||
elif current_version < self._version:
|
||||
self.onUpgrade(db, current_version, self._version)
|
||||
elif current_version > self._version:
|
||||
self.onDowngrade(db, current_version, self._version)
|
||||
self.onOpen(db)
|
||||
self._writable_db = db
|
||||
self._readable_db = db
|
||||
return db
|
||||
|
||||
def getReadableDatabase(self) -> SQLiteDatabase:
|
||||
return self.getWritableDatabase()
|
||||
|
||||
def onCreate(self, db: SQLiteDatabase):
|
||||
pass
|
||||
|
||||
def onUpgrade(self, db: SQLiteDatabase, old_version: int, new_version: int):
|
||||
pass
|
||||
|
||||
def onDowngrade(self, db: SQLiteDatabase, old_version: int, new_version: int):
|
||||
pass
|
||||
|
||||
def onOpen(self, db: SQLiteDatabase):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
if self._writable_db:
|
||||
self._writable_db.close()
|
||||
self._writable_db = None
|
||||
if self._readable_db:
|
||||
self._readable_db = None
|
||||
|
||||
def getDatabaseName(self) -> Optional[str]:
|
||||
return self._name
|
||||
|
||||
def getReadableDB(self) -> Optional[SQLiteDatabase]:
|
||||
return self._readable_db
|
||||
|
||||
def getWriteableDB(self) -> Optional[SQLiteDatabase]:
|
||||
return self._writable_db
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SQLiteOpenHelper(name={self._name}, version={self._version})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"SQLiteDatabase",
|
||||
"SQLiteOpenHelper",
|
||||
"SQLiteStatement",
|
||||
"SQLiteCursor",
|
||||
"SQLiteQuery",
|
||||
"SQLiteQueryBuilder",
|
||||
"SQLiteClosable",
|
||||
"SQLiteProgram",
|
||||
"SQLiteAbortException",
|
||||
"SQLiteAccessPermException",
|
||||
"SQLiteBindOrColumnIndexOutOfRangeException",
|
||||
"SQLiteBlobTooBigException",
|
||||
"SQLiteCantOpenDatabaseException",
|
||||
"SQLiteConstraintException",
|
||||
"SQLiteDatabaseCorruptException",
|
||||
"SQLiteDatabaseLockedException",
|
||||
"SQLiteDatatypeMismatchException",
|
||||
"SQLiteDiskIOException",
|
||||
"SQLiteDoneException",
|
||||
"SQLiteException",
|
||||
"SQLiteFullException",
|
||||
"SQLiteMisuseException",
|
||||
"SQLiteOutOfMemoryException",
|
||||
"SQLiteReadOnlyDatabaseException",
|
||||
"SQLiteTableLockedException",
|
||||
"SQLiteUnsupportedOperationException",
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,335 @@
|
||||
"""
|
||||
Android Graphics PDF 组件 — PdfDocument, PdfRenderer, ParcelFileDescriptor
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import io
|
||||
import tempfile
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL, HAS_PYMUPDF, HAS_PDF2IMAGE, resolve_android_path
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ParcelFileDescriptor
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ParcelFileDescriptor(Stub):
|
||||
"""ParcelFileDescriptor — 文件描述符"""
|
||||
|
||||
MODE_READ_ONLY = 0x10000000
|
||||
MODE_WRITE_ONLY = 0x20000000
|
||||
MODE_READ_WRITE = 0x30000000
|
||||
MODE_CREATE = 0x08000000
|
||||
MODE_TRUNCATE = 0x04000000
|
||||
MODE_APPEND = 0x02000000
|
||||
|
||||
def __init__(self, fd: int = -1, file_path: str = ""):
|
||||
super().__init__()
|
||||
self._fd = fd
|
||||
self._file_path = file_path
|
||||
self._closed = False
|
||||
self._file_obj = None
|
||||
if file_path and fd < 0:
|
||||
resolved = resolve_android_path(file_path)
|
||||
self._file_path = resolved
|
||||
|
||||
@staticmethod
|
||||
def open(file_path: str, mode: int) -> "ParcelFileDescriptor":
|
||||
resolved = resolve_android_path(file_path)
|
||||
fmode = "rb"
|
||||
if mode & ParcelFileDescriptor.MODE_WRITE_ONLY:
|
||||
fmode = "wb"
|
||||
elif mode & ParcelFileDescriptor.MODE_READ_WRITE:
|
||||
fmode = "r+b"
|
||||
if mode & ParcelFileDescriptor.MODE_CREATE:
|
||||
if not os.path.exists(os.path.dirname(resolved)):
|
||||
os.makedirs(os.path.dirname(resolved), exist_ok=True)
|
||||
if not os.path.exists(resolved):
|
||||
open(resolved, "w").close()
|
||||
return ParcelFileDescriptor(-1, file_path)
|
||||
|
||||
@staticmethod
|
||||
def parseFromFd(fd: int) -> "ParcelFileDescriptor":
|
||||
return ParcelFileDescriptor(fd)
|
||||
|
||||
@staticmethod
|
||||
def parseFromString(path: str) -> "ParcelFileDescriptor":
|
||||
resolved = resolve_android_path(path)
|
||||
return ParcelFileDescriptor(-1, resolved)
|
||||
|
||||
@staticmethod
|
||||
def adoptFd(fd: int) -> "ParcelFileDescriptor":
|
||||
return ParcelFileDescriptor(fd)
|
||||
|
||||
@staticmethod
|
||||
def createPipe() -> List["ParcelFileDescriptor"]:
|
||||
r, w = os.pipe()
|
||||
return [ParcelFileDescriptor(r), ParcelFileDescriptor(w)]
|
||||
|
||||
def getFileDescriptor(self) -> int:
|
||||
return self._fd
|
||||
|
||||
def getStatSize(self) -> int:
|
||||
if self._file_path and os.path.exists(self._file_path):
|
||||
return os.path.getsize(self._file_path)
|
||||
return 0
|
||||
|
||||
def getFd(self) -> int:
|
||||
return self._fd
|
||||
|
||||
def close(self):
|
||||
if not self._closed:
|
||||
self._closed = True
|
||||
if self._file_obj:
|
||||
self._file_obj.close()
|
||||
elif self._fd >= 0:
|
||||
try:
|
||||
os.close(self._fd)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def getFilePath(self) -> str:
|
||||
return self._file_path
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ParcelFileDescriptor(fd={self._fd})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PdfDocument
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PdfDocument(Stub):
|
||||
"""PdfDocument — PDF 文档"""
|
||||
|
||||
class Page(Stub):
|
||||
"""PdfDocument.Page — PDF 页面"""
|
||||
|
||||
def __init__(self, page_number: int, width: int, height: int):
|
||||
super().__init__()
|
||||
self._page_number = page_number
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._canvas = None
|
||||
|
||||
def getCanvas(self) -> "Canvas":
|
||||
from .graphics import Canvas
|
||||
if self._canvas is None:
|
||||
self._canvas = Canvas(self._width, self._height)
|
||||
return self._canvas
|
||||
|
||||
def getWidth(self) -> int:
|
||||
return self._width
|
||||
|
||||
def getHeight(self) -> int:
|
||||
return self._height
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def finish(self):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._pages: List[PdfDocument.Page] = []
|
||||
|
||||
def startPage(self, page_size: "Page") -> "Canvas":
|
||||
width = page_size.getWidth() if hasattr(page_size, "getWidth") else 595
|
||||
height = page_size.getHeight() if hasattr(page_size, "getHeight") else 842
|
||||
page = PdfDocument.Page(len(self._pages), width, height)
|
||||
self._pages.append(page)
|
||||
return page.getCanvas()
|
||||
|
||||
def finishPage(self, page: "Page"):
|
||||
page.finish()
|
||||
|
||||
def getPages(self) -> List["Page"]:
|
||||
return list(self._pages)
|
||||
|
||||
def writeTo(self, out_stream, close: bool = True):
|
||||
if HAS_PYMUPDF:
|
||||
import fitz
|
||||
doc = fitz.open()
|
||||
for p in self._pages:
|
||||
page = doc.new_page(width=p._width, height=p._height)
|
||||
if HAS_PIL and hasattr(p._canvas, "_image") and p._canvas._image:
|
||||
img_bytes = io.BytesIO()
|
||||
p._canvas._image.save(img_bytes, format="PNG")
|
||||
img_bytes.seek(0)
|
||||
rect = page.rect
|
||||
page.insert_image(rect, stream=img_bytes.read())
|
||||
doc.save(out_stream)
|
||||
doc.close()
|
||||
elif HAS_PIL:
|
||||
from PIL import Image
|
||||
images = []
|
||||
for p in self._pages:
|
||||
if hasattr(p._canvas, "_image") and p._canvas._image:
|
||||
images.append(p._canvas._image.convert("RGB"))
|
||||
if images:
|
||||
images[0].save(out_stream, format="PDF", save_all=True,
|
||||
append_images=images[1:])
|
||||
else:
|
||||
blank = Image.new("RGB", (595, 842), (255, 255, 255))
|
||||
blank.save(out_stream, format="PDF")
|
||||
else:
|
||||
if close:
|
||||
out_stream.close()
|
||||
|
||||
def close(self):
|
||||
self._pages.clear()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PdfRenderer
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PdfRenderer(Stub):
|
||||
"""PdfRenderer — PDF 渲染器"""
|
||||
|
||||
RENDER_MODE_FOR_DISPLAY = 1
|
||||
RENDER_MODE_FOR_PRINT = 2
|
||||
RENDER_MODE_FOR_ALL = 3
|
||||
|
||||
class Page(Stub):
|
||||
"""PdfRenderer.Page — PDF 渲染页面"""
|
||||
|
||||
def __init__(self, renderer: "PdfRenderer", index: int):
|
||||
super().__init__()
|
||||
self._renderer = renderer
|
||||
self._index = index
|
||||
self._opened = True
|
||||
if renderer._doc:
|
||||
self._fitz_page = renderer._doc[index]
|
||||
rect = self._fitz_page.rect
|
||||
self._width = int(rect.width)
|
||||
self._height = int(rect.height)
|
||||
else:
|
||||
self._fitz_page = None
|
||||
self._width = 595
|
||||
self._height = 842
|
||||
|
||||
def getWidth(self) -> int:
|
||||
return self._width
|
||||
|
||||
def getHeight(self) -> int:
|
||||
return self._height
|
||||
|
||||
def getIndex(self) -> int:
|
||||
return self._index
|
||||
|
||||
def render(self, canvas: "Canvas", destLeft: int, destTop: int,
|
||||
destRight: int, destBottom: int, flags: int = 0):
|
||||
if not self._opened:
|
||||
return
|
||||
if HAS_PYMUPDF and self._fitz_page:
|
||||
from .graphics import Bitmap
|
||||
mat = fitz.Matrix(2, 2) # 2x zoom for quality
|
||||
pix = self._fitz_page.get_pixmap(matrix=mat)
|
||||
img_data = pix.tobytes("png")
|
||||
if HAS_PIL and hasattr(canvas, "_image") and canvas._image:
|
||||
from PIL import Image
|
||||
import io as _io
|
||||
img = Image.open(io.BytesIO(img_data))
|
||||
img = img.resize((destRight - destLeft, destBottom - destTop))
|
||||
canvas._image.paste(img, (destLeft, destTop))
|
||||
elif HAS_PDF2IMAGE and HAS_PIL and hasattr(canvas, "_image"):
|
||||
from pdf2image import convert_from_bytes
|
||||
if self._renderer._pdf_bytes:
|
||||
images = convert_from_bytes(
|
||||
self._renderer._pdf_bytes,
|
||||
first_page=self._index + 1,
|
||||
last_page=self._index + 1
|
||||
)
|
||||
if images:
|
||||
img = images[0].resize((destRight - destLeft, destBottom - destTop))
|
||||
canvas._image.paste(img.convert("RGBA"), (destLeft, destTop))
|
||||
|
||||
def start(self):
|
||||
self._opened = True
|
||||
|
||||
def finish(self):
|
||||
self._opened = False
|
||||
|
||||
def close(self):
|
||||
self._opened = False
|
||||
|
||||
def __init__(self, pfd: ParcelFileDescriptor, closePfd: bool = True):
|
||||
super().__init__()
|
||||
self._pfd = pfd
|
||||
self._close_pfd = closePfd
|
||||
self._page_count = 0
|
||||
self._pdf_bytes = None
|
||||
self._doc = None
|
||||
|
||||
file_path = pfd.getFilePath()
|
||||
if file_path and os.path.exists(file_path):
|
||||
with open(file_path, "rb") as f:
|
||||
self._pdf_bytes = f.read()
|
||||
elif pfd._fd >= 0:
|
||||
try:
|
||||
self._pdf_bytes = os.read(pfd._fd, 10 * 1024 * 1024)
|
||||
except OSError:
|
||||
self._pdf_bytes = None
|
||||
|
||||
if HAS_PYMUPDF and self._pdf_bytes:
|
||||
import fitz
|
||||
self._doc = fitz.open(stream=self._pdf_bytes, filetype="pdf")
|
||||
self._page_count = len(self._doc)
|
||||
elif self._pdf_bytes:
|
||||
self._page_count = 1 # fallback
|
||||
else:
|
||||
self._page_count = 0
|
||||
|
||||
def openPage(self, index: int) -> "Page":
|
||||
if 0 <= index < self._page_count:
|
||||
return PdfRenderer.Page(self, index)
|
||||
raise IndexError(f"Page index {index} out of range (0-{self._page_count - 1})")
|
||||
|
||||
def close(self):
|
||||
if self._doc and HAS_PYMUPDF:
|
||||
self._doc.close()
|
||||
self._doc = None
|
||||
if self._close_pfd and self._pfd:
|
||||
self._pfd.close()
|
||||
self._pdf_bytes = None
|
||||
|
||||
def getPageCount(self) -> int:
|
||||
return self._page_count
|
||||
|
||||
def shouldScale(self) -> bool:
|
||||
return False
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PdfDocumentPage (alias for PdfDocument.Page)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
PdfDocumentPage = PdfDocument.Page
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"PdfDocument",
|
||||
"PdfDocumentPage",
|
||||
"PdfRenderer",
|
||||
"ParcelFileDescriptor",
|
||||
]
|
||||
@@ -0,0 +1,533 @@
|
||||
"""
|
||||
Android Hardware 组件 — Sensor, SensorManager, SensorEvent, 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import math
|
||||
import time
|
||||
import random
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Sensor
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Sensor(Stub):
|
||||
"""Sensor — 传感器"""
|
||||
|
||||
TYPE_ACCELEROMETER = 1
|
||||
TYPE_MAGNETIC_FIELD = 2
|
||||
TYPE_ORIENTATION = 3
|
||||
TYPE_GYROSCOPE = 4
|
||||
TYPE_LIGHT = 5
|
||||
TYPE_PRESSURE = 6
|
||||
TYPE_PROXIMITY = 8
|
||||
TYPE_GRAVITY = 9
|
||||
TYPE_LINEAR_ACCELERATION = 10
|
||||
TYPE_ROTATION_VECTOR = 11
|
||||
TYPE_RELATIVE_HUMIDITY = 12
|
||||
TYPE_AMBIENT_TEMPERATURE = 13
|
||||
TYPE_STEP_COUNTER = 19
|
||||
TYPE_STEP_DETECTOR = 18
|
||||
TYPE_HEART_RATE = 21
|
||||
TYPE_LOW_LATENCY_OFFBODY_DETECT = 34
|
||||
TYPE_ACCELEROMETER_LIMITED_AXES = 35
|
||||
TYPE_DEVICE_ORIENTATION = 27
|
||||
TYPE_STATIONARY_DETECT = 28
|
||||
|
||||
REPORTING_MODE_CONTINUOUS = 0
|
||||
REPORTING_MODE_ON_CHANGE = 1
|
||||
REPORTING_MODE_ONE_SHOT = 2
|
||||
REPORTING_MODE_SPECIAL_TRIGGER = 3
|
||||
|
||||
def __init__(self, type_: int, name: str = "Simulated Sensor",
|
||||
vendor: str = "Android-pyApi", version: int = 1,
|
||||
resolution: float = 1.0, max_range: float = 100.0,
|
||||
power: float = 0.5, min_delay: int = 10000):
|
||||
super().__init__()
|
||||
self._type = type_
|
||||
self._name = name
|
||||
self._vendor = vendor
|
||||
self._version = version
|
||||
self._resolution = resolution
|
||||
self._max_range = max_range
|
||||
self._power = power
|
||||
self._min_delay = min_delay
|
||||
|
||||
# Assign default values based on sensor type
|
||||
type_defaults = {
|
||||
Sensor.TYPE_ACCELEROMETER: ("Accelerometer", 0.01, 39.24, 3.0, 10000),
|
||||
Sensor.TYPE_GYROSCOPE: ("Gyroscope", 0.001, 34.91, 6.1, 10000),
|
||||
Sensor.TYPE_MAGNETIC_FIELD: ("Magnetic Field", 0.01, 2000.0, 4.0, 10000),
|
||||
Sensor.TYPE_LIGHT: ("Light", 1.0, 40000.0, 0.5, 10000),
|
||||
Sensor.TYPE_PROXIMITY: ("Proximity", 1.0, 10.0, 0.5, 10000),
|
||||
Sensor.TYPE_PRESSURE: ("Pressure", 0.01, 1100.0, 1.0, 10000),
|
||||
Sensor.TYPE_GRAVITY: ("Gravity", 0.01, 39.24, 3.0, 10000),
|
||||
Sensor.TYPE_LINEAR_ACCELERATION: ("Linear Acceleration", 0.01, 39.24, 3.0, 10000),
|
||||
Sensor.TYPE_ROTATION_VECTOR: ("Rotation Vector", 0.0001, 1.0, 5.0, 10000),
|
||||
Sensor.TYPE_RELATIVE_HUMIDITY: ("Relative Humidity", 0.1, 100.0, 0.5, 10000),
|
||||
Sensor.TYPE_AMBIENT_TEMPERATURE: ("Ambient Temperature", 0.01, 80.0, 0.5, 10000),
|
||||
Sensor.TYPE_STEP_COUNTER: ("Step Counter", 1.0, 100000.0, 0.1, 100000),
|
||||
Sensor.TYPE_STEP_DETECTOR: ("Step Detector", 1.0, 1.0, 0.1, 100000),
|
||||
Sensor.TYPE_HEART_RATE: ("Heart Rate", 1.0, 220.0, 0.5, 100000),
|
||||
}
|
||||
|
||||
if type_ in type_defaults:
|
||||
n, res, mr, p, md = type_defaults[type_]
|
||||
self._name = name if name != "Simulated Sensor" else n
|
||||
self._resolution = resolution if resolution != 1.0 else res
|
||||
self._max_range = max_range if max_range != 100.0 else mr
|
||||
self._power = power if power != 0.5 else p
|
||||
self._min_delay = min_delay if min_delay != 10000 else md
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._type
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getVendor(self) -> str:
|
||||
return self._vendor
|
||||
|
||||
def getVersion(self) -> int:
|
||||
return self._version
|
||||
|
||||
def getMaximumRange(self) -> float:
|
||||
return self._max_range
|
||||
|
||||
def getMinDelay(self) -> int:
|
||||
return self._min_delay
|
||||
|
||||
def getPower(self) -> float:
|
||||
return self._power
|
||||
|
||||
def getResolution(self) -> float:
|
||||
return self._resolution
|
||||
|
||||
def getFifoMaxEventCount(self) -> int:
|
||||
return 0
|
||||
|
||||
def getFifoReservedEventCount(self) -> int:
|
||||
return 0
|
||||
|
||||
def getReportingMode(self) -> int:
|
||||
return Sensor.REPORTING_MODE_CONTINUOUS
|
||||
|
||||
def isWakeUpSensor(self) -> bool:
|
||||
return False
|
||||
|
||||
def getStringType(self) -> str:
|
||||
type_names = {
|
||||
Sensor.TYPE_ACCELEROMETER: "android.sensor.accelerometer",
|
||||
Sensor.TYPE_MAGNETIC_FIELD: "android.sensor.magnetic_field",
|
||||
Sensor.TYPE_GYROSCOPE: "android.sensor.gyroscope",
|
||||
Sensor.TYPE_LIGHT: "android.sensor.light",
|
||||
Sensor.TYPE_PRESSURE: "android.sensor.pressure",
|
||||
Sensor.TYPE_PROXIMITY: "android.sensor.proximity",
|
||||
Sensor.TYPE_GRAVITY: "android.sensor.gravity",
|
||||
Sensor.TYPE_LINEAR_ACCELERATION: "android.sensor.linear_acceleration",
|
||||
Sensor.TYPE_ROTATION_VECTOR: "android.sensor.rotation_vector",
|
||||
Sensor.TYPE_RELATIVE_HUMIDITY: "android.sensor.relative_humidity",
|
||||
Sensor.TYPE_AMBIENT_TEMPERATURE: "android.sensor.ambient_temperature",
|
||||
Sensor.TYPE_STEP_COUNTER: "android.sensor.step_counter",
|
||||
Sensor.TYPE_STEP_DETECTOR: "android.sensor.step_detector",
|
||||
Sensor.TYPE_HEART_RATE: "android.sensor.heart_rate",
|
||||
}
|
||||
return type_names.get(self._type, "android.sensor.unknown")
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"{self._name} (type={self._type}, vendor={self._vendor}, ver={self._version})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SensorEvent
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SensorEvent(Stub):
|
||||
"""SensorEvent — 传感器事件"""
|
||||
|
||||
def __init__(self, sensor: Sensor, accuracy: int = 3):
|
||||
super().__init__()
|
||||
self.sensor = sensor
|
||||
self.accuracy = accuracy
|
||||
self.timestamp = int(time.time() * 1_000_000_000) # nanoseconds
|
||||
self.values: List[float] = []
|
||||
|
||||
# Generate appropriate values based on sensor type
|
||||
if sensor.getType() == Sensor.TYPE_ACCELEROMETER:
|
||||
self.values = [random.uniform(-1, 1), random.uniform(-1, 1),
|
||||
9.8 + random.uniform(-0.5, 0.5)]
|
||||
elif sensor.getType() == Sensor.TYPE_GYROSCOPE:
|
||||
self.values = [random.uniform(-0.1, 0.1)] * 3
|
||||
elif sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD:
|
||||
self.values = [random.uniform(-50, 50)] * 3
|
||||
elif sensor.getType() == Sensor.TYPE_LIGHT:
|
||||
self.values = [random.uniform(100, 1000)]
|
||||
elif sensor.getType() == Sensor.TYPE_PROXIMITY:
|
||||
self.values = [5.0 if random.random() > 0.5 else 0.0]
|
||||
elif sensor.getType() == Sensor.TYPE_PRESSURE:
|
||||
self.values = [random.uniform(980, 1050)]
|
||||
elif sensor.getType() in (Sensor.TYPE_GRAVITY, Sensor.TYPE_LINEAR_ACCELERATION):
|
||||
self.values = [random.uniform(-1, 1)] * 3
|
||||
elif sensor.getType() == Sensor.TYPE_ROTATION_VECTOR:
|
||||
self.values = [random.uniform(-1, 1)] * 5
|
||||
elif sensor.getType() == Sensor.TYPE_RELATIVE_HUMIDITY:
|
||||
self.values = [random.uniform(30, 70)]
|
||||
elif sensor.getType() == Sensor.TYPE_AMBIENT_TEMPERATURE:
|
||||
self.values = [random.uniform(15, 35)]
|
||||
elif sensor.getType() == Sensor.TYPE_STEP_COUNTER:
|
||||
self.values = [float(random.randint(0, 10000))]
|
||||
elif sensor.getType() == Sensor.TYPE_STEP_DETECTOR:
|
||||
self.values = [1.0 if random.random() > 0.95 else 0.0]
|
||||
elif sensor.getType() == Sensor.TYPE_HEART_RATE:
|
||||
self.values = [random.uniform(60, 100)]
|
||||
else:
|
||||
self.values = [0.0]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SensorEventListener
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SensorEventListener(Stub):
|
||||
"""SensorEventListener — 传感器事件监听器"""
|
||||
|
||||
def onSensorChanged(self, event: SensorEvent):
|
||||
pass
|
||||
|
||||
def onAccuracyChanged(self, sensor: Sensor, accuracy: int):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SensorDelay
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SensorDelay(Stub):
|
||||
"""SensorDelay — 传感器采样延迟常量"""
|
||||
FASTEST = 0
|
||||
GAME = 1
|
||||
UI = 2
|
||||
NORMAL = 3
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SensorManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SensorManager(Stub):
|
||||
"""SensorManager — 传感器管理器"""
|
||||
|
||||
SENSOR_DELAY_FASTEST = 0
|
||||
SENSOR_DELAY_GAME = 1
|
||||
SENSOR_DELAY_UI = 2
|
||||
SENSOR_DELAY_NORMAL = 3
|
||||
|
||||
AXIS_X = 1
|
||||
AXIS_Y = 2
|
||||
AXIS_Z = 3
|
||||
AXIS_MINUS_X = 129
|
||||
AXIS_MINUS_Y = 130
|
||||
AXIS_MINUS_Z = 131
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._sensors: Dict[int, Sensor] = {}
|
||||
self._listeners: Dict[int, List[Tuple[SensorEventListener, Sensor, int]]] = {}
|
||||
|
||||
# Create default sensors
|
||||
for type_ in [
|
||||
Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE,
|
||||
Sensor.TYPE_MAGNETIC_FIELD, Sensor.TYPE_LIGHT,
|
||||
Sensor.TYPE_PROXIMITY, Sensor.TYPE_PRESSURE,
|
||||
Sensor.TYPE_GRAVITY, Sensor.TYPE_LINEAR_ACCELERATION,
|
||||
Sensor.TYPE_ROTATION_VECTOR, Sensor.TYPE_RELATIVE_HUMIDITY,
|
||||
Sensor.TYPE_AMBIENT_TEMPERATURE, Sensor.TYPE_STEP_COUNTER,
|
||||
Sensor.TYPE_STEP_DETECTOR, Sensor.TYPE_HEART_RATE,
|
||||
]:
|
||||
self._sensors[type_] = Sensor(type_)
|
||||
|
||||
def getDefaultSensor(self, type_: int) -> Optional[Sensor]:
|
||||
return self._sensors.get(type_)
|
||||
|
||||
def getSensorList(self, type_: int) -> List[Sensor]:
|
||||
if type_ == -1: # ALL
|
||||
return list(self._sensors.values())
|
||||
sensor = self._sensors.get(type_)
|
||||
return [sensor] if sensor else []
|
||||
|
||||
def getSensors(self) -> List[Sensor]:
|
||||
return list(self._sensors.values())
|
||||
|
||||
def registerListener(self, listener: SensorEventListener, sensor: Sensor,
|
||||
samplingPeriodUs: int, maxReportLatencyUs: int = 0) -> bool:
|
||||
if sensor.getType() not in self._sensors:
|
||||
return False
|
||||
if listener not in self._listeners:
|
||||
self._listeners[id(listener)] = []
|
||||
self._listeners[id(listener)].append((listener, sensor, samplingPeriodUs))
|
||||
return True
|
||||
|
||||
def unregisterListener(self, listener: SensorEventListener, sensor: Sensor = None) -> bool:
|
||||
lid = id(listener)
|
||||
if lid in self._listeners:
|
||||
if sensor:
|
||||
self._listeners[lid] = [
|
||||
(l, s, d) for l, s, d in self._listeners[lid]
|
||||
if s.getType() != sensor.getType()
|
||||
]
|
||||
if not self._listeners[lid]:
|
||||
del self._listeners[lid]
|
||||
else:
|
||||
del self._listeners[lid]
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def getOrientation(R: List[float], values: List[float]) -> List[float]:
|
||||
if len(R) >= 9 and len(values) >= 3:
|
||||
values[0] = math.degrees(math.atan2(R[1], R[4])) # azimuth
|
||||
values[1] = math.degrees(math.asin(-R[7])) # pitch
|
||||
values[2] = math.degrees(math.atan2(-R[6], R[8])) # roll
|
||||
return values
|
||||
|
||||
@staticmethod
|
||||
def getRotationMatrix(R: List[float], I: List[float],
|
||||
gravity: List[float], geomagnetic: List[float]) -> bool:
|
||||
if len(R) < 9:
|
||||
R.extend([0] * (9 - len(R)))
|
||||
if len(R) >= 9:
|
||||
R[0] = 1.0
|
||||
R[1] = 0.0
|
||||
R[2] = 0.0
|
||||
R[3] = 0.0
|
||||
R[4] = 1.0
|
||||
R[5] = 0.0
|
||||
R[6] = 0.0
|
||||
R[7] = 0.0
|
||||
R[8] = 1.0
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def getAltitude(p0: float, p: float) -> float:
|
||||
return 44330.0 * (1.0 - math.pow(p / p0, 1.0 / 5.255))
|
||||
|
||||
@staticmethod
|
||||
def getInclination(I: List[float]) -> float:
|
||||
return 0.0
|
||||
|
||||
@staticmethod
|
||||
def remapCoordinateSystem(inR: List[float], X: int, Y: int,
|
||||
outR: List[float]) -> bool:
|
||||
if len(inR) >= 9 and len(outR) >= 9:
|
||||
for i in range(9):
|
||||
outR[i] = inR[i]
|
||||
return True
|
||||
|
||||
def getDeviceId(self) -> str:
|
||||
return "sensor_hub_0"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# HardwareBuffer
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class HardwareBuffer(Stub):
|
||||
"""HardwareBuffer — 硬件缓冲 (API 26+)"""
|
||||
|
||||
USAGE_CPU_READ_OFTEN = 0x00000003
|
||||
USAGE_CPU_WRITE_OFTEN = 0x00000030
|
||||
USAGE_GPU_SAMPLED_IMAGE = 0x00000100
|
||||
USAGE_GPU_COLOR_OUTPUT = 0x00000200
|
||||
USAGE_GPU_DATA_BUFFER = 0x00001000
|
||||
USAGE_SENSOR_DIRECT_DATA = 0x00400000
|
||||
USAGE_PROTECTED_CONTENT = 0x00004000
|
||||
USAGE_VIDEO_ENCODE = 0x00010000
|
||||
USAGE_GPU_FRAMEBUFFER = 0x00020000
|
||||
USAGE_COMPOSER_OVERLAY = 0x00000800
|
||||
USAGE_FRONT_BUFFER_QUEUE = 0x00008000
|
||||
|
||||
BLOB = 0x21
|
||||
RGBA_8888 = 1
|
||||
RGBA_FP16 = 22
|
||||
RGBA_1010102 = 43
|
||||
RGBX_8888 = 2
|
||||
RGB_888 = 3
|
||||
RGB_565 = 4
|
||||
YCBCR_420_888 = 35
|
||||
|
||||
def __init__(self, width: int, height: int, format: int, layers: int,
|
||||
usage: int, buffer_size: int = 0):
|
||||
super().__init__()
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._format = format
|
||||
self._layers = layers
|
||||
self._usage = usage
|
||||
self._buffer_size = buffer_size
|
||||
self._closed = False
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return self._width
|
||||
|
||||
@property
|
||||
def height(self) -> int:
|
||||
return self._height
|
||||
|
||||
@property
|
||||
def format(self) -> int:
|
||||
return self._format
|
||||
|
||||
@property
|
||||
def layers(self) -> int:
|
||||
return self._layers
|
||||
|
||||
@property
|
||||
def usage(self) -> int:
|
||||
return self._usage
|
||||
|
||||
def destroy(self):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
def isClosed(self) -> bool:
|
||||
return self._closed
|
||||
|
||||
def isEmpty(self) -> bool:
|
||||
return self._width == 0 or self._height == 0
|
||||
|
||||
def getWidth(self) -> int:
|
||||
return self._width
|
||||
|
||||
def getHeight(self) -> int:
|
||||
return self._height
|
||||
|
||||
def getFormat(self) -> int:
|
||||
return self._format
|
||||
|
||||
def getLayers(self) -> int:
|
||||
return self._layers
|
||||
|
||||
def getUsage(self) -> int:
|
||||
return self._usage
|
||||
|
||||
def getStride(self) -> int:
|
||||
return self._width
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BatteryManager (Constants)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BatteryManager(Stub):
|
||||
"""BatteryManager — 电池管理器常量 (实际实现在 android.os)"""
|
||||
|
||||
BATTERY_PROPERTY_CHARGE_COUNTER = 1
|
||||
BATTERY_PROPERTY_CURRENT_NOW = 2
|
||||
BATTERY_PROPERTY_CURRENT_AVERAGE = 3
|
||||
BATTERY_PROPERTY_CAPACITY = 4
|
||||
BATTERY_PROPERTY_STATUS = 5
|
||||
BATTERY_PROPERTY_ENERGY_COUNTER = 6
|
||||
|
||||
BATTERY_STATUS_UNKNOWN = 1
|
||||
BATTERY_STATUS_CHARGING = 2
|
||||
BATTERY_STATUS_DISCHARGING = 3
|
||||
BATTERY_STATUS_NOT_CHARGING = 4
|
||||
BATTERY_STATUS_FULL = 5
|
||||
|
||||
BATTERY_HEALTH_UNKNOWN = 1
|
||||
BATTERY_HEALTH_GOOD = 2
|
||||
BATTERY_HEALTH_OVERHEAT = 3
|
||||
BATTERY_HEALTH_DEAD = 4
|
||||
BATTERY_HEALTH_OVER_VOLTAGE = 5
|
||||
BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6
|
||||
BATTERY_HEALTH_COLD = 7
|
||||
|
||||
BATTERY_PLUGGED_AC = 1
|
||||
BATTERY_PLUGGED_USB = 2
|
||||
BATTERY_PLUGGED_WIRELESS = 4
|
||||
|
||||
ACTION_CHARGING = "android.intent.action.ACTION_CHARGING"
|
||||
ACTION_DISCHARGING = "android.intent.action.ACTION_DISCHARGING"
|
||||
EXTRA_STATUS = "status"
|
||||
EXTRA_HEALTH = "health"
|
||||
EXTRA_LEVEL = "level"
|
||||
EXTRA_SCALE = "scale"
|
||||
EXTRA_PLUGGED = "plugged"
|
||||
EXTRA_TEMPERATURE = "temperature"
|
||||
EXTRA_VOLTAGE = "voltage"
|
||||
EXTRA_TECHNOLOGY = "technology"
|
||||
EXTRA_CHARGE_COUNTER = "charge_counter"
|
||||
EXTRA_PRESENT = "present"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def getIntProperty(self, id: int) -> int:
|
||||
id_values = {
|
||||
BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER: 3500,
|
||||
BatteryManager.BATTERY_PROPERTY_CAPACITY: 85,
|
||||
BatteryManager.BATTERY_PROPERTY_STATUS: BatteryManager.BATTERY_STATUS_DISCHARGING,
|
||||
}
|
||||
return id_values.get(id, 0)
|
||||
|
||||
def getLongProperty(self, id: int) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BiometricManager (lightweight)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BiometricManager(Stub):
|
||||
"""BiometricManager — 生物识别管理器 (轻量)"""
|
||||
|
||||
BIOMETRIC_SUCCESS = 0
|
||||
BIOMETRIC_ERROR_HW_UNAVAILABLE = 1
|
||||
BIOMETRIC_ERROR_NONE_ENROLLED = 11
|
||||
BIOMETRIC_ERROR_NO_HARDWARE = 12
|
||||
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def canAuthenticate(self) -> int:
|
||||
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Flashlight support
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraFlashlight(Stub):
|
||||
"""CameraFlashlight — 手电筒支持"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._on = False
|
||||
|
||||
def setTorchMode(self, cameraId: str, enabled: bool):
|
||||
self._on = enabled
|
||||
|
||||
def isTorchModeEnabled(self, cameraId: str) -> bool:
|
||||
return self._on
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"Sensor", "SensorManager", "SensorEvent", "SensorEventListener",
|
||||
"SensorDelay",
|
||||
"HardwareBuffer",
|
||||
"BatteryManager",
|
||||
"BiometricManager",
|
||||
"CameraFlashlight",
|
||||
]
|
||||
@@ -0,0 +1,291 @@
|
||||
"""
|
||||
Android Biometrics 组件 — BiometricManager, BiometricPrompt, 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, android_context
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BiometricConstants
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BiometricConstants(Stub):
|
||||
"""BiometricConstants — 生物识别常量"""
|
||||
|
||||
BIOMETRIC_SUCCESS = 0
|
||||
BIOMETRIC_ERROR_HW_UNAVAILABLE = 1
|
||||
BIOMETRIC_ERROR_VENDOR = 8
|
||||
BIOMETRIC_ERROR_LOCKOUT = 7
|
||||
BIOMETRIC_ERROR_LOCKOUT_PERMANENT = 9
|
||||
BIOMETRIC_ERROR_USER_CANCELED = 10
|
||||
BIOMETRIC_ERROR_NO_BIOMETRIC = 11
|
||||
BIOMETRIC_ERROR_HW_NOT_PRESENT = 12
|
||||
BIOMETRIC_ERROR_NEGATIVE_BUTTON = 13
|
||||
BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14
|
||||
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15
|
||||
BIOMETRIC_ERROR_UNSUPPORTED = 65536
|
||||
BIOMETRIC_ERROR_CANCELED = 5
|
||||
BIOMETRIC_ERROR_TIMEOUT = 3
|
||||
BIOMETRIC_ERROR_NONE_ENROLLED = 11
|
||||
BIOMETRIC_ERROR_NO_HARDWARE = 12
|
||||
BIOMETRIC_STATUS_OK = 0
|
||||
|
||||
# Authenticator types
|
||||
BIOMETRIC_STRONG = 0x0F
|
||||
BIOMETRIC_WEAK = 0xFF
|
||||
DEVICE_CREDENTIAL = 0x8000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BiometricPrompt
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BiometricPrompt(Stub):
|
||||
"""BiometricPrompt — 生物识别提示 (API 28+)"""
|
||||
|
||||
class AuthenticationCallback(Stub):
|
||||
"""BiometricPrompt.AuthenticationCallback — 认证回调"""
|
||||
|
||||
def onAuthenticationError(self, errorCode: int, errString: str):
|
||||
pass
|
||||
|
||||
def onAuthenticationSucceeded(self, result: "BiometricPrompt.AuthenticationResult"):
|
||||
pass
|
||||
|
||||
def onAuthenticationFailed(self):
|
||||
pass
|
||||
|
||||
def onAuthenticationAcquired(self, acquireInfo: int):
|
||||
pass
|
||||
|
||||
class AuthenticationResult(Stub):
|
||||
"""BiometricPrompt.AuthenticationResult — 认证结果"""
|
||||
|
||||
def __init__(self, cryptoObject: "BiometricPromptCryptoObject" = None,
|
||||
authenticationType: int = BiometricConstants.BIOMETRIC_STRONG):
|
||||
super().__init__()
|
||||
self._crypto_object = cryptoObject
|
||||
self._authentication_type = authenticationType
|
||||
|
||||
def getCryptoObject(self) -> Optional["BiometricPromptCryptoObject"]:
|
||||
return self._crypto_object
|
||||
|
||||
def getAuthenticationType(self) -> int:
|
||||
return self._authentication_type
|
||||
|
||||
class PromptInfo(Stub):
|
||||
"""BiometricPrompt.PromptInfo — 提示信息"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""BiometricPrompt.PromptInfo.Builder — 提示信息构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._title = ""
|
||||
self._subtitle = ""
|
||||
self._description = ""
|
||||
self._device_credential_allowed = False
|
||||
self._allowed_authenticators = 0
|
||||
self._confirmation_required = True
|
||||
self._negative_button_text = ""
|
||||
|
||||
def setTitle(self, title: str) -> "BiometricPrompt.PromptInfo.Builder":
|
||||
self._title = title
|
||||
return self
|
||||
|
||||
def setSubtitle(self, subtitle: str) -> "BiometricPrompt.PromptInfo.Builder":
|
||||
self._subtitle = subtitle
|
||||
return self
|
||||
|
||||
def setDescription(self, description: str) -> "BiometricPrompt.PromptInfo.Builder":
|
||||
self._description = description
|
||||
return self
|
||||
|
||||
def setDeviceCredentialAllowed(self, allowed: bool) -> "BiometricPrompt.PromptInfo.Builder":
|
||||
self._device_credential_allowed = allowed
|
||||
return self
|
||||
|
||||
def setAllowedAuthenticators(self, authenticators: int) -> "BiometricPrompt.PromptInfo.Builder":
|
||||
self._allowed_authenticators = authenticators
|
||||
return self
|
||||
|
||||
def setConfirmationRequired(self, required: bool) -> "BiometricPrompt.PromptInfo.Builder":
|
||||
self._confirmation_required = required
|
||||
return self
|
||||
|
||||
def setNegativeButtonText(self, text: str) -> "BiometricPrompt.PromptInfo.Builder":
|
||||
self._negative_button_text = text
|
||||
return self
|
||||
|
||||
def build(self) -> "BiometricPrompt.PromptInfo":
|
||||
info = BiometricPrompt.PromptInfo()
|
||||
info._title = self._title
|
||||
info._subtitle = self._subtitle
|
||||
info._description = self._description
|
||||
info._device_credential_allowed = self._device_credential_allowed
|
||||
info._allowed_authenticators = self._allowed_authenticators
|
||||
info._confirmation_required = self._confirmation_required
|
||||
info._negative_button_text = self._negative_button_text
|
||||
return info
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._title = ""
|
||||
self._subtitle = ""
|
||||
self._description = ""
|
||||
self._device_credential_allowed = False
|
||||
self._allowed_authenticators = 0
|
||||
self._confirmation_required = True
|
||||
self._negative_button_text = ""
|
||||
|
||||
def getTitle(self) -> str:
|
||||
return self._title
|
||||
|
||||
def getSubtitle(self) -> str:
|
||||
return self._subtitle
|
||||
|
||||
def getDescription(self) -> str:
|
||||
return self._description
|
||||
|
||||
def isDeviceCredentialAllowed(self) -> bool:
|
||||
return self._device_credential_allowed
|
||||
|
||||
def getAllowedAuthenticators(self) -> int:
|
||||
return self._allowed_authenticators
|
||||
|
||||
def isConfirmationRequired(self) -> bool:
|
||||
return self._confirmation_required
|
||||
|
||||
def getNegativeButtonText(self) -> str:
|
||||
return self._negative_button_text
|
||||
|
||||
def __init__(self, context, callback: AuthenticationCallback = None,
|
||||
handler=None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._callback = callback
|
||||
self._cancelled = False
|
||||
|
||||
def authenticate(self, crypto: "BiometricPromptCryptoObject" = None,
|
||||
cancel: Any = None, executor=None,
|
||||
callback: AuthenticationCallback = None):
|
||||
self._cancelled = False
|
||||
if callback:
|
||||
self._callback = callback
|
||||
if self._callback:
|
||||
# Simulate failed authentication on desktop
|
||||
self._callback.onAuthenticationError(
|
||||
BiometricConstants.BIOMETRIC_ERROR_NO_HARDWARE,
|
||||
"No biometric hardware available on this device"
|
||||
)
|
||||
|
||||
def authenticate(self, promptInfo: PromptInfo, crypto: "BiometricPromptCryptoObject" = None):
|
||||
self._cancelled = False
|
||||
if self._callback:
|
||||
self._callback.onAuthenticationError(
|
||||
BiometricConstants.BIOMETRIC_ERROR_NO_HARDWARE,
|
||||
"No biometric hardware available on this device"
|
||||
)
|
||||
|
||||
def cancelAuthentication(self):
|
||||
self._cancelled = True
|
||||
if self._callback:
|
||||
self._callback.onAuthenticationError(
|
||||
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
|
||||
"Authentication cancelled"
|
||||
)
|
||||
|
||||
def cancel(self):
|
||||
self.cancelAuthentication()
|
||||
|
||||
def isCancelled(self) -> bool:
|
||||
return self._cancelled
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BiometricPromptCryptoObject
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BiometricPromptCryptoObject(Stub):
|
||||
"""BiometricPromptCryptoObject — 加密对象"""
|
||||
|
||||
def __init__(self, signature=None, cipher=None, mac=None):
|
||||
super().__init__()
|
||||
self._signature = signature
|
||||
self._cipher = cipher
|
||||
self._mac = mac
|
||||
|
||||
def getSignature(self):
|
||||
return self._signature
|
||||
|
||||
def getCipher(self):
|
||||
return self._cipher
|
||||
|
||||
def getMac(self):
|
||||
return self._mac
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BiometricPromptAuthenticationResult
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
BiometricPromptAuthenticationResult = BiometricPrompt.AuthenticationResult
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BiometricManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BiometricManager(Stub):
|
||||
"""BiometricManager — 生物识别管理器 (API 29+)"""
|
||||
|
||||
BIOMETRIC_SUCCESS = BiometricConstants.BIOMETRIC_SUCCESS
|
||||
BIOMETRIC_ERROR_HW_UNAVAILABLE = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
BIOMETRIC_ERROR_NONE_ENROLLED = BiometricConstants.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
BIOMETRIC_ERROR_NO_HARDWARE = BiometricConstants.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||
BIOMETRIC_ERROR_UNSUPPORTED = BiometricConstants.BIOMETRIC_ERROR_UNSUPPORTED
|
||||
BIOMETRIC_STATUS_OK = BiometricConstants.BIOMETRIC_STATUS_OK
|
||||
BIOMETRIC_ERROR_VENDOR = BiometricConstants.BIOMETRIC_ERROR_VENDOR
|
||||
BIOMETRIC_ERROR_USER_CANCELED = BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED
|
||||
BIOMETRIC_ERROR_CANCELED = BiometricConstants.BIOMETRIC_ERROR_CANCELED
|
||||
BIOMETRIC_ERROR_TIMEOUT = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT
|
||||
BIOMETRIC_ERROR_LOCKOUT = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
|
||||
BIOMETRIC_ERROR_LOCKOUT_PERMANENT = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT
|
||||
|
||||
# Authenticators
|
||||
AUTHENTICATOR_BIOMETRIC_STRONG = BiometricConstants.BIOMETRIC_STRONG
|
||||
AUTHENTICATOR_BIOMETRIC_WEAK = BiometricConstants.BIOMETRIC_WEAK
|
||||
AUTHENTICATOR_DEVICE_CREDENTIAL = BiometricConstants.DEVICE_CREDENTIAL
|
||||
|
||||
def __init__(self, context=None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
|
||||
def canAuthenticate(self, authenticators: int = AUTHENTICATOR_BIOMETRIC_STRONG) -> int:
|
||||
"""Check if biometric authentication is available"""
|
||||
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
|
||||
@staticmethod
|
||||
def fromContext(context) -> "BiometricManager":
|
||||
"""Get BiometricManager from context"""
|
||||
return BiometricManager(context)
|
||||
|
||||
def toString(self) -> str:
|
||||
return "BiometricManager(desktop - no hardware)"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"BiometricManager",
|
||||
"BiometricPrompt",
|
||||
"BiometricPromptAuthenticationResult",
|
||||
"BiometricPromptCryptoObject",
|
||||
"BiometricConstants",
|
||||
]
|
||||
@@ -0,0 +1,761 @@
|
||||
"""
|
||||
Android Camera2 组件 — CameraManager, CameraDevice, CaptureRequest 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraMetadata
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraMetadata(Stub):
|
||||
"""CameraMetadata — 相机元数据基类"""
|
||||
|
||||
# CONTROL_AE modes
|
||||
CONTROL_AE_MODE_OFF = 0
|
||||
CONTROL_AE_MODE_ON = 1
|
||||
CONTROL_AE_MODE_ON_AUTO_FLASH = 2
|
||||
CONTROL_AE_MODE_ON_ALWAYS_FLASH = 3
|
||||
CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4
|
||||
CONTROL_AE_MODE_EXTERNAL_FLASH = 5
|
||||
|
||||
# CONTROL_AF modes
|
||||
CONTROL_AF_MODE_OFF = 0
|
||||
CONTROL_AF_MODE_AUTO = 1
|
||||
CONTROL_AF_MODE_MACRO = 2
|
||||
CONTROL_AF_MODE_CONTINUOUS_VIDEO = 3
|
||||
CONTROL_AF_MODE_CONTINUOUS_PICTURE = 4
|
||||
CONTROL_AF_MODE_EDOF = 5
|
||||
|
||||
# CONTROL_AWB modes
|
||||
CONTROL_AWB_MODE_OFF = 0
|
||||
CONTROL_AWB_MODE_AUTO = 1
|
||||
CONTROL_AWB_MODE_INCANDESCENT = 2
|
||||
CONTROL_AWB_MODE_FLUORESCENT = 3
|
||||
CONTROL_AWB_MODE_WARM_FLUORESCENT = 4
|
||||
CONTROL_AWB_MODE_DAYLIGHT = 5
|
||||
CONTROL_AWB_MODE_CLOUDY_DAYLIGHT = 6
|
||||
CONTROL_AWB_MODE_TWILIGHT = 7
|
||||
CONTROL_AWB_MODE_SHADE = 8
|
||||
|
||||
# CONTROL modes
|
||||
CONTROL_MODE_OFF = 0
|
||||
CONTROL_MODE_AUTO = 1
|
||||
CONTROL_MODE_USE_SCENE_MODE = 2
|
||||
CONTROL_MODE_OFF_KEEP_STATE = 3
|
||||
|
||||
# FLASH modes
|
||||
FLASH_MODE_OFF = 0
|
||||
FLASH_MODE_SINGLE = 1
|
||||
FLASH_MODE_TORCH = 2
|
||||
|
||||
# STATISTICS_FACE_DETECT modes
|
||||
STATISTICS_FACE_DETECT_MODE_OFF = 0
|
||||
STATISTICS_FACE_DETECT_MODE_SIMPLE = 1
|
||||
STATISTICS_FACE_DETECT_MODE_FULL = 2
|
||||
|
||||
# EDGE modes
|
||||
EDGE_MODE_OFF = 0
|
||||
EDGE_MODE_FAST = 1
|
||||
EDGE_MODE_HIGH_QUALITY = 2
|
||||
EDGE_MODE_ZERO_SHUTTER_LAG = 3
|
||||
|
||||
# NOISE_REDUCTION modes
|
||||
NOISE_REDUCTION_MODE_OFF = 0
|
||||
NOISE_REDUCTION_MODE_FAST = 1
|
||||
NOISE_REDUCTION_MODE_HIGH_QUALITY = 2
|
||||
NOISE_REDUCTION_MODE_MINIMAL = 3
|
||||
NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG = 4
|
||||
|
||||
# TONEMAP modes
|
||||
TONEMAP_MODE_CONTRAST_CURVE = 0
|
||||
TONEMAP_MODE_FAST = 1
|
||||
TONEMAP_MODE_HIGH_QUALITY = 2
|
||||
TONEMAP_MODE_GAMMA_VALUE = 3
|
||||
TONEMAP_MODE_PRESET_CURVE = 4
|
||||
|
||||
# COLOR_CORRECTION modes
|
||||
COLOR_CORRECTION_MODE_TRANSFORM_MATRIX = 0
|
||||
COLOR_CORRECTION_MODE_FAST = 1
|
||||
COLOR_CORRECTION_MODE_HIGH_QUALITY = 2
|
||||
|
||||
# COLOR_CORRECTION_ABERRATION modes
|
||||
COLOR_CORRECTION_ABERRATION_MODE_OFF = 0
|
||||
COLOR_CORRECTION_ABERRATION_MODE_FAST = 1
|
||||
COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY = 2
|
||||
|
||||
# CONTROL_AE_PRECAPTURE_TRIGGER
|
||||
CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0
|
||||
CONTROL_AE_PRECAPTURE_TRIGGER_START = 1
|
||||
CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2
|
||||
|
||||
# CONTROL_EFFECT modes
|
||||
CONTROL_EFFECT_MODE_OFF = 0
|
||||
CONTROL_EFFECT_MODE_MONO = 1
|
||||
CONTROL_EFFECT_MODE_NEGATIVE = 2
|
||||
CONTROL_EFFECT_MODE_SOLARIZE = 3
|
||||
CONTROL_EFFECT_MODE_SEPIA = 4
|
||||
CONTROL_EFFECT_MODE_POSTERIZE = 5
|
||||
CONTROL_EFFECT_MODE_WHITEBOARD = 6
|
||||
CONTROL_EFFECT_MODE_BLACKBOARD = 7
|
||||
CONTROL_EFFECT_MODE_AQUA = 8
|
||||
|
||||
# CONTROL_SCENE modes
|
||||
CONTROL_SCENE_MODE_DISABLED = 0
|
||||
CONTROL_SCENE_MODE_FACE_PRIORITY = 1
|
||||
CONTROL_SCENE_MODE_ACTION = 2
|
||||
CONTROL_SCENE_MODE_PORTRAIT = 3
|
||||
CONTROL_SCENE_MODE_LANDSCAPE = 4
|
||||
CONTROL_SCENE_MODE_NIGHT = 5
|
||||
CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6
|
||||
CONTROL_SCENE_MODE_THEATRE = 7
|
||||
CONTROL_SCENE_MODE_BEACH = 8
|
||||
CONTROL_SCENE_MODE_SNOW = 9
|
||||
CONTROL_SCENE_MODE_SUNSET = 10
|
||||
CONTROL_SCENE_MODE_STEADYPHOTO = 11
|
||||
CONTROL_SCENE_MODE_FIREWORKS = 12
|
||||
CONTROL_SCENE_MODE_SPORTS = 13
|
||||
CONTROL_SCENE_MODE_PARTY = 14
|
||||
CONTROL_SCENE_MODE_CANDLELIGHT = 15
|
||||
CONTROL_SCENE_MODE_BARCODE = 16
|
||||
CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17
|
||||
CONTROL_SCENE_MODE_HDR = 18
|
||||
|
||||
# CONTROL_VIDEO_STABILIZATION modes
|
||||
CONTROL_VIDEO_STABILIZATION_MODE_OFF = 0
|
||||
CONTROL_VIDEO_STABILIZATION_MODE_ON = 1
|
||||
|
||||
# DISTORTION_CORRECTION modes
|
||||
DISTORTION_CORRECTION_MODE_OFF = 0
|
||||
DISTORTION_CORRECTION_MODE_FAST = 1
|
||||
DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2
|
||||
|
||||
# HOT_PIXEL modes
|
||||
HOT_PIXEL_MODE_OFF = 0
|
||||
HOT_PIXEL_MODE_FAST = 1
|
||||
HOT_PIXEL_MODE_HIGH_QUALITY = 2
|
||||
|
||||
# LENS_OPTICAL_STABILIZATION modes
|
||||
LENS_OPTICAL_STABILIZATION_MODE_OFF = 0
|
||||
LENS_OPTICAL_STABILIZATION_MODE_ON = 1
|
||||
|
||||
# LENS_FACING
|
||||
LENS_FACING_FRONT = 0
|
||||
LENS_FACING_BACK = 1
|
||||
LENS_FACING_EXTERNAL = 2
|
||||
|
||||
# INFO_SUPPORTED_HARDWARE_LEVEL
|
||||
INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED = 0
|
||||
INFO_SUPPORTED_HARDWARE_LEVEL_FULL = 1
|
||||
INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY = 2
|
||||
INFO_SUPPORTED_HARDWARE_LEVEL_3 = 3
|
||||
INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL = 4
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraCharacteristics
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraCharacteristics(CameraMetadata):
|
||||
"""CameraCharacteristics — 相机特性"""
|
||||
|
||||
LENS_FACING = "android.lens.facing"
|
||||
SENSOR_ORIENTATION = "android.sensor.orientation"
|
||||
FLASH_INFO_AVAILABLE = "android.flash.info.available"
|
||||
INFO_SUPPORTED_HARDWARE_LEVEL = "android.info.supportedHardwareLevel"
|
||||
SCALER_STREAM_CONFIGURATION_MAP = "android.scaler.streamConfigurationMap"
|
||||
CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES = "android.control.aeAvailableTargetFpsRanges"
|
||||
CONTROL_AE_AVAILABLE_MODES = "android.control.aeAvailableModes"
|
||||
CONTROL_AF_AVAILABLE_MODES = "android.control.afAvailableModes"
|
||||
CONTROL_AVAILABLE_EFFECTS = "android.control.availableEffects"
|
||||
CONTROL_AVAILABLE_SCENE_MODES = "android.control.availableSceneModes"
|
||||
CONTROL_MAX_REGIONS_AE = "android.control.maxRegionsAe"
|
||||
CONTROL_MAX_REGIONS_AF = "android.control.maxRegionsAf"
|
||||
CONTROL_MAX_REGIONS_AWB = "android.control.maxRegionsAwb"
|
||||
JPEG_AVAILABLE_THUMBNAIL_SIZES = "android.jpeg.availableThumbnailSizes"
|
||||
JPEG_MAX_SIZE = "android.jpeg.maxSize"
|
||||
SENSOR_INFO_PIXEL_ARRAY_SIZE = "android.sensor.info.pixelArraySize"
|
||||
SENSOR_INFO_ACTIVE_ARRAY_SIZE = "android.sensor.info.activeArraySize"
|
||||
FLASH_MODE = "android.flash.mode"
|
||||
|
||||
def __init__(self, camera_id: str = "0"):
|
||||
super().__init__()
|
||||
self._properties: Dict[str, Any] = {
|
||||
CameraCharacteristics.LENS_FACING: CameraCharacteristics.LENS_FACING_BACK,
|
||||
CameraCharacteristics.SENSOR_ORIENTATION: 90,
|
||||
CameraCharacteristics.FLASH_INFO_AVAILABLE: True,
|
||||
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:
|
||||
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
|
||||
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP: {
|
||||
"output_sizes": [(1920, 1080), (1280, 720), (640, 480)],
|
||||
"input_sizes": [(1920, 1080)],
|
||||
},
|
||||
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES: [(15, 30), (30, 30)],
|
||||
CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES: [0, 1, 2, 3, 4],
|
||||
CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES: [0, 1, 2, 3, 4, 5],
|
||||
CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS: list(range(9)),
|
||||
CameraCharacteristics.CONTROL_AVAILABLE_SCENE_MODES: list(range(19)),
|
||||
CameraCharacteristics.CONTROL_MAX_REGIONS_AE: 1,
|
||||
CameraCharacteristics.CONTROL_MAX_REGIONS_AF: 1,
|
||||
CameraCharacteristics.CONTROL_MAX_REGIONS_AWB: 0,
|
||||
CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES: [(320, 240), (160, 120)],
|
||||
CameraCharacteristics.JPEG_MAX_SIZE: 5000000,
|
||||
CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE: (4032, 3024),
|
||||
CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE: (4032, 3024),
|
||||
}
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
return self._properties.get(key)
|
||||
|
||||
def keys(self) -> Set[str]:
|
||||
return set(self._properties.keys())
|
||||
|
||||
def query(self, key: str) -> Any:
|
||||
return self._properties.get(key)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraId
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraId(Stub):
|
||||
"""CameraId — 相机ID封装"""
|
||||
|
||||
def __init__(self, id_string: str):
|
||||
super().__init__()
|
||||
self._id = id_string
|
||||
|
||||
def getId(self) -> str:
|
||||
return self._id
|
||||
|
||||
def toString(self) -> str:
|
||||
return self._id
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if isinstance(other, CameraId):
|
||||
return self._id == other._id
|
||||
if isinstance(other, str):
|
||||
return self._id == other
|
||||
return False
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._id)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraCharacteristicsKey
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraCharacteristicsKey(Stub):
|
||||
"""CameraCharacteristicsKey — 相机特性键"""
|
||||
|
||||
def __init__(self, name: str, native_type: type = object):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._native_type = native_type
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getNativeType(self) -> type:
|
||||
return self._native_type
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if isinstance(other, CameraCharacteristicsKey):
|
||||
return self._name == other._name
|
||||
return False
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._name)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CameraCharacteristicsKey({self._name})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CaptureRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CaptureRequest(CameraMetadata):
|
||||
"""CaptureRequest — 捕获请求"""
|
||||
|
||||
CONTROL_AE_MODE = "android.control.aeMode"
|
||||
CONTROL_AF_MODE = "android.control.afMode"
|
||||
CONTROL_AWB_MODE = "android.control.awbMode"
|
||||
CONTROL_MODE = "android.control.mode"
|
||||
FLASH_MODE = "android.flash.mode"
|
||||
SENSOR_EXPOSURE_TIME = "android.sensor.exposureTime"
|
||||
SENSOR_SENSITIVITY = "android.sensor.sensitivity"
|
||||
LENS_FOCUS_DISTANCE = "android.lens.focusDistance"
|
||||
STATISTICS_FACE_DETECT_MODE = "android.statistics.faceDetectMode"
|
||||
JPEG_QUALITY = "android.jpeg.quality"
|
||||
JPEG_ORIENTATION = "android.jpeg.orientation"
|
||||
JPEG_THUMBNAIL_QUALITY = "android.jpeg.thumbnailQuality"
|
||||
JPEG_THUMBNAIL_SIZE = "android.jpeg.thumbnailSize"
|
||||
SCALER_CROP_REGION = "android.scaler.cropRegion"
|
||||
EDGE_MODE = "android.edge.mode"
|
||||
NOISE_REDUCTION_MODE = "android.noiseReduction.mode"
|
||||
TONEMAP_MODE = "android.tonemap.mode"
|
||||
BLACK_LEVEL_LOCK = "android.blackLevel.lock"
|
||||
CONTROL_AE_TARGET_FPS_RANGE = "android.control.aeTargetFpsRange"
|
||||
CONTROL_AE_EXPOSURE_COMPENSATION = "android.control.aeExposureCompensation"
|
||||
CONTROL_AE_LOCK = "android.control.aeLock"
|
||||
CONTROL_AE_PRECAPTURE_TRIGGER = "android.control.aePrecaptureTrigger"
|
||||
CONTROL_AWB_LOCK = "android.control.awbLock"
|
||||
CONTROL_EFFECT_MODE = "android.control.effectMode"
|
||||
CONTROL_SCENE_MODE = "android.control.sceneMode"
|
||||
CONTROL_VIDEO_STABILIZATION_MODE = "android.control.videoStabilizationMode"
|
||||
COLOR_CORRECTION_MODE = "android.colorCorrection.mode"
|
||||
COLOR_CORRECTION_GAINS = "android.colorCorrection.gains"
|
||||
COLOR_CORRECTION_TRANSFORM = "android.colorCorrection.transform"
|
||||
COLOR_CORRECTION_ABERRATION_MODE = "android.colorCorrection.aberrationMode"
|
||||
CONTROL_POST_RAW_SENSITIVITY_BOOST = "android.control.postRawSensitivityBoost"
|
||||
DISTORTION_CORRECTION_MODE = "android.distortionCorrection.mode"
|
||||
HOT_PIXEL_MODE = "android.hotPixel.mode"
|
||||
LENS_OPTICAL_STABILIZATION_MODE = "android.lens.opticalStabilizationMode"
|
||||
REQUEST_PIPELINE_MAX_DEPTH = "android.request.pipelineMaxDepth"
|
||||
SENSOR_TIMESTAMP = "android.sensor.timestamp"
|
||||
STATISTICS_LENS_SHADING_MAP_MODE = "android.statistics.lensShadingMapMode"
|
||||
STATISTICS_OIS_DATA_MODE = "android.statistics.oisDataMode"
|
||||
STATISTICS_SECTION = "android.statistics.section"
|
||||
TONEMAP_CURVE = "android.tonemap.curve"
|
||||
TONEMAP_GAMMA = "android.tonemap.gamma"
|
||||
|
||||
class Builder(Stub):
|
||||
"""CaptureRequest.Builder — 捕获请求构建器"""
|
||||
|
||||
def __init__(self, camera_device: "CameraDevice"):
|
||||
super().__init__()
|
||||
self._parameters: Dict[str, Any] = {}
|
||||
self._tag = None
|
||||
self._camera_device = camera_device
|
||||
|
||||
def set(self, key: str, value: Any):
|
||||
self._parameters[key] = value
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
return self._parameters.get(key)
|
||||
|
||||
def setTag(self, tag: Any):
|
||||
self._tag = tag
|
||||
|
||||
def build(self) -> "CaptureRequest":
|
||||
req = CaptureRequest()
|
||||
req._parameters = dict(self._parameters)
|
||||
req._tag = self._tag
|
||||
return req
|
||||
|
||||
def addTarget(self, surface):
|
||||
pass
|
||||
|
||||
def removeTarget(self, surface):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._parameters: Dict[str, Any] = {
|
||||
CaptureRequest.CONTROL_AE_MODE: CameraMetadata.CONTROL_AE_MODE_ON,
|
||||
CaptureRequest.CONTROL_AF_MODE: CameraMetadata.CONTROL_AF_MODE_AUTO,
|
||||
CaptureRequest.CONTROL_AWB_MODE: CameraMetadata.CONTROL_AWB_MODE_AUTO,
|
||||
CaptureRequest.CONTROL_MODE: CameraMetadata.CONTROL_MODE_AUTO,
|
||||
CaptureRequest.FLASH_MODE: CameraMetadata.FLASH_MODE_OFF,
|
||||
CaptureRequest.JPEG_QUALITY: 95,
|
||||
CaptureRequest.JPEG_ORIENTATION: 0,
|
||||
}
|
||||
self._tag = None
|
||||
self._targets: List[Any] = []
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
return self._parameters.get(key)
|
||||
|
||||
def keys(self) -> Set[str]:
|
||||
return set(self._parameters.keys())
|
||||
|
||||
def getTag(self) -> Any:
|
||||
return self._tag
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CaptureResult
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CaptureResult(CameraMetadata):
|
||||
"""CaptureResult — 捕获结果"""
|
||||
|
||||
# Same keys as CaptureRequest
|
||||
CONTROL_AE_MODE = CaptureRequest.CONTROL_AE_MODE
|
||||
CONTROL_AF_MODE = CaptureRequest.CONTROL_AF_MODE
|
||||
CONTROL_AWB_MODE = CaptureRequest.CONTROL_AWB_MODE
|
||||
CONTROL_MODE = CaptureRequest.CONTROL_MODE
|
||||
FLASH_MODE = CaptureRequest.FLASH_MODE
|
||||
SENSOR_EXPOSURE_TIME = CaptureRequest.SENSOR_EXPOSURE_TIME
|
||||
SENSOR_SENSITIVITY = CaptureRequest.SENSOR_SENSITIVITY
|
||||
LENS_FOCUS_DISTANCE = CaptureRequest.LENS_FOCUS_DISTANCE
|
||||
STATISTICS_FACE_DETECT_MODE = CaptureRequest.STATISTICS_FACE_DETECT_MODE
|
||||
JPEG_QUALITY = CaptureRequest.JPEG_QUALITY
|
||||
JPEG_ORIENTATION = CaptureRequest.JPEG_ORIENTATION
|
||||
JPEG_THUMBNAIL_QUALITY = CaptureRequest.JPEG_THUMBNAIL_QUALITY
|
||||
JPEG_THUMBNAIL_SIZE = CaptureRequest.JPEG_THUMBNAIL_SIZE
|
||||
SCALER_CROP_REGION = CaptureRequest.SCALER_CROP_REGION
|
||||
EDGE_MODE = CaptureRequest.EDGE_MODE
|
||||
NOISE_REDUCTION_MODE = CaptureRequest.NOISE_REDUCTION_MODE
|
||||
TONEMAP_MODE = CaptureRequest.TONEMAP_MODE
|
||||
BLACK_LEVEL_LOCK = CaptureRequest.BLACK_LEVEL_LOCK
|
||||
CONTROL_AE_TARGET_FPS_RANGE = CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE
|
||||
CONTROL_AE_EXPOSURE_COMPENSATION = CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
|
||||
CONTROL_AE_LOCK = CaptureRequest.CONTROL_AE_LOCK
|
||||
CONTROL_AE_PRECAPTURE_TRIGGER = CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER
|
||||
CONTROL_AWB_LOCK = CaptureRequest.CONTROL_AWB_LOCK
|
||||
CONTROL_EFFECT_MODE = CaptureRequest.CONTROL_EFFECT_MODE
|
||||
CONTROL_SCENE_MODE = CaptureRequest.CONTROL_SCENE_MODE
|
||||
CONTROL_VIDEO_STABILIZATION_MODE = CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE
|
||||
COLOR_CORRECTION_MODE = CaptureRequest.COLOR_CORRECTION_MODE
|
||||
COLOR_CORRECTION_GAINS = CaptureRequest.COLOR_CORRECTION_GAINS
|
||||
COLOR_CORRECTION_TRANSFORM = CaptureRequest.COLOR_CORRECTION_TRANSFORM
|
||||
COLOR_CORRECTION_ABERRATION_MODE = CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE
|
||||
CONTROL_POST_RAW_SENSITIVITY_BOOST = CaptureRequest.CONTROL_POST_RAW_SENSITIVITY_BOOST
|
||||
DISTORTION_CORRECTION_MODE = CaptureRequest.DISTORTION_CORRECTION_MODE
|
||||
HOT_PIXEL_MODE = CaptureRequest.HOT_PIXEL_MODE
|
||||
LENS_OPTICAL_STABILIZATION_MODE = CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE
|
||||
REQUEST_PIPELINE_MAX_DEPTH = CaptureRequest.REQUEST_PIPELINE_MAX_DEPTH
|
||||
SENSOR_TIMESTAMP = CaptureRequest.SENSOR_TIMESTAMP
|
||||
STATISTICS_LENS_SHADING_MAP_MODE = CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE
|
||||
STATISTICS_OIS_DATA_MODE = CaptureRequest.STATISTICS_OIS_DATA_MODE
|
||||
STATISTICS_SECTION = CaptureRequest.STATISTICS_SECTION
|
||||
TONEMAP_CURVE = CaptureRequest.TONEMAP_CURVE
|
||||
TONEMAP_GAMMA = CaptureRequest.TONEMAP_GAMMA
|
||||
|
||||
def __init__(self, request: CaptureRequest = None):
|
||||
super().__init__()
|
||||
self._results: Dict[str, Any] = {
|
||||
CaptureResult.CONTROL_AE_MODE: CameraMetadata.CONTROL_AE_MODE_ON,
|
||||
CaptureResult.CONTROL_AF_MODE: CameraMetadata.CONTROL_AF_MODE_AUTO,
|
||||
CaptureResult.CONTROL_AWB_MODE: CameraMetadata.CONTROL_AWB_MODE_AUTO,
|
||||
CaptureResult.CONTROL_MODE: CameraMetadata.CONTROL_MODE_AUTO,
|
||||
CaptureResult.FLASH_MODE: CameraMetadata.FLASH_MODE_OFF,
|
||||
CaptureResult.SENSOR_EXPOSURE_TIME: 33333333,
|
||||
CaptureResult.SENSOR_SENSITIVITY: 100,
|
||||
CaptureResult.SENSOR_TIMESTAMP: int(time.time() * 1e9),
|
||||
}
|
||||
self._request = request
|
||||
self._sequence_id = -1
|
||||
self._frame_number = -1
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
return self._results.get(key)
|
||||
|
||||
def keys(self) -> Set[str]:
|
||||
return set(self._results.keys())
|
||||
|
||||
def getRequest(self) -> Optional[CaptureRequest]:
|
||||
return self._request
|
||||
|
||||
def getSequenceId(self) -> int:
|
||||
return self._sequence_id
|
||||
|
||||
def getFrameNumber(self) -> int:
|
||||
return self._frame_number
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TotalCaptureResult
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TotalCaptureResult(CaptureResult):
|
||||
"""TotalCaptureResult — 完整捕获结果"""
|
||||
|
||||
def __init__(self, request: CaptureRequest = None,
|
||||
partial_results: List[CaptureResult] = None):
|
||||
super().__init__(request)
|
||||
self._partial_results = partial_results or []
|
||||
|
||||
def getPartialResults(self) -> List[CaptureResult]:
|
||||
return list(self._partial_results)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraCaptureSession
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraCaptureSession(Stub):
|
||||
"""CameraCaptureSession — 捕获会话"""
|
||||
|
||||
class StateCallback(Stub):
|
||||
"""CameraCaptureSession.StateCallback — 会话状态回调"""
|
||||
|
||||
def onConfigured(self, session: "CameraCaptureSession"):
|
||||
pass
|
||||
|
||||
def onConfigureFailed(self, session: "CameraCaptureSession"):
|
||||
pass
|
||||
|
||||
def onReady(self, session: "CameraCaptureSession"):
|
||||
pass
|
||||
|
||||
def onActive(self, session: "CameraCaptureSession"):
|
||||
pass
|
||||
|
||||
def onClosed(self, session: "CameraCaptureSession"):
|
||||
pass
|
||||
|
||||
def onSurfacePrepared(self, session: "CameraCaptureSession", surface):
|
||||
pass
|
||||
|
||||
class CaptureCallback(Stub):
|
||||
"""CameraCaptureSession.CaptureCallback — 捕获回调"""
|
||||
|
||||
def onCaptureStarted(self, session: "CameraCaptureSession", request: CaptureRequest,
|
||||
timestamp: int, frameNumber: int):
|
||||
pass
|
||||
|
||||
def onCaptureCompleted(self, session: "CameraCaptureSession", request: CaptureRequest,
|
||||
result: TotalCaptureResult):
|
||||
pass
|
||||
|
||||
def onCaptureFailed(self, session: "CameraCaptureSession", request: CaptureRequest,
|
||||
failure):
|
||||
pass
|
||||
|
||||
def onCaptureProgressed(self, session: "CameraCaptureSession", request: CaptureRequest,
|
||||
result: CaptureResult):
|
||||
pass
|
||||
|
||||
def onCaptureSequenceCompleted(self, session: "CameraCaptureSession",
|
||||
sequenceId: int, frameNumber: int):
|
||||
pass
|
||||
|
||||
def onCaptureSequenceAborted(self, session: "CameraCaptureSession", sequenceId: int):
|
||||
pass
|
||||
|
||||
def onCaptureBufferLost(self, session: "CameraCaptureSession", request: CaptureRequest,
|
||||
target, frameNumber: int):
|
||||
pass
|
||||
|
||||
def __init__(self, device: "CameraDevice"):
|
||||
super().__init__()
|
||||
self._device = device
|
||||
self._closed = False
|
||||
self._repeating_request = None
|
||||
self._state_callback = None
|
||||
self._session_id = int(time.time() * 1000)
|
||||
|
||||
def capture(self, request: CaptureRequest, callback: CaptureCallback = None,
|
||||
handler=None) -> int:
|
||||
seq_id = int(time.time() * 1000)
|
||||
result = TotalCaptureResult(request)
|
||||
if callback:
|
||||
callback.onCaptureStarted(self, request, int(time.time() * 1e9), 0)
|
||||
callback.onCaptureCompleted(self, request, result)
|
||||
callback.onCaptureSequenceCompleted(self, seq_id, 0)
|
||||
return seq_id
|
||||
|
||||
def setRepeatingRequest(self, request: CaptureRequest, callback: CaptureCallback = None,
|
||||
handler=None) -> int:
|
||||
self._repeating_request = request
|
||||
seq_id = int(time.time() * 1000)
|
||||
result = TotalCaptureResult(request)
|
||||
if callback:
|
||||
callback.onCaptureStarted(self, request, int(time.time() * 1e9), 0)
|
||||
callback.onCaptureCompleted(self, request, result)
|
||||
return seq_id
|
||||
|
||||
def stopRepeatingRequest(self) -> None:
|
||||
self._repeating_request = None
|
||||
|
||||
def abortCaptures(self) -> None:
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
def getDevice(self) -> "CameraDevice":
|
||||
return self._device
|
||||
|
||||
def isClosed(self) -> bool:
|
||||
return self._closed
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraDevice
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraDevice(Stub):
|
||||
"""CameraDevice — 相机设备"""
|
||||
|
||||
STATE_OPENED = 0
|
||||
STATE_CLOSED = 3
|
||||
STATE_DISCONNECTED = 1
|
||||
STATE_ERROR = 2
|
||||
|
||||
class StateCallback(Stub):
|
||||
"""CameraDevice.StateCallback — 设备状态回调"""
|
||||
|
||||
def onOpened(self, device: "CameraDevice"):
|
||||
pass
|
||||
|
||||
def onClosed(self, device: "CameraDevice"):
|
||||
pass
|
||||
|
||||
def onDisconnected(self, device: "CameraDevice"):
|
||||
pass
|
||||
|
||||
def onError(self, device: "CameraDevice", error: int):
|
||||
pass
|
||||
|
||||
def __init__(self, camera_id: str = "0"):
|
||||
super().__init__()
|
||||
self._id = camera_id
|
||||
self._state = CameraDevice.STATE_OPENED
|
||||
self._state_callback = None
|
||||
self._sessions: List[CameraCaptureSession] = []
|
||||
|
||||
def getId(self) -> str:
|
||||
return self._id
|
||||
|
||||
def createCaptureRequest(self, templateType: int) -> CaptureRequest.Builder:
|
||||
return CaptureRequest.Builder(self)
|
||||
|
||||
def createCaptureSession(self, outputs: List[Any],
|
||||
callback: CameraCaptureSession.StateCallback,
|
||||
handler=None):
|
||||
session = CameraCaptureSession(self)
|
||||
session._state_callback = callback
|
||||
self._sessions.append(session)
|
||||
if callback:
|
||||
callback.onConfigured(session)
|
||||
return session
|
||||
|
||||
def close(self):
|
||||
self._state = CameraDevice.STATE_CLOSED
|
||||
for session in self._sessions:
|
||||
session.close()
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CameraDevice@{self._id}"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraManager(Stub):
|
||||
"""CameraManager — 相机管理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._cameras: Dict[str, CameraCharacteristics] = {
|
||||
"0": CameraCharacteristics("0"),
|
||||
}
|
||||
self._open_devices: Dict[str, CameraDevice] = {}
|
||||
self._availability_callbacks: List[Any] = []
|
||||
self._torch_callbacks: List[Any] = []
|
||||
|
||||
def getCameraIdList(self) -> List[str]:
|
||||
return list(self._cameras.keys())
|
||||
|
||||
def getCameraCharacteristics(self, cameraId: str) -> CameraCharacteristics:
|
||||
if cameraId not in self._cameras:
|
||||
raise AndroidOnlyError(f"Camera {cameraId} not available")
|
||||
return self._cameras[cameraId]
|
||||
|
||||
def openCamera(self, cameraId: str, callback: CameraDevice.StateCallback,
|
||||
handler=None):
|
||||
if cameraId not in self._cameras:
|
||||
if callback:
|
||||
callback.onError(None, CameraDevice.STATE_ERROR)
|
||||
return
|
||||
device = CameraDevice(cameraId)
|
||||
self._open_devices[cameraId] = device
|
||||
if callback:
|
||||
callback.onOpened(device)
|
||||
|
||||
def registerAvailabilityCallback(self, callback):
|
||||
self._availability_callbacks.append(callback)
|
||||
|
||||
def unregisterAvailabilityCallback(self, callback):
|
||||
if callback in self._availability_callbacks:
|
||||
self._availability_callbacks.remove(callback)
|
||||
|
||||
def registerTorchCallback(self, callback, handler=None):
|
||||
self._torch_callbacks.append(callback)
|
||||
|
||||
def unregisterTorchCallback(self, callback):
|
||||
if callback in self._torch_callbacks:
|
||||
self._torch_callbacks.remove(callback)
|
||||
|
||||
def setTorchMode(self, cameraId: str, enabled: bool):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraOfflineSession
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraOfflineSession(CameraCaptureSession):
|
||||
"""CameraOfflineSession — 离线会话 (API 30+)"""
|
||||
|
||||
def __init__(self, device: CameraDevice):
|
||||
super().__init__(device)
|
||||
|
||||
def switchToOffline(self):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CameraExtensionSession
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CameraExtensionSession(Stub):
|
||||
"""CameraExtensionSession — 扩展会话 (API 31+)"""
|
||||
|
||||
class StateCallback(Stub):
|
||||
def onConfigured(self, session: "CameraExtensionSession"):
|
||||
pass
|
||||
|
||||
def onConfigureFailed(self, session: "CameraExtensionSession"):
|
||||
pass
|
||||
|
||||
def onClosed(self, session: "CameraExtensionSession"):
|
||||
pass
|
||||
|
||||
class ExtensionCaptureCallback(Stub):
|
||||
def onCaptureStarted(self, session: "CameraExtensionSession", request: CaptureRequest,
|
||||
timestamp: int):
|
||||
pass
|
||||
|
||||
def onCaptureProcessStarted(self, session: "CameraExtensionSession", request: CaptureRequest):
|
||||
pass
|
||||
|
||||
def onCaptureFailed(self, session: "CameraExtensionSession", request: CaptureRequest):
|
||||
pass
|
||||
|
||||
def onCaptureResultAvailable(self, session: "CameraExtensionSession",
|
||||
request: CaptureRequest, result: TotalCaptureResult):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._closed = False
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"CameraManager", "CameraDevice", "CameraCaptureSession",
|
||||
"CaptureRequest", "CaptureResult", "CameraCharacteristics",
|
||||
"CameraMetadata", "CameraId", "CameraCharacteristicsKey",
|
||||
"TotalCaptureResult", "CameraOfflineSession", "CameraExtensionSession",
|
||||
]
|
||||
@@ -0,0 +1,804 @@
|
||||
"""
|
||||
Android InputMethod 组件 — InputMethodService, Keyboard, KeyboardView, Key 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, android_context
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputMethod
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputMethod(Stub):
|
||||
"""InputMethod — 输入法接口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def createSession(self, callback: Any) -> Optional["InputMethodSession"]:
|
||||
"""创建输入法会话"""
|
||||
return None
|
||||
|
||||
def startInput(self, input_connection: Any, editor_info: Any) -> None:
|
||||
"""开始输入"""
|
||||
pass
|
||||
|
||||
def restartInput(self, input_connection: Any, editor_info: Any) -> None:
|
||||
"""重启输入"""
|
||||
pass
|
||||
|
||||
def dispatchKeyEvent(self, event: Any) -> None:
|
||||
"""分发按键事件"""
|
||||
pass
|
||||
|
||||
def dispatchTrackballEvent(self, event: Any) -> None:
|
||||
"""分发轨迹球事件"""
|
||||
pass
|
||||
|
||||
def dispatchGenericMotionEvent(self, event: Any) -> None:
|
||||
"""分发通用运动事件"""
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputMethodSession
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputMethodSession(Stub):
|
||||
"""InputMethodSession — 输入法会话接口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def finishInput(self):
|
||||
"""结束输入"""
|
||||
pass
|
||||
|
||||
def updateSelection(self, old_sel_start: int, old_sel_end: int,
|
||||
new_sel_start: int, new_sel_end: int,
|
||||
candidates_start: int, candidates_end: int):
|
||||
"""更新选区"""
|
||||
pass
|
||||
|
||||
def updateCursor(self, old_cursor: Any, new_cursor: Any):
|
||||
"""更新光标"""
|
||||
pass
|
||||
|
||||
def displayCompletions(self, completions: List["CompletionInfo"]):
|
||||
"""显示补全"""
|
||||
pass
|
||||
|
||||
def appPrivateCommand(self, action: str, data: Any):
|
||||
"""应用私有命令"""
|
||||
pass
|
||||
|
||||
def viewClicked(self, focus_changed: bool):
|
||||
"""视图点击"""
|
||||
pass
|
||||
|
||||
def updateExtractedText(self, token: int, extracted_text: Any):
|
||||
"""更新提取文本"""
|
||||
pass
|
||||
|
||||
def dispatchKeyEvent(self, key_code: int, event: Any) -> None:
|
||||
"""分发按键事件"""
|
||||
pass
|
||||
|
||||
def dispatchTrackballEvent(self, event: Any) -> None:
|
||||
pass
|
||||
|
||||
def dispatchGenericMotionEvent(self, event: Any) -> None:
|
||||
pass
|
||||
|
||||
def toggleSoftInput(self, show_flags: int, hide_flags: int):
|
||||
"""切换软键盘"""
|
||||
pass
|
||||
|
||||
def notifyImeHidden(self):
|
||||
"""通知输入法隐藏"""
|
||||
pass
|
||||
|
||||
def removeImeSurface(self):
|
||||
"""移除输入法界面"""
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AbstractInputMethodService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AbstractInputMethodService(Stub):
|
||||
"""AbstractInputMethodService — 抽象输入法服务基类"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._input_method: Optional[InputMethod] = None
|
||||
self._input_method_session: Optional[InputMethodSession] = None
|
||||
|
||||
def onCreate(self):
|
||||
"""服务创建"""
|
||||
pass
|
||||
|
||||
def onDestroy(self):
|
||||
"""服务销毁"""
|
||||
pass
|
||||
|
||||
def onBind(self, intent: Any) -> Any:
|
||||
"""绑定"""
|
||||
return None
|
||||
|
||||
def onUnbind(self, intent: Any) -> bool:
|
||||
"""解绑"""
|
||||
return False
|
||||
|
||||
def onCreateInputMethod(self) -> InputMethod:
|
||||
"""创建输入法实例"""
|
||||
return InputMethod()
|
||||
|
||||
def onCreateInputMethodSession(self) -> InputMethodSession:
|
||||
"""创建输入法会话"""
|
||||
return InputMethodSession()
|
||||
|
||||
def getKeyDispatcherState(self) -> Any:
|
||||
"""获取按键调度状态"""
|
||||
return None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputMethodImpl
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputMethodImpl(InputMethod):
|
||||
"""InputMethodImpl — 输入法实现"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._session: Optional[InputMethodSession] = None
|
||||
|
||||
def createSession(self, callback: Any) -> Optional["InputMethodSession"]:
|
||||
self._session = InputMethodSessionImpl()
|
||||
return self._session
|
||||
|
||||
def startInput(self, input_connection: Any, editor_info: Any):
|
||||
sys.stderr.write("[InputMethodImpl] startInput\n")
|
||||
|
||||
def restartInput(self, input_connection: Any, editor_info: Any):
|
||||
sys.stderr.write("[InputMethodImpl] restartInput\n")
|
||||
|
||||
def dispatchKeyEvent(self, event: Any):
|
||||
sys.stderr.write("[InputMethodImpl] dispatchKeyEvent\n")
|
||||
|
||||
def dispatchTrackballEvent(self, event: Any):
|
||||
pass
|
||||
|
||||
def dispatchGenericMotionEvent(self, event: Any):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputMethodSessionImpl
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputMethodSessionImpl(InputMethodSession):
|
||||
"""InputMethodSessionImpl — 输入法会话实现"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def finishInput(self):
|
||||
sys.stderr.write("[InputMethodSessionImpl] finishInput\n")
|
||||
|
||||
def updateSelection(self, old_sel_start: int, old_sel_end: int,
|
||||
new_sel_start: int, new_sel_end: int,
|
||||
candidates_start: int, candidates_end: int):
|
||||
pass
|
||||
|
||||
def displayCompletions(self, completions: List["CompletionInfo"]):
|
||||
pass
|
||||
|
||||
def dispatchKeyEvent(self, key_code: int, event: Any):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputMethodService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputMethodService(AbstractInputMethodService):
|
||||
"""InputMethodService — 输入法服务"""
|
||||
|
||||
SHOW_EXPLICIT = 0x0001
|
||||
SHOW_IMPLICIT = 0x0002
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._input_view: Optional[Any] = None
|
||||
self._candidates_view: Optional[Any] = None
|
||||
self._input_connection: Optional[Any] = None
|
||||
self._input_editor_info: Optional["EditorInfo"] = None
|
||||
self._input_binding: Optional["InputBinding"] = None
|
||||
self._input_view_shown: bool = False
|
||||
self._fullscreen_mode: bool = False
|
||||
self._extract_to_fullscreen_mode: bool = False
|
||||
self._is_fullscreen: bool = False
|
||||
self._keyboard: Optional["Keyboard"] = None
|
||||
self._settings_observer: Any = None
|
||||
self._candidates_view_shown: bool = False
|
||||
self._show_input_requested: bool = False
|
||||
self._token: Any = None
|
||||
self._window_visible: bool = False
|
||||
self._input_started: bool = False
|
||||
|
||||
def onCreate(self):
|
||||
"""创建服务"""
|
||||
sys.stderr.write("[InputMethodService] onCreate\n")
|
||||
|
||||
def onCreateInputView(self) -> Optional[Any]:
|
||||
"""创建输入视图"""
|
||||
return None
|
||||
|
||||
def onCreateCandidatesView(self) -> Optional[Any]:
|
||||
"""创建候选视图"""
|
||||
return None
|
||||
|
||||
def onStartInputView(self, info: "EditorInfo", restarting: bool):
|
||||
"""开始输入视图"""
|
||||
pass
|
||||
|
||||
def onFinishInputView(self, finishing_input: bool):
|
||||
"""结束输入视图"""
|
||||
pass
|
||||
|
||||
def onStartInput(self, attribute: "EditorInfo", restarting: bool):
|
||||
"""开始输入"""
|
||||
self._input_editor_info = attribute
|
||||
self._input_started = True
|
||||
|
||||
def onFinishInput(self):
|
||||
"""结束输入"""
|
||||
self._input_started = False
|
||||
|
||||
def onBindInput(self, binding: "InputBinding"):
|
||||
"""绑定输入"""
|
||||
self._input_binding = binding
|
||||
|
||||
def onUnbindInput(self) -> bool:
|
||||
"""解绑输入"""
|
||||
self._input_binding = None
|
||||
return False
|
||||
|
||||
def onDestroy(self):
|
||||
"""销毁"""
|
||||
self._input_view = None
|
||||
self._candidates_view = None
|
||||
self._input_connection = None
|
||||
|
||||
def showWindow(self, show_flags: int):
|
||||
"""显示窗口"""
|
||||
self._window_visible = True
|
||||
sys.stderr.write(f"[InputMethodService] showWindow: flags={show_flags}\n")
|
||||
|
||||
def hideWindow(self):
|
||||
"""隐藏窗口"""
|
||||
self._window_visible = False
|
||||
|
||||
def setInputView(self, view: Any):
|
||||
"""设置输入视图"""
|
||||
self._input_view = view
|
||||
|
||||
def setCandidatesView(self, view: Any):
|
||||
"""设置候选视图"""
|
||||
self._candidates_view = view
|
||||
|
||||
def updateInputViewShown(self):
|
||||
"""更新输入视图显示状态"""
|
||||
pass
|
||||
|
||||
def updateFullscreenMode(self):
|
||||
"""更新全屏模式"""
|
||||
pass
|
||||
|
||||
def updateExtractToFullscreenMode(self):
|
||||
"""更新提取全屏模式"""
|
||||
pass
|
||||
|
||||
def requestHideSelf(self, flags: int):
|
||||
"""请求隐藏自身"""
|
||||
sys.stderr.write(f"[InputMethodService] requestHideSelf: flags={flags}\n")
|
||||
|
||||
def switchInputMethod(self, id: str):
|
||||
"""切换输入法"""
|
||||
sys.stderr.write(f"[InputMethodService] switchInputMethod: id={id}\n")
|
||||
|
||||
def sendDefaultEditorAction(self, from_enter_key: bool):
|
||||
"""发送默认编辑器动作"""
|
||||
pass
|
||||
|
||||
def sendDownUpKeyEvents(self, key_event_code: int):
|
||||
"""发送按键按下和释放事件"""
|
||||
pass
|
||||
|
||||
def getCurrentInputBinding(self) -> Optional["InputBinding"]:
|
||||
"""获取当前输入绑定"""
|
||||
return self._input_binding
|
||||
|
||||
def getCurrentInputEditorInfo(self) -> Optional["EditorInfo"]:
|
||||
"""获取当前编辑器信息"""
|
||||
return self._input_editor_info
|
||||
|
||||
def getCurrentInputConnection(self) -> Optional["InputConnection"]:
|
||||
"""获取当前输入连接"""
|
||||
return self._input_connection
|
||||
|
||||
def getTextForImeAction(self, ime_options: int) -> str:
|
||||
"""获取IME动作的文本"""
|
||||
action_map = {
|
||||
0x0001: "None",
|
||||
0x0002: "Go",
|
||||
0x0003: "Search",
|
||||
0x0004: "Send",
|
||||
0x0005: "Next",
|
||||
0x0006: "Done",
|
||||
0x0007: "Previous",
|
||||
}
|
||||
action = ime_options & 0x000000FF
|
||||
return action_map.get(action, "Unknown")
|
||||
|
||||
def doStartInput(self, input_connection: Any, editor_info: "EditorInfo", restarting: bool):
|
||||
"""执行启动输入"""
|
||||
self._input_connection = input_connection
|
||||
self._input_editor_info = editor_info
|
||||
self.onStartInput(editor_info, restarting)
|
||||
if self._window_visible:
|
||||
self.onStartInputView(editor_info, restarting)
|
||||
|
||||
def doFinishInput(self):
|
||||
"""执行结束输入"""
|
||||
self.onFinishInput()
|
||||
self.onFinishInputView(True)
|
||||
|
||||
def onEvaluateInputViewShown(self) -> bool:
|
||||
"""评估是否显示输入视图"""
|
||||
return self._input_view is not None
|
||||
|
||||
def onEvaluateFullscreenMode(self) -> bool:
|
||||
"""评估是否全屏模式"""
|
||||
return self._fullscreen_mode
|
||||
|
||||
def onConfigureWindow(self, win: Any, is_fullscreen: bool, is_candidates: bool):
|
||||
"""配置窗口"""
|
||||
pass
|
||||
|
||||
def isInputViewShown(self) -> bool:
|
||||
"""输入视图是否已显示"""
|
||||
return self._input_view_shown
|
||||
|
||||
def updateWindow(self, show_input: bool = False):
|
||||
"""更新窗口"""
|
||||
pass
|
||||
|
||||
def setExtractView(self, view: Any):
|
||||
"""设置提取视图"""
|
||||
pass
|
||||
|
||||
def getExtractView(self) -> Optional[Any]:
|
||||
"""获取提取视图"""
|
||||
return None
|
||||
|
||||
def getInputView(self) -> Optional[Any]:
|
||||
"""获取输入视图"""
|
||||
return self._input_view
|
||||
|
||||
def getCandidatesView(self) -> Optional[Any]:
|
||||
"""获取候选视图"""
|
||||
return self._candidates_view
|
||||
|
||||
def isFullscreenMode(self) -> bool:
|
||||
"""是否全屏模式"""
|
||||
return self._is_fullscreen
|
||||
|
||||
def onDisplayCompletions(self, completions: List["CompletionInfo"]):
|
||||
"""显示补全"""
|
||||
pass
|
||||
|
||||
def onUpdateSelection(self, old_sel_start: int, old_sel_end: int,
|
||||
new_sel_start: int, new_sel_end: int,
|
||||
candidates_start: int, candidates_end: int):
|
||||
"""更新选区"""
|
||||
pass
|
||||
|
||||
def onViewClicked(self, focus_changed: bool):
|
||||
"""视图点击"""
|
||||
pass
|
||||
|
||||
def onWindowShown(self):
|
||||
"""窗口显示"""
|
||||
self._window_visible = True
|
||||
|
||||
def onWindowHidden(self):
|
||||
"""窗口隐藏"""
|
||||
self._window_visible = False
|
||||
|
||||
def setKeyboard(self, keyboard: "Keyboard"):
|
||||
"""设置键盘"""
|
||||
self._keyboard = keyboard
|
||||
|
||||
def getKeyboard(self) -> Optional["Keyboard"]:
|
||||
"""获取键盘"""
|
||||
return self._keyboard
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Key
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Key(Stub):
|
||||
"""Key — 键盘按键"""
|
||||
|
||||
def __init__(self, row: "KeyboardRow" = None):
|
||||
super().__init__()
|
||||
self.codes: List[int] = []
|
||||
self.label: Optional[str] = None
|
||||
self.icon: Any = None
|
||||
self.sticky: bool = False
|
||||
self.modifier: bool = False
|
||||
self.repeatable: bool = False
|
||||
self.edgeFlags: int = 0
|
||||
self.popupCharacters: Optional[str] = None
|
||||
self.popupResId: int = 0
|
||||
self._x: int = 0
|
||||
self._y: int = 0
|
||||
self._width: int = 0
|
||||
self._height: int = 0
|
||||
self._gap: int = 0
|
||||
self._row: Optional["KeyboardRow"] = row
|
||||
self._pressed: bool = False
|
||||
self._on: bool = False
|
||||
|
||||
def getCode(self) -> int:
|
||||
"""获取按键码"""
|
||||
return self.codes[0] if self.codes else 0
|
||||
|
||||
def isPressed(self) -> bool:
|
||||
"""是否按下"""
|
||||
return self._pressed
|
||||
|
||||
def setPressed(self, pressed: bool):
|
||||
"""设置按下状态"""
|
||||
self._pressed = pressed
|
||||
|
||||
def isOn(self) -> bool:
|
||||
"""是否开启"""
|
||||
return self._on
|
||||
|
||||
def setOn(self, on_state: bool):
|
||||
"""设置开启状态"""
|
||||
self._on = on_state
|
||||
|
||||
def squaredDistance(self, x: int, y: int) -> int:
|
||||
"""计算距离平方"""
|
||||
center_x = self._x + self._width // 2
|
||||
center_y = self._y + self._height // 2
|
||||
return (x - center_x) ** 2 + (y - center_y) ** 2
|
||||
|
||||
def hitTest(self, x: int, y: int) -> bool:
|
||||
"""命中测试"""
|
||||
return (self._x <= x <= self._x + self._width and
|
||||
self._y <= y <= self._y + self._height)
|
||||
|
||||
def getX(self) -> int:
|
||||
return self._x
|
||||
|
||||
def getY(self) -> int:
|
||||
return self._y
|
||||
|
||||
def getWidth(self) -> int:
|
||||
return self._width
|
||||
|
||||
def getHeight(self) -> int:
|
||||
return self._height
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Key(code={self.getCode()}, label={self.label})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# KeyboardRow
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class KeyboardRow(Stub):
|
||||
"""KeyboardRow — 键盘行"""
|
||||
|
||||
def __init__(self, keyboard: "Keyboard" = None):
|
||||
super().__init__()
|
||||
self._keys: List[Key] = []
|
||||
self._keyboard: Optional["Keyboard"] = keyboard
|
||||
self._default_width: int = 0
|
||||
self._default_height: int = 0
|
||||
self._row_edge_flags: int = 0
|
||||
self._mode: str = ""
|
||||
|
||||
def getKeys(self) -> List[Key]:
|
||||
return list(self._keys)
|
||||
|
||||
def addKey(self, key: Key):
|
||||
self._keys.append(key)
|
||||
|
||||
def getKeyboard(self) -> Optional["Keyboard"]:
|
||||
return self._keyboard
|
||||
|
||||
def getDefaultWidth(self) -> int:
|
||||
return self._default_width
|
||||
|
||||
def getDefaultHeight(self) -> int:
|
||||
return self._default_height
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"KeyboardRow(keys={len(self._keys)})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Keyboard
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Keyboard(Stub):
|
||||
"""Keyboard — 键盘布局"""
|
||||
|
||||
def __init__(self, context: Any = None, xml_layout_res_id: int = 0):
|
||||
super().__init__()
|
||||
self._keys: List[Key] = []
|
||||
self._rows: List[KeyboardRow] = []
|
||||
self._modifier_keys: List[Key] = []
|
||||
self._height: int = 0
|
||||
self._min_width: int = 0
|
||||
self._key_width: int = 0
|
||||
self._key_height: int = 0
|
||||
self._horizontal_gap: int = 0
|
||||
self._vertical_gap: int = 0
|
||||
self._shift_keys: List[Key] = []
|
||||
self._alt_keys: List[Key] = []
|
||||
|
||||
def getKeys(self) -> List[Key]:
|
||||
return list(self._keys)
|
||||
|
||||
def getHeight(self) -> int:
|
||||
return self._height
|
||||
|
||||
def getMinWidth(self) -> int:
|
||||
return self._min_width
|
||||
|
||||
def getKeyWidth(self) -> int:
|
||||
return self._key_width
|
||||
|
||||
def getKeyHeight(self) -> int:
|
||||
return self._key_height
|
||||
|
||||
def getHorizontalGap(self) -> int:
|
||||
return self._horizontal_gap
|
||||
|
||||
def getVerticalGap(self) -> int:
|
||||
return self._vertical_gap
|
||||
|
||||
def getModifierKeys(self) -> List[Key]:
|
||||
return list(self._modifier_keys)
|
||||
|
||||
def getNearestKeys(self, x: int, y: int) -> List[Key]:
|
||||
"""获取最近的按键"""
|
||||
nearest: List[Key] = []
|
||||
for key in self._keys:
|
||||
if key.hitTest(x, y):
|
||||
nearest.append(key)
|
||||
return nearest
|
||||
|
||||
def setKeys(self, keys: List[Key]):
|
||||
"""设置按键列表"""
|
||||
self._keys = keys
|
||||
|
||||
def getRows(self) -> List[KeyboardRow]:
|
||||
return list(self._rows)
|
||||
|
||||
def setShifted(self, shift_state: bool):
|
||||
"""设置Shift状态"""
|
||||
pass
|
||||
|
||||
def isShifted(self) -> bool:
|
||||
"""是否Shift状态"""
|
||||
return False
|
||||
|
||||
def getShiftKeyIndex(self) -> int:
|
||||
"""获取Shift按键索引"""
|
||||
for i, key in enumerate(self._keys):
|
||||
if key.modifier:
|
||||
return i
|
||||
return -1
|
||||
|
||||
def setVerticalGap(self, gap: int):
|
||||
self._vertical_gap = gap
|
||||
|
||||
def setHorizontalGap(self, gap: int):
|
||||
self._horizontal_gap = gap
|
||||
|
||||
def setKeyWidth(self, width: int):
|
||||
self._key_width = width
|
||||
|
||||
def setKeyHeight(self, height: int):
|
||||
self._key_height = height
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Keyboard(keys={len(self._keys)}, rows={len(self._rows)})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# KeyboardView
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class KeyboardView(Stub):
|
||||
"""KeyboardView — 键盘视图"""
|
||||
|
||||
class OnKeyboardActionListener(Stub):
|
||||
"""OnKeyboardActionListener — 键盘动作监听器"""
|
||||
|
||||
def onKey(self, primary_code: int):
|
||||
pass
|
||||
|
||||
def onPress(self, primary_code: int):
|
||||
pass
|
||||
|
||||
def onRelease(self, primary_code: int):
|
||||
pass
|
||||
|
||||
def onText(self, text: str):
|
||||
pass
|
||||
|
||||
def swipeLeft(self):
|
||||
pass
|
||||
|
||||
def swipeRight(self):
|
||||
pass
|
||||
|
||||
def swipeDown(self):
|
||||
pass
|
||||
|
||||
def swipeUp(self):
|
||||
pass
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__()
|
||||
self._keyboard: Optional[Keyboard] = None
|
||||
self._on_keyboard_action_listener: Optional["KeyboardView.OnKeyboardActionListener"] = None
|
||||
self._preview_enabled: bool = True
|
||||
self._proximity_correction_enabled: bool = True
|
||||
self._vertical_correction: int = 0
|
||||
self._key_text_color: int = 0xFF000000
|
||||
self._key_text_size: int = 20
|
||||
self._shadow_color: int = 0x00000000
|
||||
self._shadow_radius: float = 0.0
|
||||
self._background_color: int = 0xFF888888
|
||||
|
||||
def setKeyboard(self, keyboard: Keyboard):
|
||||
self._keyboard = keyboard
|
||||
|
||||
def getKeyboard(self) -> Optional[Keyboard]:
|
||||
return self._keyboard
|
||||
|
||||
def onDraw(self, canvas: Any):
|
||||
"""绘制键盘"""
|
||||
pass
|
||||
|
||||
def setOnKeyboardActionListener(self, listener: "KeyboardView.OnKeyboardActionListener"):
|
||||
self._on_keyboard_action_listener = listener
|
||||
|
||||
def getOnKeyboardActionListener(self) -> Optional["KeyboardView.OnKeyboardActionListener"]:
|
||||
return self._on_keyboard_action_listener
|
||||
|
||||
def setPreviewEnabled(self, preview_enabled: bool):
|
||||
self._preview_enabled = preview_enabled
|
||||
|
||||
def isPreviewEnabled(self) -> bool:
|
||||
return self._preview_enabled
|
||||
|
||||
def setProximityCorrectionEnabled(self, enabled: bool):
|
||||
self._proximity_correction_enabled = enabled
|
||||
|
||||
def isProximityCorrectionEnabled(self) -> bool:
|
||||
return self._proximity_correction_enabled
|
||||
|
||||
def setVerticalCorrection(self, vertical_correction: int):
|
||||
self._vertical_correction = vertical_correction
|
||||
|
||||
def getVerticalCorrection(self) -> int:
|
||||
return self._vertical_correction
|
||||
|
||||
def setKeyTextColor(self, color: int):
|
||||
self._key_text_color = color
|
||||
|
||||
def setKeyTextSize(self, size: int):
|
||||
self._key_text_size = size
|
||||
|
||||
def setShadowColor(self, color: int):
|
||||
self._shadow_color = color
|
||||
|
||||
def setShadowRadius(self, radius: float):
|
||||
self._shadow_radius = radius
|
||||
|
||||
def setBackgroundColor(self, color: int):
|
||||
self._background_color = color
|
||||
|
||||
def invalidateAllKeys(self):
|
||||
"""使所有按键失效"""
|
||||
pass
|
||||
|
||||
def invalidateKey(self, key_index: int):
|
||||
"""使指定按键失效"""
|
||||
pass
|
||||
|
||||
def onClick(self, key: Key):
|
||||
"""点击按键"""
|
||||
if self._on_keyboard_action_listener and key.codes:
|
||||
self._on_keyboard_action_listener.onKey(key.codes[0])
|
||||
|
||||
def onLongPress(self, key: Key) -> bool:
|
||||
"""长按按键"""
|
||||
if key.popupCharacters:
|
||||
return True
|
||||
return False
|
||||
|
||||
def closing(self):
|
||||
"""关闭"""
|
||||
pass
|
||||
|
||||
def handleBack(self) -> bool:
|
||||
"""处理返回键"""
|
||||
return False
|
||||
|
||||
def setKeys(self, keys: List[Key]):
|
||||
"""设置按键"""
|
||||
if self._keyboard:
|
||||
self._keyboard.setKeys(keys)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# KeyEventCallback
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class KeyEventCallback(Stub):
|
||||
"""KeyEventCallback — 按键事件回调"""
|
||||
|
||||
def onKeyDown(self, key_code: int, event: Any) -> bool:
|
||||
"""按键按下"""
|
||||
return False
|
||||
|
||||
def onKeyLongPress(self, key_code: int, event: Any) -> bool:
|
||||
"""按键长按"""
|
||||
return False
|
||||
|
||||
def onKeyUp(self, key_code: int, event: Any) -> bool:
|
||||
"""按键释放"""
|
||||
return False
|
||||
|
||||
def onKeyMultiple(self, key_code: int, count: int, event: Any) -> bool:
|
||||
"""按键重复"""
|
||||
return False
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"InputMethodService",
|
||||
"InputMethodImpl",
|
||||
"InputMethodSessionImpl",
|
||||
"AbstractInputMethodService",
|
||||
"InputMethod",
|
||||
"InputMethodSession",
|
||||
"Keyboard",
|
||||
"KeyboardView",
|
||||
"Key",
|
||||
"KeyEventCallback",
|
||||
"KeyboardRow",
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,390 @@
|
||||
"""
|
||||
Android NFC 组件 — NfcAdapter, NfcManager, Tag, NdefMessage 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NdefRecord
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NdefRecord(Stub):
|
||||
"""NdefRecord — NDEF 记录"""
|
||||
|
||||
TNF_ABSOLUTE_URI = 3
|
||||
TNF_EMPTY = 0
|
||||
TNF_EXTERNAL_TYPE = 4
|
||||
TNF_MIME_MEDIA = 2
|
||||
TNF_UNCHANGED = 6
|
||||
TNF_UNKNOWN = 5
|
||||
TNF_WELL_KNOWN = 1
|
||||
|
||||
RTD_URI = "U"
|
||||
RTD_TEXT = "T"
|
||||
RTD_SMART_POSTER = "Sp"
|
||||
RTD_ALTERNATIVE_CARRIER = "ac"
|
||||
RTD_HANDOVER_CARRIER = "Hc"
|
||||
RTD_HANDOVER_REQUEST = "Hr"
|
||||
RTD_HANDOVER_SELECT = "Hs"
|
||||
|
||||
def __init__(self, tnf: int = TNF_EMPTY, type_: bytes = b"",
|
||||
id_: bytes = b"", payload: bytes = b""):
|
||||
super().__init__()
|
||||
self._tnf = tnf
|
||||
self._type = type_
|
||||
self._id = id_
|
||||
self._payload = payload
|
||||
|
||||
@staticmethod
|
||||
def createUri(uri_spec: str) -> "NdefRecord":
|
||||
return NdefRecord(NdefRecord.TNF_ABSOLUTE_URI,
|
||||
type_=uri_spec.encode("utf-8"),
|
||||
payload=uri_spec.encode("utf-8"))
|
||||
|
||||
@staticmethod
|
||||
def createMime(mime_type: str, data: bytes) -> "NdefRecord":
|
||||
return NdefRecord(NdefRecord.TNF_MIME_MEDIA,
|
||||
type_=mime_type.encode("utf-8"),
|
||||
payload=data)
|
||||
|
||||
@staticmethod
|
||||
def createExternal(domain: str, type_: str, data: bytes) -> "NdefRecord":
|
||||
full_type = f"{domain}:{type_}"
|
||||
return NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE,
|
||||
type_=full_type.encode("utf-8"),
|
||||
payload=data)
|
||||
|
||||
@staticmethod
|
||||
def createApplicationRecord(package_name: str) -> "NdefRecord":
|
||||
return NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE,
|
||||
type_=package_name.encode("utf-8"),
|
||||
payload=package_name.encode("utf-8"))
|
||||
|
||||
def getTnf(self) -> int:
|
||||
return self._tnf
|
||||
|
||||
def getType(self) -> bytes:
|
||||
return self._type
|
||||
|
||||
def getId(self) -> bytes:
|
||||
return self._id
|
||||
|
||||
def getPayload(self) -> bytes:
|
||||
return self._payload
|
||||
|
||||
def toByteArray(self) -> bytes:
|
||||
import struct
|
||||
tnf_byte = struct.pack("B", self._tnf & 0x07)
|
||||
type_len = len(self._type)
|
||||
id_len = len(self._id)
|
||||
payload_len = len(self._payload)
|
||||
header = struct.pack("BB", type_len, id_len)
|
||||
return tnf_byte + header + struct.pack(">I", payload_len) + \
|
||||
self._type + self._id + self._payload
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"NdefRecord(tnf={self._tnf}, type={self._type})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NdefMessage
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NdefMessage(Stub):
|
||||
"""NdefMessage — NDEF 消息"""
|
||||
|
||||
def __init__(self, records: Union[List[NdefRecord], bytes, Any]):
|
||||
super().__init__()
|
||||
if isinstance(records, list):
|
||||
self._records = list(records)
|
||||
elif isinstance(records, bytes):
|
||||
self._records = []
|
||||
else:
|
||||
self._records = []
|
||||
|
||||
def getRecords(self) -> List[NdefRecord]:
|
||||
return list(self._records)
|
||||
|
||||
def getByteArrayLength(self) -> int:
|
||||
return sum(len(r.toByteArray()) for r in self._records)
|
||||
|
||||
def toByteArray(self) -> bytes:
|
||||
data = b""
|
||||
for r in self._records:
|
||||
data += r.toByteArray()
|
||||
return data
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Tag
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Tag(Stub):
|
||||
"""Tag — NFC 标签"""
|
||||
|
||||
def __init__(self, id_: bytes = b"", tech_list: Optional[List[str]] = None):
|
||||
super().__init__()
|
||||
self._id = id_
|
||||
self._tech_list = tech_list or []
|
||||
|
||||
def getId(self) -> bytes:
|
||||
return self._id
|
||||
|
||||
def getTechList(self) -> List[str]:
|
||||
return list(self._tech_list)
|
||||
|
||||
def getTechListParcelable(self) -> Any:
|
||||
return TechListParcelable(self._tech_list)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Tag(id={self._id.hex()}, techs={self._tech_list})"
|
||||
|
||||
|
||||
class TechListParcelable(Stub):
|
||||
"""TechListParcelable — 技术列表包裹"""
|
||||
|
||||
def __init__(self, tech_list: List[str]):
|
||||
super().__init__()
|
||||
self._tech_list = list(tech_list)
|
||||
|
||||
def getTechList(self) -> List[str]:
|
||||
return list(self._tech_list)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"TechListParcelable(techs={self._tech_list})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NfcAdapter callback classes
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NfcAdapterCreateBeamUrisCallback(Stub):
|
||||
def createBeamUris(self, event: "NfcEvent") -> List[Any]:
|
||||
return []
|
||||
|
||||
|
||||
class NfcAdapterOnNdefPushCompleteCallback(Stub):
|
||||
def onNdefPushComplete(self, event: "NfcEvent"):
|
||||
pass
|
||||
|
||||
|
||||
class NdefMessageCallback(Stub):
|
||||
def createNdefMessage(self, event: "NfcEvent") -> Optional[NdefMessage]:
|
||||
return None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NfcEvent
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NfcEvent(Stub):
|
||||
"""NfcEvent — NFC 事件"""
|
||||
|
||||
def __init__(self, nfc_adapter: Any = None):
|
||||
super().__init__()
|
||||
self.nfcAdapter = nfc_adapter
|
||||
|
||||
def getNfcAdapter(self) -> Any:
|
||||
return self.nfcAdapter
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NfcAntennaInfo / ApduServiceInfo / CardEmulation / NfcFCardEmulation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NfcAntennaInfo(Stub):
|
||||
"""NfcAntennaInfo — NFC 天线信息"""
|
||||
|
||||
def __init__(self, x: float = 0.0, y: float = 0.0):
|
||||
super().__init__()
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
def getX(self) -> float:
|
||||
return self._x
|
||||
|
||||
def getY(self) -> float:
|
||||
return self._y
|
||||
|
||||
|
||||
class ApduServiceInfo(Stub):
|
||||
"""ApduServiceInfo — APDU 服务信息"""
|
||||
|
||||
def __init__(self, description: str = ""):
|
||||
super().__init__()
|
||||
self._description = description
|
||||
|
||||
def getDescription(self) -> str:
|
||||
return self._description
|
||||
|
||||
|
||||
class CardEmulation(Stub):
|
||||
"""CardEmulation — 卡模拟"""
|
||||
|
||||
CATEGORY_PAYMENT = "payment"
|
||||
CATEGORY_OTHER = "other"
|
||||
|
||||
SELECTION_TYPE_ASK_IF_CONFLICT = 1
|
||||
SELECTION_TYPE_PREFER_DEFAULT = 2
|
||||
SELECTION_TYPE_USER_PROMPTED = 0
|
||||
|
||||
|
||||
class NfcFCardEmulation(Stub):
|
||||
"""NfcFCardEmulation — NFC-F 卡模拟"""
|
||||
|
||||
EXTRA_NFC_F_CARD_EMULATION_PARAMS = "android.nfc.extra.EXTRA_NFC_F_CARD_EMULATION_PARAMS"
|
||||
|
||||
@staticmethod
|
||||
def getInstance(nfc_adapter: Any) -> "NfcFCardEmulation":
|
||||
return NfcFCardEmulation()
|
||||
|
||||
def enableService(self, activity: Any, service: Any) -> bool:
|
||||
return True
|
||||
|
||||
def disableService(self, activity: Any, service: Any) -> bool:
|
||||
return True
|
||||
|
||||
def setSystemCodeForService(self, activity: Any, service: Any, system_code: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NfcManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NfcManager(Stub):
|
||||
"""NfcManager — NFC 管理器"""
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._default_adapter: Optional["NfcAdapter"] = None
|
||||
|
||||
def getDefaultAdapter(self) -> Optional["NfcAdapter"]:
|
||||
if self._default_adapter is None:
|
||||
self._default_adapter = NfcAdapter()
|
||||
return self._default_adapter
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NfcAdapter
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NfcAdapter(Stub):
|
||||
"""NfcAdapter — NFC 适配器"""
|
||||
|
||||
ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED"
|
||||
ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED"
|
||||
ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED"
|
||||
|
||||
FLAG_READER_NFC_A = 1
|
||||
FLAG_READER_NFC_B = 2
|
||||
FLAG_READER_NFC_F = 4
|
||||
FLAG_READER_NFC_V = 8
|
||||
FLAG_READER_NFC_BARCODE = 16
|
||||
FLAG_READER_SKIP_NDEF_CHECK = 128
|
||||
FLAG_READER_NO_PLATFORM_SOUNDS = 256
|
||||
|
||||
FLAG_NDEF_PUSH_NO_CONFIRM = 1
|
||||
|
||||
_default_adapter: Optional["NfcAdapter"] = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._enabled = True
|
||||
self._ndef_push_message: Optional[NdefMessage] = None
|
||||
self._ndef_push_callback: Optional[NdefMessageCallback] = None
|
||||
self._push_complete_callback: Optional[NfcAdapterOnNdefPushCompleteCallback] = None
|
||||
self._beam_uri_callback: Optional[NfcAdapterCreateBeamUrisCallback] = None
|
||||
|
||||
@staticmethod
|
||||
def getDefaultAdapter(context: Any = None) -> Optional["NfcAdapter"]:
|
||||
if NfcAdapter._default_adapter is None:
|
||||
NfcAdapter._default_adapter = NfcAdapter()
|
||||
return NfcAdapter._default_adapter
|
||||
|
||||
def isEnabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
def enable(self) -> bool:
|
||||
self._enabled = True
|
||||
return True
|
||||
|
||||
def disable(self) -> bool:
|
||||
self._enabled = False
|
||||
return True
|
||||
|
||||
def setNdefPushMessage(self, message: NdefMessage, activity: Any,
|
||||
vararg_activities: Any = None):
|
||||
self._ndef_push_message = message
|
||||
|
||||
def setNdefPushMessageCallback(self, callback: NdefMessageCallback, activity: Any,
|
||||
vararg_activities: Any = None):
|
||||
self._ndef_push_callback = callback
|
||||
|
||||
def setOnNdefPushCompleteCallback(self, callback: NfcAdapterOnNdefPushCompleteCallback,
|
||||
activity: Any, vararg_activities: Any = None):
|
||||
self._push_complete_callback = callback
|
||||
|
||||
def setBeamUrisCallback(self, callback: NfcAdapterCreateBeamUrisCallback, activity: Any):
|
||||
self._beam_uri_callback = callback
|
||||
|
||||
def enableForegroundDispatch(self, activity: Any, intent: Any,
|
||||
filters: Any, tech_lists: Any):
|
||||
pass
|
||||
|
||||
def disableForegroundDispatch(self, activity: Any):
|
||||
pass
|
||||
|
||||
def enableReaderMode(self, activity: Any, callback: Any, flags: int, extras: Any = None):
|
||||
pass
|
||||
|
||||
def disableReaderMode(self, activity: Any):
|
||||
pass
|
||||
|
||||
def invokeBeam(self, activity: Any) -> bool:
|
||||
return True
|
||||
|
||||
def isSecureNfcSupported(self) -> bool:
|
||||
return False
|
||||
|
||||
def isSecureNfcEnabled(self) -> bool:
|
||||
return False
|
||||
|
||||
def enableSecureNfc(self, enable: bool) -> bool:
|
||||
return True
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"NfcAdapter(enabled={self._enabled})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"NfcAdapter",
|
||||
"NfcManager",
|
||||
"Tag",
|
||||
"NdefMessage",
|
||||
"NdefRecord",
|
||||
"NfcEvent",
|
||||
"NfcAdapterCreateBeamUrisCallback",
|
||||
"NfcAdapterOnNdefPushCompleteCallback",
|
||||
"NdefMessageCallback",
|
||||
"TechListParcelable",
|
||||
"NfcAntennaInfo",
|
||||
"ApduServiceInfo",
|
||||
"CardEmulation",
|
||||
"NfcFCardEmulation",
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,654 @@
|
||||
"""
|
||||
Android Preference 组件 — Preference, PreferenceScreen, PreferenceManager 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PreferenceChangeListener
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PreferenceChangeListener(Stub):
|
||||
"""OnPreferenceChangeListener — 偏好变化监听器"""
|
||||
|
||||
def onPreferenceChange(self, preference: "Preference", new_value: Any) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class PreferenceOnPreferenceClickListener(Stub):
|
||||
"""OnPreferenceClickListener — 偏好点击监听器"""
|
||||
|
||||
def onPreferenceClick(self, preference: "Preference") -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PreferenceManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PreferenceManager(Stub):
|
||||
"""PreferenceManager — 偏好管理器"""
|
||||
|
||||
KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values"
|
||||
|
||||
def __init__(self, context: Any):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._shared_prefs_name: str = f"{_package_name}_preferences"
|
||||
self._shared_prefs_mode: int = 0
|
||||
self._prefs_data: Dict[str, Any] = {}
|
||||
self._prefs_path = os.path.join(_app_data_dir, "shared_prefs",
|
||||
f"{self._shared_prefs_name}.json")
|
||||
self._load()
|
||||
|
||||
def _load(self):
|
||||
os.makedirs(os.path.dirname(self._prefs_path), exist_ok=True)
|
||||
if os.path.exists(self._prefs_path):
|
||||
try:
|
||||
with open(self._prefs_path, "r") as f:
|
||||
self._prefs_data = json.load(f)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
self._prefs_data = {}
|
||||
else:
|
||||
self._prefs_data = {}
|
||||
|
||||
def _save(self):
|
||||
os.makedirs(os.path.dirname(self._prefs_path), exist_ok=True)
|
||||
with open(self._prefs_path, "w") as f:
|
||||
json.dump(self._prefs_data, f)
|
||||
|
||||
def getSharedPreferences(self) -> Any:
|
||||
return self._prefs_data
|
||||
|
||||
def setSharedPreferencesName(self, name: str):
|
||||
self._shared_prefs_name = name
|
||||
self._prefs_path = os.path.join(_app_data_dir, "shared_prefs",
|
||||
f"{name}.json")
|
||||
self._load()
|
||||
|
||||
def setSharedPreferencesMode(self, mode: int):
|
||||
self._shared_prefs_mode = mode
|
||||
|
||||
@staticmethod
|
||||
def getDefaultSharedPreferences(context: Any) -> Any:
|
||||
mgr = PreferenceManager(context)
|
||||
return mgr.getSharedPreferences()
|
||||
|
||||
@staticmethod
|
||||
def setDefaultValues(context: Any, res_id: int, read_again: bool):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def setDefaultValues(context: Any, shared_prefs_name: str,
|
||||
shared_prefs_mode: int, res_id: int,
|
||||
read_again: bool):
|
||||
pass
|
||||
|
||||
def getPreference(self, key: str, default: Any = None) -> Any:
|
||||
return self._prefs_data.get(key, default)
|
||||
|
||||
def putPreference(self, key: str, value: Any):
|
||||
self._prefs_data[key] = value
|
||||
self._save()
|
||||
|
||||
def removePreference(self, key: str):
|
||||
if key in self._prefs_data:
|
||||
del self._prefs_data[key]
|
||||
self._save()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Preference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Preference(Stub):
|
||||
"""Preference — 偏好设置项基类"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None,
|
||||
def_style_attr: int = 0):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._title: str = ""
|
||||
self._summary: str = ""
|
||||
self._key: Optional[str] = None
|
||||
self._enabled: bool = True
|
||||
self._default_value: Any = None
|
||||
self._order: int = 0
|
||||
self._persistent: bool = True
|
||||
self._icon: Any = None
|
||||
self._dependents: List["Preference"] = []
|
||||
self._dependency_key: Optional[str] = None
|
||||
self._preference_manager: Optional[PreferenceManager] = None
|
||||
self._parent: Optional["PreferenceGroup"] = None
|
||||
self._on_change_listener: Optional[PreferenceChangeListener] = None
|
||||
self._on_click_listener: Optional[PreferenceOnPreferenceClickListener] = None
|
||||
|
||||
def setTitle(self, title: str):
|
||||
self._title = title
|
||||
|
||||
def getTitle(self) -> str:
|
||||
return self._title
|
||||
|
||||
def setSummary(self, summary: str):
|
||||
self._summary = summary
|
||||
|
||||
def getSummary(self) -> str:
|
||||
return self._summary
|
||||
|
||||
def setKey(self, key: str):
|
||||
self._key = key
|
||||
|
||||
def getKey(self) -> Optional[str]:
|
||||
return self._key
|
||||
|
||||
def setEnabled(self, enabled: bool):
|
||||
self._enabled = enabled
|
||||
|
||||
def isEnabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
def setDefaultValue(self, value: Any):
|
||||
self._default_value = value
|
||||
|
||||
def setOnPreferenceClickListener(self, listener: PreferenceOnPreferenceClickListener):
|
||||
self._on_click_listener = listener
|
||||
|
||||
def setOnPreferenceChangeListener(self, listener: PreferenceChangeListener):
|
||||
self._on_change_listener = listener
|
||||
|
||||
def getSharedPreferences(self) -> Any:
|
||||
if self._preference_manager:
|
||||
return self._preference_manager.getSharedPreferences()
|
||||
return {}
|
||||
|
||||
def getEditor(self) -> Any:
|
||||
if self._preference_manager:
|
||||
return self._preference_manager
|
||||
return None
|
||||
|
||||
def shouldCommit(self) -> bool:
|
||||
return True
|
||||
|
||||
def shouldPersist(self) -> bool:
|
||||
return self._persistent and self._key is not None
|
||||
|
||||
def persistString(self, value: str) -> bool:
|
||||
if self.shouldPersist() and self._preference_manager:
|
||||
self._preference_manager.putPreference(self._key, value)
|
||||
return True
|
||||
return False
|
||||
|
||||
def persistInt(self, value: int) -> bool:
|
||||
if self.shouldPersist() and self._preference_manager:
|
||||
self._preference_manager.putPreference(self._key, value)
|
||||
return True
|
||||
return False
|
||||
|
||||
def persistBoolean(self, value: bool) -> bool:
|
||||
if self.shouldPersist() and self._preference_manager:
|
||||
self._preference_manager.putPreference(self._key, value)
|
||||
return True
|
||||
return False
|
||||
|
||||
def persistFloat(self, value: float) -> bool:
|
||||
if self.shouldPersist() and self._preference_manager:
|
||||
self._preference_manager.putPreference(self._key, value)
|
||||
return True
|
||||
return False
|
||||
|
||||
def persistLong(self, value: int) -> bool:
|
||||
return self.persistInt(value)
|
||||
|
||||
def getPersistedString(self, default_return: Optional[str] = None) -> Optional[str]:
|
||||
if self.shouldPersist() and self._preference_manager:
|
||||
val = self._preference_manager.getPreference(self._key, default_return)
|
||||
return str(val) if val is not None else default_return
|
||||
return default_return
|
||||
|
||||
def getPersistedInt(self, default_return: int = 0) -> int:
|
||||
if self.shouldPersist() and self._preference_manager:
|
||||
return int(self._preference_manager.getPreference(self._key, default_return))
|
||||
return default_return
|
||||
|
||||
def getPersistedBoolean(self, default_return: bool = False) -> bool:
|
||||
if self.shouldPersist() and self._preference_manager:
|
||||
val = self._preference_manager.getPreference(self._key, default_return)
|
||||
if isinstance(val, bool):
|
||||
return val
|
||||
return str(val).lower() in ("true", "1", "yes")
|
||||
return default_return
|
||||
|
||||
def getPersistedFloat(self, default_return: float = 0.0) -> float:
|
||||
if self.shouldPersist() and self._preference_manager:
|
||||
return float(self._preference_manager.getPreference(self._key, default_return))
|
||||
return default_return
|
||||
|
||||
def getPersistedLong(self, default_return: int = 0) -> int:
|
||||
return self.getPersistedInt(default_return)
|
||||
|
||||
def notifyChanged(self):
|
||||
pass
|
||||
|
||||
def notifyHierarchyChanged(self):
|
||||
pass
|
||||
|
||||
def onBindView(self, view: Any):
|
||||
pass
|
||||
|
||||
def onClick(self):
|
||||
if self._on_click_listener:
|
||||
self._on_click_listener.onPreferenceClick(self)
|
||||
|
||||
def setOrder(self, order: int):
|
||||
self._order = order
|
||||
|
||||
def getOrder(self) -> int:
|
||||
return self._order
|
||||
|
||||
def setPersistent(self, persistent: bool):
|
||||
self._persistent = persistent
|
||||
|
||||
def isPersistent(self) -> bool:
|
||||
return self._persistent
|
||||
|
||||
def setDependency(self, dependency_key: str):
|
||||
self._dependency_key = dependency_key
|
||||
|
||||
def getDependency(self) -> Optional[str]:
|
||||
return self._dependency_key
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Preference(key={self._key}, title={self._title})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._key) & 0x7FFFFFFF if self._key else 0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PreferenceGroup
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PreferenceGroup(Preference):
|
||||
"""PreferenceGroup — 偏好组"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._preferences: List[Preference] = []
|
||||
|
||||
def addPreference(self, preference: Preference) -> bool:
|
||||
if preference not in self._preferences:
|
||||
self._preferences.append(preference)
|
||||
preference._parent = self
|
||||
return True
|
||||
return False
|
||||
|
||||
def removePreference(self, preference: Preference) -> bool:
|
||||
if preference in self._preferences:
|
||||
self._preferences.remove(preference)
|
||||
preference._parent = None
|
||||
return True
|
||||
return False
|
||||
|
||||
def removeAll(self):
|
||||
for p in self._preferences:
|
||||
p._parent = None
|
||||
self._preferences.clear()
|
||||
|
||||
def getPreference(self, index: int) -> Optional[Preference]:
|
||||
if 0 <= index < len(self._preferences):
|
||||
return self._preferences[index]
|
||||
return None
|
||||
|
||||
def getPreferenceCount(self) -> int:
|
||||
return len(self._preferences)
|
||||
|
||||
def findPreference(self, key: str) -> Optional[Preference]:
|
||||
for p in self._preferences:
|
||||
if p.getKey() == key:
|
||||
return p
|
||||
if isinstance(p, PreferenceGroup):
|
||||
result = p.findPreference(key)
|
||||
if result:
|
||||
return result
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PreferenceGroup(count={len(self._preferences)})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PreferenceScreen
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PreferenceScreen(PreferenceGroup):
|
||||
"""PreferenceScreen — 根偏好屏幕"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._root: bool = True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PreferenceCategory
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PreferenceCategory(PreferenceGroup):
|
||||
"""PreferenceCategory — 偏好分类标题"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TwoStatePreference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TwoStatePreference(Preference):
|
||||
"""TwoStatePreference — 双状态偏好基类"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._checked: bool = False
|
||||
self._summary_on: str = ""
|
||||
self._summary_off: str = ""
|
||||
self._disable_dependents_state: bool = False
|
||||
|
||||
def isChecked(self) -> bool:
|
||||
return self._checked
|
||||
|
||||
def setChecked(self, checked: bool):
|
||||
self._checked = checked
|
||||
self.persistBoolean(checked)
|
||||
|
||||
def setSummaryOn(self, summary: str):
|
||||
self._summary_on = summary
|
||||
|
||||
def setSummaryOff(self, summary: str):
|
||||
self._summary_off = summary
|
||||
|
||||
def setDisableDependentsState(self, state: bool):
|
||||
self._disable_dependents_state = state
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CheckBoxPreference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CheckBoxPreference(TwoStatePreference):
|
||||
"""CheckBoxPreference — 复选框偏好"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SwitchPreference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SwitchPreference(TwoStatePreference):
|
||||
"""SwitchPreference — 开关偏好"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._switch_text_on: str = ""
|
||||
self._switch_text_off: str = ""
|
||||
|
||||
def setSwitchTextOn(self, text: str):
|
||||
self._switch_text_on = text
|
||||
|
||||
def setSwitchTextOff(self, text: str):
|
||||
self._switch_text_off = text
|
||||
|
||||
def getSwitchTextOn(self) -> str:
|
||||
return self._switch_text_on
|
||||
|
||||
def getSwitchTextOff(self) -> str:
|
||||
return self._switch_text_off
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DialogPreference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DialogPreference(Preference):
|
||||
"""DialogPreference — 对话框偏好基类"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._dialog_title: str = ""
|
||||
self._dialog_message: str = ""
|
||||
self._dialog_icon: Any = None
|
||||
self._positive_button_text: str = ""
|
||||
self._negative_button_text: str = ""
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# EditTextPreference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class EditTextPreference(DialogPreference):
|
||||
"""EditTextPreference — 文本编辑偏好"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._text: str = ""
|
||||
|
||||
def getText(self) -> str:
|
||||
return self._text
|
||||
|
||||
def setText(self, text: str):
|
||||
self._text = text
|
||||
self.persistString(text)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ListPreference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ListPreference(DialogPreference):
|
||||
"""ListPreference — 列表偏好"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._entries: List[str] = []
|
||||
self._entry_values: List[str] = []
|
||||
self._value: str = ""
|
||||
|
||||
def getEntries(self) -> List[str]:
|
||||
return list(self._entries)
|
||||
|
||||
def setEntries(self, entries: List[str]):
|
||||
self._entries = list(entries)
|
||||
|
||||
def getEntryValues(self) -> List[str]:
|
||||
return list(self._entry_values)
|
||||
|
||||
def setEntryValues(self, values: List[str]):
|
||||
self._entry_values = list(values)
|
||||
|
||||
def getValue(self) -> str:
|
||||
return self._value
|
||||
|
||||
def setValue(self, value: str):
|
||||
self._value = value
|
||||
self.persistString(value)
|
||||
|
||||
def getEntry(self) -> Optional[str]:
|
||||
try:
|
||||
idx = self._entry_values.index(self._value)
|
||||
if 0 <= idx < len(self._entries):
|
||||
return self._entries[idx]
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def getSummary(self) -> str:
|
||||
entry = self.getEntry()
|
||||
if entry:
|
||||
return entry
|
||||
return self._summary
|
||||
|
||||
def setDefaultValue(self, value: Any):
|
||||
super().setDefaultValue(value)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MultiSelectListPreference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MultiSelectListPreference(DialogPreference):
|
||||
"""MultiSelectListPreference — 多选列表偏好"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._entries: List[str] = []
|
||||
self._entry_values: List[str] = []
|
||||
self._values: Set[str] = set()
|
||||
|
||||
def getEntries(self) -> List[str]:
|
||||
return list(self._entries)
|
||||
|
||||
def setEntries(self, entries: List[str]):
|
||||
self._entries = list(entries)
|
||||
|
||||
def getEntryValues(self) -> List[str]:
|
||||
return list(self._entry_values)
|
||||
|
||||
def setEntryValues(self, values: List[str]):
|
||||
self._entry_values = list(values)
|
||||
|
||||
def getValues(self) -> Set[str]:
|
||||
return set(self._values)
|
||||
|
||||
def setValues(self, values: Set[str]):
|
||||
self._values = set(values)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RingtonePreference / SeekBarPreference / DropDownPreference
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RingtonePreference(Preference):
|
||||
"""RingtonePreference — 铃声偏好"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._ringtone_type: int = 1
|
||||
self._show_default: bool = True
|
||||
self._show_silent: bool = True
|
||||
|
||||
|
||||
class SeekBarPreference(Preference):
|
||||
"""SeekBarPreference — 滑块偏好"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
self._min: int = 0
|
||||
self._max: int = 100
|
||||
self._current: int = 50
|
||||
self._show_progress: bool = True
|
||||
self._adjustable: bool = True
|
||||
|
||||
def getMin(self) -> int:
|
||||
return self._min
|
||||
|
||||
def setMin(self, min_val: int):
|
||||
self._min = min_val
|
||||
|
||||
def getMax(self) -> int:
|
||||
return self._max
|
||||
|
||||
def setMax(self, max_val: int):
|
||||
self._max = max_val
|
||||
|
||||
def getValue(self) -> int:
|
||||
return self._current
|
||||
|
||||
def setValue(self, value: int):
|
||||
self._current = value
|
||||
self.persistInt(value)
|
||||
|
||||
|
||||
class DropDownPreference(ListPreference):
|
||||
"""DropDownPreference — 下拉列表偏好"""
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__(context, attrs)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PreferenceFragment / PreferenceFragmentCompat
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PreferenceFragment(Stub):
|
||||
"""PreferenceFragment — 偏好片段"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._preference_manager: Optional[PreferenceManager] = None
|
||||
|
||||
def onCreate(self, savedInstanceState: Any):
|
||||
pass
|
||||
|
||||
def onCreatePreferences(self, savedInstanceState: Any, root_key: Optional[str]):
|
||||
pass
|
||||
|
||||
def addPreferencesFromResource(self, res_id: int):
|
||||
pass
|
||||
|
||||
def setPreferenceScreen(self, screen: PreferenceScreen):
|
||||
pass
|
||||
|
||||
def getPreferenceScreen(self) -> Optional[PreferenceScreen]:
|
||||
return None
|
||||
|
||||
def findPreference(self, key: str) -> Optional[Preference]:
|
||||
screen = self.getPreferenceScreen()
|
||||
if screen:
|
||||
return screen.findPreference(key)
|
||||
return None
|
||||
|
||||
|
||||
class PreferenceFragmentCompat(PreferenceFragment):
|
||||
"""PreferenceFragmentCompat — 兼容偏好片段"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"Preference",
|
||||
"PreferenceScreen",
|
||||
"PreferenceCategory",
|
||||
"PreferenceManager",
|
||||
"CheckBoxPreference",
|
||||
"SwitchPreference",
|
||||
"ListPreference",
|
||||
"EditTextPreference",
|
||||
"MultiSelectListPreference",
|
||||
"PreferenceFragment",
|
||||
"PreferenceFragmentCompat",
|
||||
"PreferenceGroup",
|
||||
"PreferenceChangeListener",
|
||||
"PreferenceOnPreferenceClickListener",
|
||||
"TwoStatePreference",
|
||||
"DialogPreference",
|
||||
"RingtonePreference",
|
||||
"SeekBarPreference",
|
||||
"DropDownPreference",
|
||||
]
|
||||
@@ -0,0 +1,550 @@
|
||||
"""
|
||||
Android Provider 组件 — Settings, MediaStore, ContactsContract 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# OpenableColumns
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class OpenableColumns(Stub):
|
||||
DISPLAY_NAME = "_display_name"
|
||||
SIZE = "_size"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Settings
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class _SettingsBase(Stub):
|
||||
"""Settings 基类 — 存储到 JSON 文件"""
|
||||
|
||||
_DATA_DIR = os.path.join(_app_data_dir, "shared_prefs")
|
||||
_cache: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
@classmethod
|
||||
def _get_path(cls, name: str) -> str:
|
||||
os.makedirs(cls._DATA_DIR, exist_ok=True)
|
||||
return os.path.join(cls._DATA_DIR, f"{name}.json")
|
||||
|
||||
@classmethod
|
||||
def _load(cls, name: str) -> Dict[str, Any]:
|
||||
if name in cls._cache:
|
||||
return cls._cache[name]
|
||||
path = cls._get_path(name)
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
data = json.load(f)
|
||||
cls._cache[name] = data
|
||||
return data
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
cls._cache[name] = {}
|
||||
return cls._cache[name]
|
||||
|
||||
@classmethod
|
||||
def _save(cls, name: str):
|
||||
path = cls._get_path(name)
|
||||
try:
|
||||
with open(path, "w") as f:
|
||||
json.dump(cls._cache.get(name, {}), f)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def putString(name: str, key: str, value: str) -> bool:
|
||||
data = _SettingsBase._load(name)
|
||||
data[key] = value
|
||||
_SettingsBase._save(name)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def getString(name: str, key: str, default_value: Optional[str] = None) -> Optional[str]:
|
||||
data = _SettingsBase._load(name)
|
||||
return data.get(key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putInt(name: str, key: str, value: int) -> bool:
|
||||
data = _SettingsBase._load(name)
|
||||
data[key] = value
|
||||
_SettingsBase._save(name)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def getInt(name: str, key: str, default_value: int = 0) -> int:
|
||||
data = _SettingsBase._load(name)
|
||||
return data.get(key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putLong(name: str, key: str, value: int) -> bool:
|
||||
return _SettingsBase.putInt(name, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getLong(name: str, key: str, default_value: int = 0) -> int:
|
||||
return _SettingsBase.getInt(name, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putFloat(name: str, key: str, value: float) -> bool:
|
||||
data = _SettingsBase._load(name)
|
||||
data[key] = value
|
||||
_SettingsBase._save(name)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def getFloat(name: str, key: str, default_value: float = 0.0) -> float:
|
||||
data = _SettingsBase._load(name)
|
||||
return data.get(key, default_value)
|
||||
|
||||
|
||||
class SettingsSystem(_SettingsBase):
|
||||
_NAME = "settings_system"
|
||||
|
||||
@staticmethod
|
||||
def putInt(key: str, value: int) -> bool:
|
||||
return _SettingsBase.putInt(SettingsSystem._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getInt(key: str, default_value: int = 0) -> int:
|
||||
return _SettingsBase.getInt(SettingsSystem._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putString(key: str, value: str) -> bool:
|
||||
return _SettingsBase.putString(SettingsSystem._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getString(key: str, default_value: Optional[str] = None) -> Optional[str]:
|
||||
return _SettingsBase.getString(SettingsSystem._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putFloat(key: str, value: float) -> bool:
|
||||
return _SettingsBase.putFloat(SettingsSystem._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getFloat(key: str, default_value: float = 0.0) -> float:
|
||||
return _SettingsBase.getFloat(SettingsSystem._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putLong(key: str, value: int) -> bool:
|
||||
return _SettingsBase.putLong(SettingsSystem._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getLong(key: str, default_value: int = 0) -> int:
|
||||
return _SettingsBase.getLong(SettingsSystem._NAME, key, default_value)
|
||||
|
||||
|
||||
class SettingsSecure(_SettingsBase):
|
||||
_NAME = "settings_secure"
|
||||
|
||||
@staticmethod
|
||||
def putInt(key: str, value: int) -> bool:
|
||||
return _SettingsBase.putInt(SettingsSecure._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getInt(key: str, default_value: int = 0) -> int:
|
||||
return _SettingsBase.getInt(SettingsSecure._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putString(key: str, value: str) -> bool:
|
||||
return _SettingsBase.putString(SettingsSecure._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getString(key: str, default_value: Optional[str] = None) -> Optional[str]:
|
||||
return _SettingsBase.getString(SettingsSecure._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putFloat(key: str, value: float) -> bool:
|
||||
return _SettingsBase.putFloat(SettingsSecure._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getFloat(key: str, default_value: float = 0.0) -> float:
|
||||
return _SettingsBase.getFloat(SettingsSecure._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putLong(key: str, value: int) -> bool:
|
||||
return _SettingsBase.putLong(SettingsSecure._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getLong(key: str, default_value: int = 0) -> int:
|
||||
return _SettingsBase.getLong(SettingsSecure._NAME, key, default_value)
|
||||
|
||||
|
||||
class SettingsGlobal(_SettingsBase):
|
||||
_NAME = "settings_global"
|
||||
|
||||
@staticmethod
|
||||
def putInt(key: str, value: int) -> bool:
|
||||
return _SettingsBase.putInt(SettingsGlobal._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getInt(key: str, default_value: int = 0) -> int:
|
||||
return _SettingsBase.getInt(SettingsGlobal._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putString(key: str, value: str) -> bool:
|
||||
return _SettingsBase.putString(SettingsGlobal._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getString(key: str, default_value: Optional[str] = None) -> Optional[str]:
|
||||
return _SettingsBase.getString(SettingsGlobal._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putFloat(key: str, value: float) -> bool:
|
||||
return _SettingsBase.putFloat(SettingsGlobal._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getFloat(key: str, default_value: float = 0.0) -> float:
|
||||
return _SettingsBase.getFloat(SettingsGlobal._NAME, key, default_value)
|
||||
|
||||
@staticmethod
|
||||
def putLong(key: str, value: int) -> bool:
|
||||
return _SettingsBase.putLong(SettingsGlobal._NAME, key, value)
|
||||
|
||||
@staticmethod
|
||||
def getLong(key: str, default_value: int = 0) -> int:
|
||||
return _SettingsBase.getLong(SettingsGlobal._NAME, key, default_value)
|
||||
|
||||
|
||||
class Settings(Stub):
|
||||
"""Settings — 系统设置"""
|
||||
|
||||
System = SettingsSystem
|
||||
Secure = SettingsSecure
|
||||
Global = SettingsGlobal
|
||||
|
||||
ACTION_SETTINGS = "android.settings.SETTINGS"
|
||||
ACTION_APPLICATION_DETAILS_SETTINGS = "android.settings.APPLICATION_DETAILS_SETTINGS"
|
||||
EXTRA_APP_PACKAGE = "android.intent.extra.PACKAGE_NAME"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaStore
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaStore(Stub):
|
||||
"""MediaStore — 媒体存储"""
|
||||
|
||||
class MediaColumns(Stub):
|
||||
_ID = 0
|
||||
DATA = 1
|
||||
SIZE = 2
|
||||
DISPLAY_NAME = 3
|
||||
MIME_TYPE = 4
|
||||
TITLE = 5
|
||||
DATE_ADDED = 6
|
||||
DATE_MODIFIED = 7
|
||||
ORIENTATION = 8
|
||||
WIDTH = 9
|
||||
HEIGHT = 10
|
||||
|
||||
class Files(Stub):
|
||||
@staticmethod
|
||||
def getContentUri(volume_name: str) -> str:
|
||||
return f"content://media/{volume_name}/file"
|
||||
|
||||
class Images(Stub):
|
||||
class Media(Stub):
|
||||
EXTERNAL_CONTENT_URI = "content://media/external/images/media"
|
||||
INTERNAL_CONTENT_URI = "content://media/internal/images/media"
|
||||
|
||||
class Thumbnails(Stub):
|
||||
EXTERNAL_CONTENT_URI = "content://media/external/images/thumbnails"
|
||||
|
||||
class Video(Stub):
|
||||
class Media(Stub):
|
||||
EXTERNAL_CONTENT_URI = "content://media/external/video/media"
|
||||
|
||||
class Audio(Stub):
|
||||
class Media(Stub):
|
||||
EXTERNAL_CONTENT_URI = "content://media/external/audio/media"
|
||||
|
||||
@staticmethod
|
||||
def getVersion(context: Any, uri: str) -> str:
|
||||
return "0"
|
||||
|
||||
@staticmethod
|
||||
def setVersion(context: Any, uri: str, version: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DocumentsContract
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DocumentsContract(Stub):
|
||||
"""DocumentsContract — 文档合约"""
|
||||
|
||||
EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI"
|
||||
EXTRA_LOADING = "android.provider.extra.LOADING"
|
||||
EXTRA_INFO = "android.provider.extra.INFO"
|
||||
EXTRA_PROMPT = "android.provider.extra.PROMPT"
|
||||
|
||||
@staticmethod
|
||||
def buildDocumentUri(authority: str, document_id: str) -> str:
|
||||
return f"content://{authority}/document/{document_id}"
|
||||
|
||||
@staticmethod
|
||||
def buildDocumentUriUsingTree(tree_uri: str, document_id: str) -> str:
|
||||
return f"{tree_uri}/document/{document_id}"
|
||||
|
||||
@staticmethod
|
||||
def buildChildDocumentsUri(authority: str, parent_document_id: str) -> str:
|
||||
return f"content://{authority}/document/{parent_document_id}/children"
|
||||
|
||||
@staticmethod
|
||||
def buildChildDocumentsUriUsingTree(tree_uri: str, parent_document_id: str) -> str:
|
||||
return f"{tree_uri}/document/{parent_document_id}/children"
|
||||
|
||||
@staticmethod
|
||||
def getDocumentId(document_uri: str) -> str:
|
||||
return document_uri.rsplit("/", 1)[-1]
|
||||
|
||||
@staticmethod
|
||||
def getTreeDocumentId(document_uri: str) -> str:
|
||||
parts = document_uri.split("/")
|
||||
if "document" in parts:
|
||||
idx = parts.index("document")
|
||||
if idx + 1 < len(parts):
|
||||
return parts[idx + 1]
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def getRootId(root_uri: str) -> str:
|
||||
parts = root_uri.split("/")
|
||||
if "root" in parts:
|
||||
idx = parts.index("root")
|
||||
if idx + 1 < len(parts):
|
||||
return parts[idx + 1]
|
||||
return ""
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ContactsContract
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ContactsContract(Stub):
|
||||
"""ContactsContract — 联系人合约"""
|
||||
|
||||
class Contacts(Stub):
|
||||
CONTENT_URI = "content://com.android.contacts/contacts"
|
||||
CONTENT_FILTER_URI = "content://com.android.contacts/contacts/filter"
|
||||
DISPLAY_NAME = "display_name"
|
||||
PHOTO_URI = "photo_uri"
|
||||
_ID = "_id"
|
||||
|
||||
class CommonDataKinds(Stub):
|
||||
class Email(Stub):
|
||||
CONTENT_URI = "content://com.android.contacts/data/emails"
|
||||
|
||||
class Phone(Stub):
|
||||
CONTENT_URI = "content://com.android.contacts/data/phones"
|
||||
|
||||
class Data(Stub):
|
||||
CONTENT_URI = "content://com.android.contacts/data"
|
||||
|
||||
|
||||
class ContactsContractContacts(ContactsContract.Contacts):
|
||||
pass
|
||||
|
||||
|
||||
class ContactsContractData(ContactsContract.Data):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CallLog
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CallLog(Stub):
|
||||
"""CallLog — 通话记录"""
|
||||
|
||||
class Calls(Stub):
|
||||
CONTENT_URI = "content://call_log/calls"
|
||||
_ID = "_id"
|
||||
NUMBER = "number"
|
||||
TYPE = "type"
|
||||
TYPE_INCOMING = 1
|
||||
TYPE_OUTGOING = 2
|
||||
TYPE_MISSED = 3
|
||||
TYPE_VOICEMAIL = 4
|
||||
TYPE_REJECTED = 5
|
||||
TYPE_BLOCKED = 6
|
||||
TYPE_ANSWERED_EXTERNALLY = 7
|
||||
DATE = "date"
|
||||
DURATION = "duration"
|
||||
NEW = "new"
|
||||
VOICEMAIL_URI = "voicemail_uri"
|
||||
CACHED_NAME = "cached_name"
|
||||
CACHED_NUMBER_LABEL = "cached_number_label"
|
||||
GEOCODED_LOCATION = "geocoded_location"
|
||||
COUNTRY_ISO = "country_iso"
|
||||
|
||||
@staticmethod
|
||||
def getLastOutgoingCall(context: Any) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CalendarContract
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CalendarContract(Stub):
|
||||
"""CalendarContract — 日历合约"""
|
||||
|
||||
class Events(Stub):
|
||||
CONTENT_URI = "content://com.android.calendar/events"
|
||||
_ID = "_id"
|
||||
TITLE = "title"
|
||||
DESCRIPTION = "description"
|
||||
EVENT_LOCATION = "event_location"
|
||||
DTSTART = "dtstart"
|
||||
DTEND = "dtend"
|
||||
EVENT_TIMEZONE = "event_timezone"
|
||||
DURATION = "duration"
|
||||
ALL_DAY = "allDay"
|
||||
CALENDAR_ID = "calendar_id"
|
||||
ORGANIZER = "organizer"
|
||||
STATUS = "eventStatus"
|
||||
AVAILABILITY = "availability"
|
||||
ACCESS_LEVEL = "accessLevel"
|
||||
|
||||
class Attendees(Stub):
|
||||
CONTENT_URI = "content://com.android.calendar/attendees"
|
||||
|
||||
class Reminders(Stub):
|
||||
CONTENT_URI = "content://com.android.calendar/reminders"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Browser / SearchRecentSuggestions / UserDictionary
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Browser(Stub):
|
||||
"""Browser — 浏览器书签"""
|
||||
|
||||
class BookmarkColumns(Stub):
|
||||
TITLE = "title"
|
||||
URL = "url"
|
||||
_ID = "_id"
|
||||
BOOKMARK = "bookmark"
|
||||
DATE = "date"
|
||||
CREATED = "created"
|
||||
VISITS = "visits"
|
||||
|
||||
class SearchColumns(Stub):
|
||||
URL = "url"
|
||||
SEARCH = "search"
|
||||
DATE = "date"
|
||||
|
||||
HISTORY_PROJECTION = ["_id", "url", "title", "date", "visits", "bookmark", "created"]
|
||||
SEARCHES_PROJECTION = ["_id", "search", "date"]
|
||||
|
||||
|
||||
class SearchRecentSuggestions(Stub):
|
||||
"""SearchRecentSuggestions — 搜索建议"""
|
||||
|
||||
def __init__(self, context: Any, authority: str, mode: int):
|
||||
super().__init__()
|
||||
self._authority = authority
|
||||
self._mode = mode
|
||||
|
||||
def saveRecentQuery(self, query_string: str, line2: str):
|
||||
pass
|
||||
|
||||
def clearHistory(self):
|
||||
pass
|
||||
|
||||
|
||||
class UserDictionary(Stub):
|
||||
"""UserDictionary — 用户词典"""
|
||||
|
||||
class Words(Stub):
|
||||
CONTENT_URI = "content://user_dictionary/words"
|
||||
_ID = "_id"
|
||||
WORD = "word"
|
||||
FREQUENCY = "frequency"
|
||||
LOCALE = "locale"
|
||||
APP_ID = "app_id"
|
||||
SHORTCUT = "shortcut"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SyncStateContract / LiveFolders
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SyncStateContract(Stub):
|
||||
"""SyncStateContract — 同步状态合约"""
|
||||
|
||||
class Columns(Stub):
|
||||
_ID = "_id"
|
||||
ACCOUNT_NAME = "account_name"
|
||||
ACCOUNT_TYPE = "account_type"
|
||||
DATA = "data"
|
||||
|
||||
class Helpers(Stub):
|
||||
@staticmethod
|
||||
def get(cr: Any, provider_uri: str, account: Any) -> Optional[bytes]:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def set(cr: Any, provider_uri: str, account: Any, data: bytes):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def insert(cr: Any, provider_uri: str, account: Any, data: bytes):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def update(cr: Any, provider_uri: str, account: Any, data: bytes):
|
||||
pass
|
||||
|
||||
|
||||
class LiveFolders(Stub):
|
||||
"""LiveFolders — 实时文件夹"""
|
||||
|
||||
NAME = "name"
|
||||
DESCRIPTION = "description"
|
||||
INTENT = "intent"
|
||||
ICON_BITMAP = "icon_bitmap"
|
||||
ICON_PACKAGE = "icon_package"
|
||||
ICON_RESOURCE = "icon_resource"
|
||||
DISPLAY_MODE = "display_mode"
|
||||
DISPLAY_MODE_GRID = 1
|
||||
DISPLAY_MODE_LIST = 2
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"Settings",
|
||||
"SettingsSystem",
|
||||
"SettingsSecure",
|
||||
"SettingsGlobal",
|
||||
"CallLog",
|
||||
"ContactsContract",
|
||||
"ContactsContractContacts",
|
||||
"ContactsContractData",
|
||||
"MediaStore",
|
||||
"DocumentsContract",
|
||||
"CalendarContract",
|
||||
"Browser",
|
||||
"SearchRecentSuggestions",
|
||||
"UserDictionary",
|
||||
"SyncStateContract",
|
||||
"LiveFolders",
|
||||
"OpenableColumns",
|
||||
]
|
||||
@@ -0,0 +1,401 @@
|
||||
"""
|
||||
Android Speech/TTS 组件 — TextToSpeech, Voice, SynthesisCallback 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import locale as _locale
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TextToSpeech.OnInitListener
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TextToSpeechOnInitListener(Stub):
|
||||
"""OnInitListener — 初始化监听器"""
|
||||
|
||||
def onInit(self, status: int):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UtteranceProgressListener
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UtteranceProgressListener(Stub):
|
||||
"""UtteranceProgressListener — 发音进度监听器"""
|
||||
|
||||
def onStart(self, utterance_id: str):
|
||||
pass
|
||||
|
||||
def onDone(self, utterance_id: str):
|
||||
pass
|
||||
|
||||
def onError(self, utterance_id: str, error_code: int = -1):
|
||||
pass
|
||||
|
||||
def onStop(self, utterance_id: str, interrupted: bool = False):
|
||||
pass
|
||||
|
||||
def onBeginSynthesis(self, utterance_id: str, sample_rate_in_hz: int,
|
||||
audio_format: int, channel_count: int):
|
||||
pass
|
||||
|
||||
def onRangeStart(self, utterance_id: str, start: int, end: int, frame: int):
|
||||
pass
|
||||
|
||||
|
||||
class TextToSpeechOnUtteranceCompletedListener(Stub):
|
||||
"""OnUtteranceCompletedListener — 旧版发音完成监听器"""
|
||||
|
||||
def onUtteranceCompleted(self, utterance_id: str):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SynthesisRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SynthesisRequest(Stub):
|
||||
"""SynthesisRequest — 合成请求"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._text: str = ""
|
||||
self._speech_rate: float = 1.0
|
||||
self._voice: Optional["Voice"] = None
|
||||
self._calling_uid: int = 0
|
||||
self._country: str = ""
|
||||
self._language: str = ""
|
||||
self._variant: str = ""
|
||||
self._params: Dict[str, str] = {}
|
||||
|
||||
def getText(self) -> str:
|
||||
return self._text
|
||||
|
||||
def getSpeechRate(self) -> float:
|
||||
return self._speech_rate
|
||||
|
||||
def getVoice(self) -> Optional["Voice"]:
|
||||
return self._voice
|
||||
|
||||
def getCallingUid(self) -> int:
|
||||
return self._calling_uid
|
||||
|
||||
def getCountry(self) -> str:
|
||||
return self._country
|
||||
|
||||
def getLanguage(self) -> str:
|
||||
return self._language
|
||||
|
||||
def getVariant(self) -> str:
|
||||
return self._variant
|
||||
|
||||
def getParams(self) -> Dict[str, str]:
|
||||
return dict(self._params)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SynthesisCallback
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SynthesisCallback(Stub):
|
||||
"""SynthesisCallback — 合成回调"""
|
||||
|
||||
def start(self, sample_rate_in_hz: int, audio_format: int, channel_count: int) -> int:
|
||||
return 0
|
||||
|
||||
def getMaxBufferSize(self) -> int:
|
||||
return 8192
|
||||
|
||||
def getChannelCount(self) -> int:
|
||||
return 1
|
||||
|
||||
def getAudioFormat(self) -> int:
|
||||
return 1
|
||||
|
||||
def getSampleRate(self) -> int:
|
||||
return 22050
|
||||
|
||||
def availableBuffer(self, buffer_size: int) -> int:
|
||||
return buffer_size
|
||||
|
||||
def audioAvailable(self, audio: bytes) -> int:
|
||||
return len(audio)
|
||||
|
||||
def done(self) -> int:
|
||||
return 0
|
||||
|
||||
def error(self) -> int:
|
||||
return -1
|
||||
|
||||
def hasStarted(self) -> bool:
|
||||
return False
|
||||
|
||||
def hasFinished(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Voice
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Voice(Stub):
|
||||
"""Voice — 语音"""
|
||||
|
||||
QUALITY_DEFAULT = -1
|
||||
QUALITY_VERY_LOW = 100
|
||||
QUALITY_LOW = 200
|
||||
QUALITY_NORMAL = 300
|
||||
QUALITY_HIGH = 400
|
||||
QUALITY_VERY_HIGH = 500
|
||||
|
||||
LATENCY_DEFAULT = -1
|
||||
LATENCY_VERY_LOW = 100
|
||||
LATENCY_LOW = 200
|
||||
LATENCY_NORMAL = 300
|
||||
LATENCY_HIGH = 400
|
||||
LATENCY_VERY_HIGH = 500
|
||||
|
||||
def __init__(self, name: str = "", locale: Any = None,
|
||||
quality: int = QUALITY_NORMAL, latency: int = LATENCY_NORMAL,
|
||||
requires_network: bool = False,
|
||||
features: Optional[List[str]] = None):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._locale = locale or _locale.getdefaultlocale()[0]
|
||||
self._quality = quality
|
||||
self._latency = latency
|
||||
self._requires_network = requires_network
|
||||
self._features = features or []
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getLocale(self) -> Any:
|
||||
return self._locale
|
||||
|
||||
def getQuality(self) -> int:
|
||||
return self._quality
|
||||
|
||||
def getLatency(self) -> int:
|
||||
return self._latency
|
||||
|
||||
def isNetworkConnectionRequired(self) -> bool:
|
||||
return self._requires_network
|
||||
|
||||
def getFeatures(self) -> List[str]:
|
||||
return list(self._features)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Voice(name={self._name}, locale={self._locale})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TextToSpeech.Engine
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TextToSpeechEngine(Stub):
|
||||
"""TextToSpeech.Engine — TTS 引擎元数据"""
|
||||
|
||||
ACTION_CHECK_TTS_DATA = "android.speech.tts.engine.CHECK_TTS_DATA"
|
||||
ACTION_INSTALL_TTS_DATA = "android.speech.tts.engine.INSTALL_TTS_DATA"
|
||||
ACTION_GET_SAMPLE_TEXT = "android.speech.tts.engine.GET_SAMPLE_TEXT"
|
||||
|
||||
KEY_PARAM_STREAM = "streamType"
|
||||
KEY_PARAM_VOLUME = "volume"
|
||||
KEY_PARAM_PAN = "pan"
|
||||
KEY_PARAM_UTTERANCE_ID = "utteranceId"
|
||||
KEY_PARAM_SESSION_ID = "sessionId"
|
||||
|
||||
KEY_FEATURE_NETWORK_SYNTHESIS = "networkSynthesis"
|
||||
KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedSynthesis"
|
||||
KEY_FEATURE_NOT_INSTALLED = "notInstalled"
|
||||
|
||||
DEFAULT_STREAM = 3
|
||||
DEFAULT_LANG = "eng"
|
||||
DEFAULT_COUNTRY = "USA"
|
||||
DEFAULT_VARIANT = ""
|
||||
|
||||
@staticmethod
|
||||
def getLocaleLang(lang: str) -> str:
|
||||
return lang[:3] if len(lang) >= 3 else lang
|
||||
|
||||
@staticmethod
|
||||
def getLocaleCountry(lang: str) -> str:
|
||||
return lang[3:6] if len(lang) >= 6 else ""
|
||||
|
||||
@staticmethod
|
||||
def getLocaleVariant(lang: str) -> str:
|
||||
return lang[6:] if len(lang) > 6 else ""
|
||||
|
||||
|
||||
Engine = TextToSpeechEngine
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TextToSpeech
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TextToSpeech(Stub):
|
||||
"""TextToSpeech — 文字转语音"""
|
||||
|
||||
SUCCESS = 0
|
||||
ERROR = -1
|
||||
STOPPED = -2
|
||||
|
||||
QUEUE_FLUSH = 0
|
||||
QUEUE_ADD = 1
|
||||
|
||||
LANG_COUNTRY_AVAILABLE = 1
|
||||
LANG_AVAILABLE = 0
|
||||
LANG_COUNTRY_VAR_AVAILABLE = 2
|
||||
LANG_MISSING_DATA = -1
|
||||
LANG_NOT_SUPPORTED = -2
|
||||
|
||||
Engine = TextToSpeechEngine
|
||||
|
||||
def __init__(self, context: Any = None, listener: Optional[TextToSpeechOnInitListener] = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._listener = listener
|
||||
self._utterance_progress_listener: Optional[UtteranceProgressListener] = None
|
||||
self._language: str = ""
|
||||
self._country: str = ""
|
||||
self._pitch: float = 1.0
|
||||
self._speech_rate: float = 1.0
|
||||
self._voice: Optional[Voice] = None
|
||||
self._voices: List[Voice] = [
|
||||
Voice("en-us-1", "en_US", Voice.QUALITY_HIGH, Voice.LATENCY_NORMAL),
|
||||
Voice("en-gb-1", "en_GB", Voice.QUALITY_NORMAL, Voice.LATENCY_NORMAL),
|
||||
Voice("zh-cn-1", "zh_CN", Voice.QUALITY_HIGH, Voice.LATENCY_NORMAL),
|
||||
]
|
||||
self._engine_package: str = ""
|
||||
self._initialized = False
|
||||
if listener:
|
||||
try:
|
||||
listener.onInit(self.SUCCESS)
|
||||
except Exception:
|
||||
pass
|
||||
self._initialized = True
|
||||
|
||||
def speak(self, text: str, queue_mode: int = QUEUE_FLUSH,
|
||||
params: Any = None, utterance_id: str = "") -> int:
|
||||
return self.SUCCESS
|
||||
|
||||
def stop(self) -> int:
|
||||
return self.SUCCESS
|
||||
|
||||
def shutdown(self):
|
||||
self._initialized = False
|
||||
|
||||
def setLanguage(self, language: str, country: str = "", variant: str = "") -> int:
|
||||
self._language = language
|
||||
self._country = country
|
||||
return self.LANG_AVAILABLE
|
||||
|
||||
def getLanguage(self) -> Tuple[str, str, str]:
|
||||
return (self._language, self._country, "")
|
||||
|
||||
def isLanguageAvailable(self, language: str, country: str = "",
|
||||
variant: str = "") -> int:
|
||||
return self.LANG_AVAILABLE
|
||||
|
||||
def setOnUtteranceProgressListener(self, listener: UtteranceProgressListener):
|
||||
self._utterance_progress_listener = listener
|
||||
|
||||
def setOnUtteranceCompletedListener(self, listener: TextToSpeechOnUtteranceCompletedListener):
|
||||
pass
|
||||
|
||||
def setPitch(self, pitch: float) -> int:
|
||||
self._pitch = pitch
|
||||
return self.SUCCESS
|
||||
|
||||
def setSpeechRate(self, rate: float) -> int:
|
||||
self._speech_rate = rate
|
||||
return self.SUCCESS
|
||||
|
||||
def getVoice(self) -> Optional[Voice]:
|
||||
return self._voice
|
||||
|
||||
def setVoice(self, voice: Voice) -> int:
|
||||
self._voice = voice
|
||||
return self.SUCCESS
|
||||
|
||||
def getVoices(self) -> Set[Voice]:
|
||||
return set(self._voices)
|
||||
|
||||
def addSpeech(self, text: str, filename: str) -> int:
|
||||
return self.SUCCESS
|
||||
|
||||
def addEyes(self, text: str, filename: str) -> int:
|
||||
return self.SUCCESS
|
||||
|
||||
def synthesizeToFile(self, text: str, params: Any, filename: str) -> int:
|
||||
return self.SUCCESS
|
||||
|
||||
def setEngineByPackageName(self, package_name: str):
|
||||
self._engine_package = package_name
|
||||
|
||||
def getDefaultEngine(self) -> Optional[str]:
|
||||
return "com.android.tts"
|
||||
|
||||
def getEngines(self) -> List["EngineInfo"]:
|
||||
return []
|
||||
|
||||
def getFeatures(self, locale: Any) -> Set[str]:
|
||||
return set()
|
||||
|
||||
def areDefaultsEnforced(self) -> bool:
|
||||
return False
|
||||
|
||||
def setQueueMode(self, queue_mode: int):
|
||||
pass
|
||||
|
||||
def isSpeaking(self) -> bool:
|
||||
return False
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"TextToSpeech(lang={self._language})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# EngineInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class EngineInfo(Stub):
|
||||
"""EngineInfo — 引擎信息"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name: str = ""
|
||||
self.label: str = ""
|
||||
self.icon: int = 0
|
||||
self.priority: int = 0
|
||||
self.system: bool = False
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"TextToSpeech",
|
||||
"TextToSpeechEngine",
|
||||
"TextToSpeechOnInitListener",
|
||||
"TextToSpeechOnUtteranceCompletedListener",
|
||||
"SynthesisCallback",
|
||||
"SynthesisRequest",
|
||||
"Voice",
|
||||
"UtteranceProgressListener",
|
||||
"Engine",
|
||||
"EngineInfo",
|
||||
]
|
||||
@@ -0,0 +1,676 @@
|
||||
"""
|
||||
Android Telephony 组件 — TelephonyManager, CellInfo, PhoneStateListener 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import random
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CellIdentity 基类集合
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CellIdentity(Stub):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
class CellIdentityGsm(CellIdentity):
|
||||
def __init__(self, mcc: int = 0, mnc: int = 0, lac: int = 0, cid: int = 0, arfcn: int = 0):
|
||||
super().__init__()
|
||||
self._mcc = mcc
|
||||
self._mnc = mnc
|
||||
self._lac = lac
|
||||
self._cid = cid
|
||||
self._arfcn = arfcn
|
||||
|
||||
def getMcc(self) -> int: return self._mcc
|
||||
def getMnc(self) -> int: return self._mnc
|
||||
def getLac(self) -> int: return self._lac
|
||||
def getCid(self) -> int: return self._cid
|
||||
def getArfcn(self) -> int: return self._arfcn
|
||||
|
||||
|
||||
class CellIdentityCdma(CellIdentity):
|
||||
def __init__(self, network_id: int = 0, system_id: int = 0, basestation_id: int = 0,
|
||||
latitude: int = 0, longitude: int = 0):
|
||||
super().__init__()
|
||||
self._network_id = network_id
|
||||
self._system_id = system_id
|
||||
self._basestation_id = basestation_id
|
||||
self._latitude = latitude
|
||||
self._longitude = longitude
|
||||
|
||||
def getNetworkId(self) -> int: return self._network_id
|
||||
def getSystemId(self) -> int: return self._system_id
|
||||
def getBasestationId(self) -> int: return self._basestation_id
|
||||
def getLatitude(self) -> int: return self._latitude
|
||||
def getLongitude(self) -> int: return self._longitude
|
||||
|
||||
|
||||
class CellIdentityLte(CellIdentity):
|
||||
def __init__(self, mcc: int = 0, mnc: int = 0, ci: int = 0, pci: int = 0, tac: int = 0):
|
||||
super().__init__()
|
||||
self._mcc = mcc; self._mnc = mnc
|
||||
self._ci = ci; self._pci = pci; self._tac = tac
|
||||
|
||||
def getMcc(self) -> int: return self._mcc
|
||||
def getMnc(self) -> int: return self._mnc
|
||||
def getCi(self) -> int: return self._ci
|
||||
def getPci(self) -> int: return self._pci
|
||||
def getTac(self) -> int: return self._tac
|
||||
|
||||
|
||||
class CellIdentityWcdma(CellIdentity):
|
||||
def __init__(self, mcc: int = 0, mnc: int = 0, lac: int = 0, cid: int = 0, psc: int = 0):
|
||||
super().__init__()
|
||||
self._mcc = mcc; self._mnc = mnc
|
||||
self._lac = lac; self._cid = cid; self._psc = psc
|
||||
|
||||
def getMcc(self) -> int: return self._mcc
|
||||
def getMnc(self) -> int: return self._mnc
|
||||
def getLac(self) -> int: return self._lac
|
||||
def getCid(self) -> int: return self._cid
|
||||
def getPsc(self) -> int: return self._psc
|
||||
|
||||
|
||||
class CellIdentityNr(CellIdentity):
|
||||
def __init__(self, mcc: int = 0, mnc: int = 0, nci: int = 0, pci: int = 0, tac: int = 0):
|
||||
super().__init__()
|
||||
self._mcc = mcc; self._mnc = mnc
|
||||
self._nci = nci; self._pci = pci; self._tac = tac
|
||||
|
||||
def getMcc(self) -> int: return self._mcc
|
||||
def getMnc(self) -> int: return self._mnc
|
||||
def getNci(self) -> int: return self._nci
|
||||
def getPci(self) -> int: return self._pci
|
||||
def getTac(self) -> int: return self._tac
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CellSignalStrength 基类集合
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CellSignalStrength(Stub):
|
||||
SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0
|
||||
SIGNAL_STRENGTH_POOR = 1
|
||||
SIGNAL_STRENGTH_MODERATE = 2
|
||||
SIGNAL_STRENGTH_GOOD = 3
|
||||
SIGNAL_STRENGTH_GREAT = 4
|
||||
|
||||
def getLevel(self) -> int:
|
||||
return self.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
|
||||
|
||||
def getAsuLevel(self) -> int:
|
||||
return 0
|
||||
|
||||
def getDbm(self) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
class CellSignalStrengthGsm(CellSignalStrength):
|
||||
def __init__(self, rssi: int = 0, ber: int = 0, ta: int = 0):
|
||||
super().__init__()
|
||||
self._rssi = rssi; self._ber = ber; self._ta = ta
|
||||
|
||||
def getRssi(self) -> int: return self._rssi
|
||||
def getDbm(self) -> int: return self._rssi - 113 if self._rssi > 0 else 0
|
||||
|
||||
|
||||
class CellSignalStrengthCdma(CellSignalStrength):
|
||||
def __init__(self, dbm: int = 0, ecio: int = 0, evdo_dbm: int = 0, evdo_ecio: int = 0,
|
||||
evdo_snr: int = 0):
|
||||
super().__init__()
|
||||
self._dbm = dbm; self._ecio = ecio
|
||||
self._evdo_dbm = evdo_dbm; self._evdo_ecio = evdo_ecio; self._evdo_snr = evdo_snr
|
||||
|
||||
def getDbm(self) -> int: return self._dbm
|
||||
|
||||
|
||||
class CellSignalStrengthLte(CellSignalStrength):
|
||||
def __init__(self, rssi: int = 0, rsrp: int = 0, rsrq: int = 0, rssnr: int = 0,
|
||||
cqi: int = 0, ta: int = 0):
|
||||
super().__init__()
|
||||
self._rssi = rssi; self._rsrp = rsrp; self._rsrq = rsrq
|
||||
self._rssnr = rssnr; self._cqi = cqi; self._ta = ta
|
||||
|
||||
def getRsrp(self) -> int: return self._rsrp
|
||||
def getRsrq(self) -> int: return self._rsrq
|
||||
def getRssnr(self) -> int: return self._rssnr
|
||||
def getDbm(self) -> int: return self._rsrp
|
||||
|
||||
|
||||
class CellSignalStrengthWcdma(CellSignalStrength):
|
||||
def __init__(self, rscp: int = 0, ecno: int = 0):
|
||||
super().__init__()
|
||||
self._rscp = rscp; self._ecno = ecno
|
||||
|
||||
def getRscp(self) -> int: return self._rscp
|
||||
def getEcno(self) -> int: return self._ecno
|
||||
def getDbm(self) -> int: return self._rscp - 113 if self._rscp > 0 else 0
|
||||
|
||||
|
||||
class CellSignalStrengthNr(CellSignalStrength):
|
||||
def __init__(self, ssRsrp: int = 0, ssRsrq: int = 0, ssSinr: int = 0, csiRsrp: int = 0):
|
||||
super().__init__()
|
||||
self._ssRsrp = ssRsrp; self._ssRsrq = ssRsrq
|
||||
self._ssSinr = ssSinr; self._csiRsrp = csiRsrp
|
||||
|
||||
def getSsRsrp(self) -> int: return self._ssRsrp
|
||||
def getDbm(self) -> int: return self._ssRsrp
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CellInfo 基类集合
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CellInfo(Stub):
|
||||
UNAVAILABLE = 0x7FFFFFFF
|
||||
TIMESTAMP_TYPE_UNKNOWN = 0
|
||||
TIMESTAMP_TYPE_GSM = 1
|
||||
TIMESTAMP_TYPE_UMTS = 2
|
||||
TIMESTAMP_TYPE_CDMA = 3
|
||||
TIMESTAMP_TYPE_LTE = 4
|
||||
TIMESTAMP_TYPE_WCDMA = 5
|
||||
TIMESTAMP_TYPE_NR = 6
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._registered = False
|
||||
self._timestamp = 0
|
||||
self._connection_status = 0
|
||||
|
||||
def isRegistered(self) -> bool: return self._registered
|
||||
def getTimeStamp(self) -> int: return self._timestamp
|
||||
def getCellConnectionStatus(self) -> int: return self._connection_status
|
||||
|
||||
|
||||
class CellInfoGsm(CellInfo):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._identity = CellIdentityGsm()
|
||||
self._signal_strength = CellSignalStrengthGsm()
|
||||
|
||||
def getCellIdentity(self) -> CellIdentityGsm: return self._identity
|
||||
def getCellSignalStrength(self) -> CellSignalStrengthGsm: return self._signal_strength
|
||||
|
||||
|
||||
class CellInfoCdma(CellInfo):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._identity = CellIdentityCdma()
|
||||
self._signal_strength = CellSignalStrengthCdma()
|
||||
|
||||
def getCellIdentity(self) -> CellIdentityCdma: return self._identity
|
||||
def getCellSignalStrength(self) -> CellSignalStrengthCdma: return self._signal_strength
|
||||
|
||||
|
||||
class CellInfoLte(CellInfo):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._identity = CellIdentityLte()
|
||||
self._signal_strength = CellSignalStrengthLte()
|
||||
|
||||
def getCellIdentity(self) -> CellIdentityLte: return self._identity
|
||||
def getCellSignalStrength(self) -> CellSignalStrengthLte: return self._signal_strength
|
||||
|
||||
|
||||
class CellInfoWcdma(CellInfo):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._identity = CellIdentityWcdma()
|
||||
self._signal_strength = CellSignalStrengthWcdma()
|
||||
|
||||
def getCellIdentity(self) -> CellIdentityWcdma: return self._identity
|
||||
def getCellSignalStrength(self) -> CellSignalStrengthWcdma: return self._signal_strength
|
||||
|
||||
|
||||
class CellInfoNr(CellInfo):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._identity = CellIdentityNr()
|
||||
self._signal_strength = CellSignalStrengthNr()
|
||||
|
||||
def getCellIdentity(self) -> CellIdentityNr: return self._identity
|
||||
def getCellSignalStrength(self) -> CellSignalStrengthNr: return self._signal_strength
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PhoneStateListener
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PhoneStateListener(Stub):
|
||||
LISTEN_NONE = 0
|
||||
LISTEN_SERVICE_STATE = 1
|
||||
LISTEN_SIGNAL_STRENGTHS = 2
|
||||
LISTEN_MESSAGE_WAITING_INDICATOR = 4
|
||||
LISTEN_CALL_FORWARDING_INDICATOR = 8
|
||||
LISTEN_CELL_LOCATION = 16
|
||||
LISTEN_CALL_STATE = 32
|
||||
LISTEN_DATA_CONNECTION_STATE = 64
|
||||
LISTEN_DATA_ACTIVITY = 128
|
||||
LISTEN_SIGNAL_STRENGTH = 256
|
||||
LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH = 512
|
||||
LISTEN_CELL_INFO = 1024
|
||||
LISTEN_PRECISE_CALL_STATE = 2048
|
||||
LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096
|
||||
LISTEN_DATA_CONNECTION_REAL_TIME_INFO = 8192
|
||||
LISTEN_OEM_HOOK_RAW_EVENT = 32768
|
||||
LISTEN_SRVCC_STATE_CHANGED = 65536
|
||||
LISTEN_CARRIER_NETWORK_CHANGE = 131072
|
||||
LISTEN_VOICE_ACTIVATION_STATE = 262144
|
||||
LISTEN_DATA_ACTIVATION_STATE = 524288
|
||||
LISTEN_USER_MOBILE_DATA_STATE = 1048576
|
||||
LISTEN_DISPLAY_INFO_CHANGED = 2097152
|
||||
LISTEN_PHONE_CAPABILITY_CHANGE = 4194304
|
||||
LISTEN_ACTIVE_PHONE_TYPE_CHANGE = 8388608
|
||||
LISTEN_EMERGENCY_NUMBER_LIST = 16777216
|
||||
LISTEN_EMERGENCY_NUMBER_SOURCE = 33554432
|
||||
LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864
|
||||
LISTEN_BARRING_INFO_CHANGED = 134217728
|
||||
LISTEN_ALL = 2147483647
|
||||
|
||||
def __init__(self, looper: Any = None):
|
||||
super().__init__()
|
||||
self._looper = looper
|
||||
|
||||
def onServiceStateChanged(self, service_state: "ServiceState"):
|
||||
pass
|
||||
|
||||
def onSignalStrengthsChanged(self, signal_strength: "SignalStrength"):
|
||||
pass
|
||||
|
||||
def onMessageWaitingIndicatorChanged(self, mwi: bool):
|
||||
pass
|
||||
|
||||
def onCallForwardingIndicatorChanged(self, cfi: bool):
|
||||
pass
|
||||
|
||||
def onCellLocationChanged(self, location: Any):
|
||||
pass
|
||||
|
||||
def onCallStateChanged(self, state: int, phone_number: str):
|
||||
pass
|
||||
|
||||
def onDataConnectionStateChanged(self, state: int, network_type: int = 0):
|
||||
pass
|
||||
|
||||
def onDataActivity(self, direction: int):
|
||||
pass
|
||||
|
||||
def onSignalStrengthChanged(self, asu: int):
|
||||
pass
|
||||
|
||||
def onCellInfoChanged(self, cell_info: List[CellInfo]):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ServiceState / SignalStrength
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ServiceState(Stub):
|
||||
STATE_IN_SERVICE = 0
|
||||
STATE_OUT_OF_SERVICE = 1
|
||||
STATE_EMERGENCY_ONLY = 2
|
||||
STATE_POWER_OFF = 3
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._state = self.STATE_OUT_OF_SERVICE
|
||||
self._roaming = False
|
||||
self._operator_alpha_long: str = ""
|
||||
self._operator_alpha_short: str = ""
|
||||
self._operator_numeric: str = ""
|
||||
self._is_manual_selection = False
|
||||
self._cdma_default_roaming_indicator = -1
|
||||
self._cdma_eri_icon_index = -1
|
||||
self._cdma_eri_icon_mode = -1
|
||||
self._cdma_roaming_indicator = -1
|
||||
self._channel_number = 0
|
||||
|
||||
def getState(self) -> int: return self._state
|
||||
def setState(self, state: int): self._state = state
|
||||
def getRoaming(self) -> bool: return self._roaming
|
||||
def setRoaming(self, roaming: bool): self._roaming = roaming
|
||||
def getOperatorAlphaLong(self) -> str: return self._operator_alpha_long
|
||||
def getOperatorAlphaShort(self) -> str: return self._operator_alpha_short
|
||||
def getOperatorNumeric(self) -> str: return self._operator_numeric
|
||||
def getIsManualSelection(self) -> bool: return self._is_manual_selection
|
||||
def getCdmaDefaultRoamingIndicator(self) -> int: return self._cdma_default_roaming_indicator
|
||||
def getCdmaEriIconIndex(self) -> int: return self._cdma_eri_icon_index
|
||||
def getCdmaEriIconMode(self) -> int: return self._cdma_eri_icon_mode
|
||||
def getCdmaRoamingIndicator(self) -> int: return self._cdma_roaming_indicator
|
||||
def getChannelNumber(self) -> int: return self._channel_number
|
||||
def getNetworkRegistrationInfo(self, domain: int, transport_type: int) -> Any:
|
||||
return None
|
||||
def toString(self) -> str:
|
||||
return f"ServiceState(state={self._state}, roaming={self._roaming})"
|
||||
|
||||
|
||||
class SignalStrength(Stub):
|
||||
NUM_SIGNAL_STRENGTH_BINS = 5
|
||||
SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0
|
||||
SIGNAL_STRENGTH_POOR = 1
|
||||
SIGNAL_STRENGTH_MODERATE = 2
|
||||
SIGNAL_STRENGTH_GOOD = 3
|
||||
SIGNAL_STRENGTH_GREAT = 4
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._gsm_signal_strength = 0
|
||||
self._cdma_dbm = 0
|
||||
self._cdma_ecio = 0
|
||||
self._evdo_dbm = 0
|
||||
self._evdo_ecio = 0
|
||||
self._evdo_snr = 0
|
||||
self._lte_signal_strength = 0
|
||||
self._td_scdma_rscp = 0
|
||||
|
||||
def getGsmSignalStrength(self) -> int: return self._gsm_signal_strength
|
||||
def getCdmaDbm(self) -> int: return self._cdma_dbm
|
||||
def getCdmaEcio(self) -> int: return self._cdma_ecio
|
||||
def getEvdoDbm(self) -> int: return self._evdo_dbm
|
||||
def getEvdoEcio(self) -> int: return self._evdo_ecio
|
||||
def getEvdoSnr(self) -> int: return self._evdo_snr
|
||||
def getLteSignalStrength(self) -> int: return self._lte_signal_strength
|
||||
def getLevel(self) -> int: return self.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
|
||||
def getAsuLevel(self) -> int: return 0
|
||||
def getDbm(self) -> int: return 0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SmsManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SmsManager(Stub):
|
||||
RESULT_ERROR_GENERIC_FAILURE = 1
|
||||
RESULT_ERROR_RADIO_OFF = 2
|
||||
RESULT_ERROR_NULL_PDU = 3
|
||||
RESULT_ERROR_NO_SERVICE = 4
|
||||
RESULT_ERROR_LIMIT_EXCEEDED = 5
|
||||
RESULT_ERROR_FDN_CHECK_FAILURE = 6
|
||||
RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 7
|
||||
RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 8
|
||||
RESULT_RIL_GENERIC_FAILURE = 9
|
||||
RESULT_RIL_RADIO_NOT_AVAILABLE = 10
|
||||
STATUS_ON_ICC_FREE = 0
|
||||
STATUS_ON_ICC_READ = 1
|
||||
STATUS_ON_ICC_UNREAD = 3
|
||||
STATUS_ON_ICC_SENT = 5
|
||||
STATUS_ON_ICC_UNSENT = 7
|
||||
|
||||
_instance: Optional["SmsManager"] = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def getDefault() -> "SmsManager":
|
||||
if SmsManager._instance is None:
|
||||
SmsManager._instance = SmsManager()
|
||||
return SmsManager._instance
|
||||
|
||||
def sendTextMessage(self, dest_address: str, sc_address: Optional[str],
|
||||
text: str, sent_intent: Any, delivery_intent: Any):
|
||||
pass
|
||||
|
||||
def sendMultipartTextMessage(self, dest_address: str, sc_address: Optional[str],
|
||||
parts: List[str], sent_intents: Optional[List[Any]],
|
||||
delivery_intents: Optional[List[Any]]):
|
||||
pass
|
||||
|
||||
def sendDataMessage(self, dest_address: str, sc_address: Optional[str],
|
||||
dest_port: int, data: bytes, sent_intent: Any,
|
||||
delivery_intent: Any):
|
||||
pass
|
||||
|
||||
def sendMultimediaMessage(self, context: Any, content_uri: str,
|
||||
location_url: Optional[str],
|
||||
config_overrides: Optional[Any],
|
||||
sent_intent: Any):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PhoneNumberUtils
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PhoneNumberUtils(Stub):
|
||||
|
||||
FORMAT_JAPAN = 2
|
||||
FORMAT_NANP = 1
|
||||
FORMAT_UNKNOWN = 0
|
||||
|
||||
@staticmethod
|
||||
def formatNumber(source: str) -> str:
|
||||
return source
|
||||
|
||||
@staticmethod
|
||||
def isGlobalPhoneNumber(phone_number: str) -> bool:
|
||||
return bool(phone_number and len(phone_number) >= 7)
|
||||
|
||||
@staticmethod
|
||||
def compare(a: str, b: str) -> bool:
|
||||
return a.replace("-", "").replace(" ", "") == b.replace("-", "").replace(" ", "")
|
||||
|
||||
@staticmethod
|
||||
def formatNumberToE164(phone_number: str, default_country_iso: str) -> Optional[str]:
|
||||
return phone_number
|
||||
|
||||
@staticmethod
|
||||
def formatNumberToRFC3966(phone_number: str, default_country_iso: str) -> Optional[str]:
|
||||
return phone_number
|
||||
|
||||
@staticmethod
|
||||
def isEmergencyNumber(phone_number: str) -> bool:
|
||||
return phone_number in ("911", "112", "999", "000", "110", "119", "120", "122")
|
||||
|
||||
@staticmethod
|
||||
def isVoiceMailNumber(phone_number: str) -> bool:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def convertKeypadLettersToDigits(input_: str) -> str:
|
||||
mapping = {"A": "2", "B": "2", "C": "2", "D": "3", "E": "3",
|
||||
"F": "3", "G": "4", "H": "4", "I": "4", "J": "5",
|
||||
"K": "5", "L": "5", "M": "6", "N": "6", "O": "6",
|
||||
"P": "7", "Q": "7", "R": "7", "S": "7", "T": "8",
|
||||
"U": "8", "V": "8", "W": "9", "X": "9", "Y": "9", "Z": "9"}
|
||||
result = ""
|
||||
for c in input_.upper():
|
||||
result += mapping.get(c, c)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def toCallerIDMinMatch(phone_number: str) -> str:
|
||||
return phone_number[-7:] if len(phone_number) >= 7 else phone_number
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TelephonyManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TelephonyManager(Stub):
|
||||
"""TelephonyManager — 电话管理器"""
|
||||
|
||||
PHONE_TYPE_NONE = 0
|
||||
PHONE_TYPE_GSM = 1
|
||||
PHONE_TYPE_CDMA = 2
|
||||
PHONE_TYPE_SIP = 3
|
||||
|
||||
NETWORK_TYPE_UNKNOWN = 0
|
||||
NETWORK_TYPE_GPRS = 1
|
||||
NETWORK_TYPE_EDGE = 2
|
||||
NETWORK_TYPE_UMTS = 3
|
||||
NETWORK_TYPE_CDMA = 4
|
||||
NETWORK_TYPE_EVDO_0 = 5
|
||||
NETWORK_TYPE_EVDO_A = 6
|
||||
NETWORK_TYPE_1xRTT = 7
|
||||
NETWORK_TYPE_HSDPA = 8
|
||||
NETWORK_TYPE_HSUPA = 9
|
||||
NETWORK_TYPE_HSPA = 10
|
||||
NETWORK_TYPE_IDEN = 11
|
||||
NETWORK_TYPE_EVDO_B = 12
|
||||
NETWORK_TYPE_LTE = 13
|
||||
NETWORK_TYPE_EHRPD = 14
|
||||
NETWORK_TYPE_HSPAP = 15
|
||||
NETWORK_TYPE_GSM = 16
|
||||
NETWORK_TYPE_TD_SCDMA = 17
|
||||
NETWORK_TYPE_IWLAN = 18
|
||||
NETWORK_TYPE_LTE_CA = 19
|
||||
NETWORK_TYPE_NR = 20
|
||||
|
||||
SIM_STATE_UNKNOWN = 0
|
||||
SIM_STATE_ABSENT = 1
|
||||
SIM_STATE_PIN_REQUIRED = 2
|
||||
SIM_STATE_PUK_REQUIRED = 3
|
||||
SIM_STATE_NETWORK_LOCKED = 4
|
||||
SIM_STATE_READY = 5
|
||||
SIM_STATE_NOT_READY = 6
|
||||
SIM_STATE_PERM_DISABLED = 7
|
||||
|
||||
DATA_DISCONNECTED = 0
|
||||
DATA_CONNECTING = 1
|
||||
DATA_CONNECTED = 2
|
||||
DATA_SUSPENDED = 3
|
||||
|
||||
CALL_STATE_IDLE = 0
|
||||
CALL_STATE_RINGING = 1
|
||||
CALL_STATE_OFFHOOK = 2
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
|
||||
def getNetworkOperatorName(self) -> str:
|
||||
return "MockOperator"
|
||||
|
||||
def getNetworkOperator(self) -> str:
|
||||
return "310260"
|
||||
|
||||
def getNetworkCountryIso(self) -> str:
|
||||
return "us"
|
||||
|
||||
def getNetworkType(self) -> int:
|
||||
return self.NETWORK_TYPE_LTE
|
||||
|
||||
def getPhoneType(self) -> int:
|
||||
return self.PHONE_TYPE_GSM
|
||||
|
||||
def getDataNetworkType(self) -> int:
|
||||
return self.NETWORK_TYPE_LTE
|
||||
|
||||
def getVoiceNetworkType(self) -> int:
|
||||
return self.NETWORK_TYPE_LTE
|
||||
|
||||
def getSimOperator(self) -> str:
|
||||
return "310260"
|
||||
|
||||
def getSimOperatorName(self) -> str:
|
||||
return "MockSIM"
|
||||
|
||||
def getSimCountryIso(self) -> str:
|
||||
return "us"
|
||||
|
||||
def getSimSerialNumber(self) -> Optional[str]:
|
||||
return "89014103211118510720"
|
||||
|
||||
def getSubscriberId(self) -> Optional[str]:
|
||||
return "310260123456789"
|
||||
|
||||
def getDeviceId(self) -> Optional[str]:
|
||||
return "000000000000000"
|
||||
|
||||
def getDeviceSoftwareVersion(self) -> Optional[str]:
|
||||
return "00.00.00.00"
|
||||
|
||||
def getLine1Number(self) -> Optional[str]:
|
||||
return "15551234567"
|
||||
|
||||
def getGroupIdLevel1(self) -> Optional[str]:
|
||||
return ""
|
||||
|
||||
def getIccAuthentication(self, app_type: int, aid: str, data: bytes) -> Optional[bytes]:
|
||||
return None
|
||||
|
||||
def getVoiceMailNumber(self) -> Optional[str]:
|
||||
return "15551234567"
|
||||
|
||||
def getVoiceMailAlphaTag(self) -> str:
|
||||
return "Voicemail"
|
||||
|
||||
def isNetworkRoaming(self) -> bool:
|
||||
return False
|
||||
|
||||
def hasIccCard(self) -> bool:
|
||||
return True
|
||||
|
||||
def getSimState(self) -> int:
|
||||
return self.SIM_STATE_READY
|
||||
|
||||
def getCellLocation(self) -> Any:
|
||||
return None
|
||||
|
||||
def getAllCellInfo(self) -> Optional[List[CellInfo]]:
|
||||
return []
|
||||
|
||||
def getMmsUserAgent(self) -> str:
|
||||
return "Android-Mms/1.0"
|
||||
|
||||
def getMmsUAProfUrl(self) -> str:
|
||||
return "http://www.example.com/uaprof.xml"
|
||||
|
||||
def listen(self, listener: PhoneStateListener, events: int):
|
||||
pass
|
||||
|
||||
def getDataState(self) -> int:
|
||||
return self.DATA_CONNECTED
|
||||
|
||||
def getDataActivity(self) -> int:
|
||||
return 0 # DATA_ACTIVITY_NONE
|
||||
|
||||
def getCallState(self) -> int:
|
||||
return self.CALL_STATE_IDLE
|
||||
|
||||
def toString(self) -> str:
|
||||
return "TelephonyManager()"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"TelephonyManager",
|
||||
"CellInfo",
|
||||
"CellInfoGsm",
|
||||
"CellInfoCdma",
|
||||
"CellInfoLte",
|
||||
"CellInfoWcdma",
|
||||
"CellInfoNr",
|
||||
"CellIdentityGsm",
|
||||
"CellIdentityCdma",
|
||||
"CellIdentityLte",
|
||||
"CellIdentityWcdma",
|
||||
"CellIdentityNr",
|
||||
"CellSignalStrengthGsm",
|
||||
"CellSignalStrengthCdma",
|
||||
"CellSignalStrengthLte",
|
||||
"CellSignalStrengthWcdma",
|
||||
"CellSignalStrengthNr",
|
||||
"PhoneStateListener",
|
||||
"PhoneNumberUtils",
|
||||
"ServiceState",
|
||||
"SignalStrength",
|
||||
"SmsManager",
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,935 @@
|
||||
"""
|
||||
Android View Animation 组件 — Animation, AnimationSet, Interpolators,
|
||||
AnimationUtils, Transformation 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import math
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, android_context
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Interpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Interpolator(Stub):
|
||||
"""Interpolator — 插值器接口"""
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
return input
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AccelerateInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AccelerateInterpolator(Interpolator):
|
||||
"""AccelerateInterpolator — 加速插值器(先慢后快)"""
|
||||
|
||||
def __init__(self, factor: float = 1.0):
|
||||
super().__init__()
|
||||
self._factor = factor
|
||||
self._double_factor = 2.0 * factor
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
if self._factor == 1.0:
|
||||
return input * input
|
||||
return input ** (self._double_factor)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DecelerateInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DecelerateInterpolator(Interpolator):
|
||||
"""DecelerateInterpolator — 减速插值器(先快后慢)"""
|
||||
|
||||
def __init__(self, factor: float = 1.0):
|
||||
super().__init__()
|
||||
self._factor = factor
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
if self._factor == 1.0:
|
||||
return 1.0 - (1.0 - input) * (1.0 - input)
|
||||
return 1.0 - (1.0 - input) ** (2.0 * self._factor)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AccelerateDecelerateInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AccelerateDecelerateInterpolator(Interpolator):
|
||||
"""AccelerateDecelerateInterpolator — 加速减速插值器"""
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
return float(math.cos((input + 1) * math.pi) / 2.0 + 0.5)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# LinearInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class LinearInterpolator(Interpolator):
|
||||
"""LinearInterpolator — 线性插值器"""
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
return input
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# OvershootInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class OvershootInterpolator(Interpolator):
|
||||
"""OvershootInterpolator — 超出插值器(越过目标再回弹)"""
|
||||
|
||||
def __init__(self, tension: float = 2.0):
|
||||
super().__init__()
|
||||
self._tension = tension
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
input -= 1.0
|
||||
return input * input * ((self._tension + 1) * input + self._tension) + 1.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AnticipateInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AnticipateInterpolator(Interpolator):
|
||||
"""AnticipateInterpolator — 预判插值器(先反向后正向前进)"""
|
||||
|
||||
def __init__(self, tension: float = 2.0):
|
||||
super().__init__()
|
||||
self._tension = tension
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
return input * input * ((self._tension + 1) * input - self._tension)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AnticipateOvershootInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AnticipateOvershootInterpolator(Interpolator):
|
||||
"""AnticipateOvershootInterpolator — 预判超出插值器"""
|
||||
|
||||
def __init__(self, tension: float = 2.0, extra_tension: float = 1.5):
|
||||
super().__init__()
|
||||
self._tension = tension
|
||||
self._extra_tension = extra_tension
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
if input < 0.5:
|
||||
t = input * 2.0
|
||||
return 0.5 * t * t * ((self._tension + 1) * t - self._tension)
|
||||
else:
|
||||
t = input * 2.0 - 2.0
|
||||
return 0.5 * (t * t * ((self._tension + 1) * t + self._tension) + 2.0)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BounceInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BounceInterpolator(Interpolator):
|
||||
"""BounceInterpolator — 弹跳插值器"""
|
||||
|
||||
@staticmethod
|
||||
def _bounce(t: float) -> float:
|
||||
t2 = t * t
|
||||
return t2 * 8.0
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
if input < 0.3535:
|
||||
return BounceInterpolator._bounce(input)
|
||||
elif input < 0.7408:
|
||||
return BounceInterpolator._bounce(input - 0.54719) + 0.7
|
||||
elif input < 0.9644:
|
||||
return BounceInterpolator._bounce(input - 0.8526) + 0.9
|
||||
else:
|
||||
return BounceInterpolator._bounce(input - 1.0435) + 0.95
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CycleInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CycleInterpolator(Interpolator):
|
||||
"""CycleInterpolator — 周期插值器(正弦循环)"""
|
||||
|
||||
def __init__(self, cycles: float = 1.0):
|
||||
super().__init__()
|
||||
self._cycles = cycles
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
return float(math.sin(2.0 * math.pi * self._cycles * input))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PathInterpolator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PathInterpolator(Interpolator):
|
||||
"""PathInterpolator — 路径插值器"""
|
||||
|
||||
def __init__(self, control_x1: float = 0.0, control_y1: float = 0.0,
|
||||
control_x2: float = 1.0, control_y2: float = 1.0):
|
||||
super().__init__()
|
||||
self._control_x1 = control_x1
|
||||
self._control_y1 = control_y1
|
||||
self._control_x2 = control_x2
|
||||
self._control_y2 = control_y2
|
||||
|
||||
def getInterpolation(self, input: float) -> float:
|
||||
return self._cubic_bezier(input)
|
||||
|
||||
def _cubic_bezier(self, t: float) -> float:
|
||||
u = 1.0 - t
|
||||
return (3.0 * u * u * t * self._control_y1 +
|
||||
3.0 * u * t * t * self._control_y2 +
|
||||
t * t * t)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Transformation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Transformation(Stub):
|
||||
"""Transformation — 动画变换矩阵"""
|
||||
|
||||
TYPE_IDENTITY = 0
|
||||
TYPE_ALPHA = 1
|
||||
TYPE_BOTH = 2
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._alpha: float = 1.0
|
||||
self._matrix: Any = None # 占位,实际 Android 为 Matrix 对象
|
||||
self._transformation_type: int = Transformation.TYPE_IDENTITY
|
||||
|
||||
def getAlpha(self) -> float:
|
||||
return self._alpha
|
||||
|
||||
def setAlpha(self, alpha: float):
|
||||
self._alpha = alpha
|
||||
|
||||
def getMatrix(self) -> Any:
|
||||
return self._matrix
|
||||
|
||||
def setMatrix(self, matrix: Any):
|
||||
self._matrix = matrix
|
||||
|
||||
def setTransformationType(self, transformation_type: int):
|
||||
self._transformation_type = transformation_type
|
||||
|
||||
def getTransformationType(self) -> int:
|
||||
return self._transformation_type
|
||||
|
||||
def clear(self):
|
||||
self._alpha = 1.0
|
||||
self._matrix = None
|
||||
self._transformation_type = Transformation.TYPE_IDENTITY
|
||||
|
||||
def compose(self, other: "Transformation"):
|
||||
if other is None:
|
||||
return
|
||||
self._alpha *= other._alpha
|
||||
if self._matrix is None:
|
||||
self._matrix = other._matrix
|
||||
elif other._matrix is not None:
|
||||
pass # 占位:矩阵乘法
|
||||
self._transformation_type = max(self._transformation_type,
|
||||
other._transformation_type)
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Transformation(alpha={self._alpha}, "
|
||||
f"type={self._transformation_type})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Animation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Animation(Stub):
|
||||
"""Animation — 动画基类"""
|
||||
|
||||
INFINITE = -1
|
||||
RESTART = 1
|
||||
REVERSE = 2
|
||||
|
||||
ABSOLUTE = 0
|
||||
RELATIVE_TO_SELF = 1
|
||||
RELATIVE_TO_PARENT = 2
|
||||
|
||||
START_ON_FIRST_FRAME = -1
|
||||
|
||||
class AnimationListener(Stub):
|
||||
"""AnimationListener — 动画监听器"""
|
||||
|
||||
def onAnimationStart(self, animation: "Animation"):
|
||||
pass
|
||||
|
||||
def onAnimationEnd(self, animation: "Animation"):
|
||||
pass
|
||||
|
||||
def onAnimationRepeat(self, animation: "Animation"):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._duration: int = 0
|
||||
self._start_offset: int = 0
|
||||
self._repeat_count: int = 0
|
||||
self._repeat_mode: int = Animation.RESTART
|
||||
self._fill_before: bool = False
|
||||
self._fill_after: bool = False
|
||||
self._fill_enabled: bool = False
|
||||
self._z_adjustment: int = 0
|
||||
self._interpolator: Optional[Interpolator] = None
|
||||
self._listener: Optional["Animation.AnimationListener"] = None
|
||||
self._start_time: Optional[int] = None
|
||||
self._started: bool = False
|
||||
self._ended: bool = False
|
||||
self._cycle: float = 0.0
|
||||
self._current_repeat: int = 0
|
||||
self._initialized: bool = False
|
||||
self._scale_factor: float = 1.0
|
||||
self._paused: bool = False
|
||||
self._more: bool = True
|
||||
|
||||
def start(self):
|
||||
self._start_time = self._current_time_millis()
|
||||
self._started = True
|
||||
self._ended = False
|
||||
self._current_repeat = 0
|
||||
self._more = True
|
||||
if self._listener:
|
||||
self._listener.onAnimationStart(self)
|
||||
|
||||
def startNow(self):
|
||||
self.start()
|
||||
|
||||
def cancel(self):
|
||||
self._ended = True
|
||||
self._more = False
|
||||
|
||||
def reset(self):
|
||||
self._start_time = None
|
||||
self._started = False
|
||||
self._ended = False
|
||||
self._current_repeat = 0
|
||||
self._cycle = 0.0
|
||||
self._more = True
|
||||
self._initialized = False
|
||||
|
||||
def setDuration(self, duration_millis: int):
|
||||
self._duration = duration_millis
|
||||
|
||||
def getDuration(self) -> int:
|
||||
return self._duration
|
||||
|
||||
def setStartOffset(self, start_offset: int):
|
||||
self._start_offset = start_offset
|
||||
|
||||
def getStartOffset(self) -> int:
|
||||
return self._start_offset
|
||||
|
||||
def setRepeatCount(self, repeat_count: int):
|
||||
self._repeat_count = repeat_count
|
||||
|
||||
def getRepeatCount(self) -> int:
|
||||
return self._repeat_count
|
||||
|
||||
def setRepeatMode(self, repeat_mode: int):
|
||||
self._repeat_mode = repeat_mode
|
||||
|
||||
def getRepeatMode(self) -> int:
|
||||
return self._repeat_mode
|
||||
|
||||
def setInterpolator(self, interpolator: Optional[Interpolator]):
|
||||
self._interpolator = interpolator
|
||||
|
||||
def getInterpolator(self) -> Optional[Interpolator]:
|
||||
return self._interpolator
|
||||
|
||||
def setAnimationListener(self, listener: "Animation.AnimationListener"):
|
||||
self._listener = listener
|
||||
|
||||
def getAnimationListener(self) -> Optional["Animation.AnimationListener"]:
|
||||
return self._listener
|
||||
|
||||
def getTransformation(self, current_time: int,
|
||||
out_transformation: Transformation) -> bool:
|
||||
if self._start_time is None:
|
||||
self._start_time = current_time
|
||||
|
||||
elapsed = current_time - self._start_time - self._start_offset
|
||||
if elapsed < 0:
|
||||
out_transformation.clear()
|
||||
return True
|
||||
|
||||
duration = self._duration
|
||||
if duration == 0:
|
||||
duration = 1
|
||||
|
||||
normalized = elapsed / duration
|
||||
|
||||
if normalized >= 1.0:
|
||||
if self._current_repeat < self._repeat_count or self._repeat_count == self.INFINITE:
|
||||
if self._repeat_mode == Animation.REVERSE:
|
||||
normalized = 0.0
|
||||
else:
|
||||
normalized = 0.0
|
||||
self._current_repeat += 1
|
||||
if self._listener:
|
||||
self._listener.onAnimationRepeat(self)
|
||||
else:
|
||||
normalized = 1.0
|
||||
self._ended = True
|
||||
self._more = False
|
||||
self._apply_transformation(1.0, out_transformation)
|
||||
if self._listener:
|
||||
self._listener.onAnimationEnd(self)
|
||||
return False
|
||||
|
||||
if normalized > 1.0:
|
||||
normalized = 1.0
|
||||
|
||||
if self._interpolator:
|
||||
interpolated = self._interpolator.getInterpolation(normalized)
|
||||
else:
|
||||
interpolated = normalized
|
||||
|
||||
self._cycle = normalized
|
||||
self._apply_transformation(interpolated, out_transformation)
|
||||
return self._more
|
||||
|
||||
def _apply_transformation(self, interpolated_time: float, t: Transformation):
|
||||
pass
|
||||
|
||||
def hasEnded(self) -> bool:
|
||||
return self._ended
|
||||
|
||||
def hasStarted(self) -> bool:
|
||||
return self._started
|
||||
|
||||
def setFillBefore(self, fill_before: bool):
|
||||
self._fill_before = fill_before
|
||||
|
||||
def getFillBefore(self) -> bool:
|
||||
return self._fill_before
|
||||
|
||||
def setFillAfter(self, fill_after: bool):
|
||||
self._fill_after = fill_after
|
||||
|
||||
def getFillAfter(self) -> bool:
|
||||
return self._fill_after
|
||||
|
||||
def setFillEnabled(self, fill_enabled: bool):
|
||||
self._fill_enabled = fill_enabled
|
||||
|
||||
def getFillEnabled(self) -> bool:
|
||||
return self._fill_enabled
|
||||
|
||||
def setZAdjustment(self, z_adjustment: int):
|
||||
self._z_adjustment = z_adjustment
|
||||
|
||||
def getZAdjustment(self) -> int:
|
||||
return self._z_adjustment
|
||||
|
||||
def willChangeBounds(self) -> bool:
|
||||
return True
|
||||
|
||||
def willChangeTransformationMatrix(self) -> bool:
|
||||
return True
|
||||
|
||||
def initialize(self, width: int, height: int, parent_width: int, parent_height: int):
|
||||
self._initialized = True
|
||||
|
||||
def restrictDuration(self, duration_millis: int):
|
||||
if self._duration > duration_millis:
|
||||
self._duration = duration_millis
|
||||
|
||||
def scaleCurrentDuration(self, scale: float):
|
||||
self._duration = int(self._duration * scale)
|
||||
|
||||
@staticmethod
|
||||
def _current_time_millis() -> int:
|
||||
return int(time.time() * 1000)
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Animation(duration={self._duration}, "
|
||||
f"repeatCount={self._repeat_count}, "
|
||||
f"started={self._started})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AlphaAnimation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AlphaAnimation(Animation):
|
||||
"""AlphaAnimation — 透明度动画"""
|
||||
|
||||
def __init__(self, from_alpha: float, to_alpha: float):
|
||||
super().__init__()
|
||||
self._from_alpha = from_alpha
|
||||
self._to_alpha = to_alpha
|
||||
|
||||
def _apply_transformation(self, interpolated_time: float, t: Transformation):
|
||||
alpha = self._from_alpha + (self._to_alpha - self._from_alpha) * interpolated_time
|
||||
t.setAlpha(alpha)
|
||||
|
||||
def getFromAlpha(self) -> float:
|
||||
return self._from_alpha
|
||||
|
||||
def getToAlpha(self) -> float:
|
||||
return self._to_alpha
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScaleAnimation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScaleAnimation(Animation):
|
||||
"""ScaleAnimation — 缩放动画"""
|
||||
|
||||
def __init__(self, from_x: float, to_x: float, from_y: float, to_y: float,
|
||||
pivot_x: float = 0.0, pivot_y: float = 0.0,
|
||||
pivot_x_type: int = Animation.ABSOLUTE,
|
||||
pivot_y_type: int = Animation.ABSOLUTE):
|
||||
super().__init__()
|
||||
self._from_x = from_x
|
||||
self._to_x = to_x
|
||||
self._from_y = from_y
|
||||
self._to_y = to_y
|
||||
self._pivot_x = pivot_x
|
||||
self._pivot_y = pivot_y
|
||||
self._pivot_x_type = pivot_x_type
|
||||
self._pivot_y_type = pivot_y_type
|
||||
|
||||
def _apply_transformation(self, interpolated_time: float, t: Transformation):
|
||||
sx = self._from_x + (self._to_x - self._from_x) * interpolated_time
|
||||
sy = self._from_y + (self._to_y - self._from_y) * interpolated_time
|
||||
# 占位:Matrix 的缩放操作
|
||||
if t.getMatrix() is not None:
|
||||
pass
|
||||
|
||||
def getFromX(self) -> float:
|
||||
return self._from_x
|
||||
|
||||
def getToX(self) -> float:
|
||||
return self._to_x
|
||||
|
||||
def getFromY(self) -> float:
|
||||
return self._from_y
|
||||
|
||||
def getToY(self) -> float:
|
||||
return self._to_y
|
||||
|
||||
def getPivotX(self) -> float:
|
||||
return self._pivot_x
|
||||
|
||||
def getPivotY(self) -> float:
|
||||
return self._pivot_y
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TranslateAnimation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TranslateAnimation(Animation):
|
||||
"""TranslateAnimation — 平移动画"""
|
||||
|
||||
def __init__(self, from_x: float, to_x: float, from_y: float, to_y: float,
|
||||
from_x_type: int = Animation.ABSOLUTE,
|
||||
to_x_type: int = Animation.ABSOLUTE,
|
||||
from_y_type: int = Animation.ABSOLUTE,
|
||||
to_y_type: int = Animation.ABSOLUTE):
|
||||
super().__init__()
|
||||
self._from_x = from_x
|
||||
self._to_x = to_x
|
||||
self._from_y = from_y
|
||||
self._to_y = to_y
|
||||
self._from_x_type = from_x_type
|
||||
self._to_x_type = to_x_type
|
||||
self._from_y_type = from_y_type
|
||||
self._to_y_type = to_y_type
|
||||
|
||||
def _apply_transformation(self, interpolated_time: float, t: Transformation):
|
||||
dx = self._from_x + (self._to_x - self._from_x) * interpolated_time
|
||||
dy = self._from_y + (self._to_y - self._from_y) * interpolated_time
|
||||
if t.getMatrix() is not None:
|
||||
pass
|
||||
|
||||
def getFromX(self) -> float:
|
||||
return self._from_x
|
||||
|
||||
def getToX(self) -> float:
|
||||
return self._to_x
|
||||
|
||||
def getFromY(self) -> float:
|
||||
return self._from_y
|
||||
|
||||
def getToY(self) -> float:
|
||||
return self._to_y
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RotateAnimation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RotateAnimation(Animation):
|
||||
"""RotateAnimation — 旋转动画"""
|
||||
|
||||
def __init__(self, from_degrees: float, to_degrees: float,
|
||||
pivot_x: float = 0.0, pivot_y: float = 0.0,
|
||||
pivot_x_type: int = Animation.ABSOLUTE,
|
||||
pivot_y_type: int = Animation.ABSOLUTE):
|
||||
super().__init__()
|
||||
self._from_degrees = from_degrees
|
||||
self._to_degrees = to_degrees
|
||||
self._pivot_x = pivot_x
|
||||
self._pivot_y = pivot_y
|
||||
self._pivot_x_type = pivot_x_type
|
||||
self._pivot_y_type = pivot_y_type
|
||||
|
||||
def _apply_transformation(self, interpolated_time: float, t: Transformation):
|
||||
degrees = self._from_degrees + (self._to_degrees - self._from_degrees) * interpolated_time
|
||||
if t.getMatrix() is not None:
|
||||
pass
|
||||
|
||||
def getFromDegrees(self) -> float:
|
||||
return self._from_degrees
|
||||
|
||||
def getToDegrees(self) -> float:
|
||||
return self._to_degrees
|
||||
|
||||
def getPivotX(self) -> float:
|
||||
return self._pivot_x
|
||||
|
||||
def getPivotY(self) -> float:
|
||||
return self._pivot_y
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AnimationSet
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AnimationSet(Animation):
|
||||
"""AnimationSet — 动画集合"""
|
||||
|
||||
def __init__(self, share_interpolator: bool = True):
|
||||
super().__init__()
|
||||
self._share_interpolator = share_interpolator
|
||||
self._animations: List[Animation] = []
|
||||
|
||||
def addAnimation(self, animation: Animation):
|
||||
self._animations.append(animation)
|
||||
if self._share_interpolator and self._interpolator:
|
||||
animation.setInterpolator(self._interpolator)
|
||||
|
||||
def getAnimations(self) -> List[Animation]:
|
||||
return list(self._animations)
|
||||
|
||||
def setDuration(self, duration_millis: int):
|
||||
super().setDuration(duration_millis)
|
||||
for anim in self._animations:
|
||||
anim.setDuration(duration_millis)
|
||||
|
||||
def setFillAfter(self, fill_after: bool):
|
||||
super().setFillAfter(fill_after)
|
||||
for anim in self._animations:
|
||||
anim.setFillAfter(fill_after)
|
||||
|
||||
def setFillBefore(self, fill_before: bool):
|
||||
super().setFillBefore(fill_before)
|
||||
for anim in self._animations:
|
||||
anim.setFillBefore(fill_before)
|
||||
|
||||
def setRepeatMode(self, repeat_mode: int):
|
||||
super().setRepeatMode(repeat_mode)
|
||||
for anim in self._animations:
|
||||
anim.setRepeatMode(repeat_mode)
|
||||
|
||||
def setRepeatCount(self, repeat_count: int):
|
||||
super().setRepeatCount(repeat_count)
|
||||
for anim in self._animations:
|
||||
anim.setRepeatCount(repeat_count)
|
||||
|
||||
def setStartOffset(self, start_offset: int):
|
||||
super().setStartOffset(start_offset)
|
||||
for anim in self._animations:
|
||||
anim.setStartOffset(start_offset)
|
||||
|
||||
def setInterpolator(self, interpolator: Optional[Interpolator]):
|
||||
super().setInterpolator(interpolator)
|
||||
if self._share_interpolator:
|
||||
for anim in self._animations:
|
||||
anim.setInterpolator(interpolator)
|
||||
|
||||
def start(self):
|
||||
super().start()
|
||||
for anim in self._animations:
|
||||
anim.start()
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
for anim in self._animations:
|
||||
anim.reset()
|
||||
|
||||
def cancel(self):
|
||||
super().cancel()
|
||||
for anim in self._animations:
|
||||
anim.cancel()
|
||||
|
||||
def getTransformation(self, current_time: int,
|
||||
out_transformation: Transformation) -> bool:
|
||||
more = False
|
||||
for anim in self._animations:
|
||||
local_t = Transformation()
|
||||
if anim.getTransformation(current_time, local_t):
|
||||
more = True
|
||||
out_transformation.compose(local_t)
|
||||
return more
|
||||
|
||||
def initialize(self, width: int, height: int, parent_width: int, parent_height: int):
|
||||
super().initialize(width, height, parent_width, parent_height)
|
||||
for anim in self._animations:
|
||||
anim.initialize(width, height, parent_width, parent_height)
|
||||
|
||||
def willChangeBounds(self) -> bool:
|
||||
for anim in self._animations:
|
||||
if anim.willChangeBounds():
|
||||
return True
|
||||
return False
|
||||
|
||||
def willChangeTransformationMatrix(self) -> bool:
|
||||
for anim in self._animations:
|
||||
if anim.willChangeTransformationMatrix():
|
||||
return True
|
||||
return False
|
||||
|
||||
def getAnimations(self) -> List[Animation]:
|
||||
return list(self._animations)
|
||||
|
||||
def restrictDuration(self, duration_millis: int):
|
||||
super().restrictDuration(duration_millis)
|
||||
for anim in self._animations:
|
||||
anim.restrictDuration(duration_millis)
|
||||
|
||||
def scaleCurrentDuration(self, scale: float):
|
||||
super().scaleCurrentDuration(scale)
|
||||
for anim in self._animations:
|
||||
anim.scaleCurrentDuration(scale)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Animation.AnimationListener (已定义在 Animation 类中)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
# Animation.AnimationListener is defined inside the Animation class above
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AnimationUtils
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AnimationUtils(Stub):
|
||||
"""AnimationUtils — 动画工具类"""
|
||||
|
||||
@staticmethod
|
||||
def loadAnimation(context: Any, id: int) -> Animation:
|
||||
"""加载动画资源(占位实现)"""
|
||||
sys.stderr.write(f"[AnimationUtils] loadAnimation: id={id}\n")
|
||||
return Animation()
|
||||
|
||||
@staticmethod
|
||||
def makeInAnimation(context: Any, from_left: bool) -> Animation:
|
||||
"""创建进入动画"""
|
||||
anim = AlphaAnimation(0.0, 1.0)
|
||||
anim.setDuration(300)
|
||||
return anim
|
||||
|
||||
@staticmethod
|
||||
def makeOutAnimation(context: Any, to_left: bool) -> Animation:
|
||||
"""创建退出动画"""
|
||||
anim = AlphaAnimation(1.0, 0.0)
|
||||
anim.setDuration(300)
|
||||
return anim
|
||||
|
||||
@staticmethod
|
||||
def makeInChildBottomAnimation(context: Any) -> Animation:
|
||||
"""创建子视图从底部进入动画"""
|
||||
anim = TranslateAnimation(0, 0, 1.0, 0.0,
|
||||
from_x_type=Animation.RELATIVE_TO_SELF,
|
||||
to_x_type=Animation.RELATIVE_TO_SELF,
|
||||
from_y_type=Animation.RELATIVE_TO_SELF,
|
||||
to_y_type=Animation.RELATIVE_TO_SELF)
|
||||
anim.setDuration(300)
|
||||
return anim
|
||||
|
||||
@staticmethod
|
||||
def currentAnimationTimeMillis() -> int:
|
||||
return int(time.time() * 1000)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# LayoutAnimationController
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class LayoutAnimationController(Stub):
|
||||
"""LayoutAnimationController — 布局动画控制器"""
|
||||
|
||||
ORDER_NORMAL = 0
|
||||
ORDER_REVERSE = 1
|
||||
ORDER_RANDOM = 2
|
||||
|
||||
def __init__(self, animation: Optional[Animation] = None):
|
||||
super().__init__()
|
||||
self._animation: Optional[Animation] = animation
|
||||
self._delay: float = 0.5
|
||||
self._order: int = LayoutAnimationController.ORDER_NORMAL
|
||||
self._initialized: bool = False
|
||||
self._max_delay: int = 0
|
||||
|
||||
def setAnimation(self, animation: Animation):
|
||||
self._animation = animation
|
||||
|
||||
def getAnimation(self) -> Optional[Animation]:
|
||||
return self._animation
|
||||
|
||||
def setOrder(self, order: int):
|
||||
self._order = order
|
||||
|
||||
def getOrder(self) -> int:
|
||||
return self._order
|
||||
|
||||
def setDelay(self, delay: float):
|
||||
self._delay = delay
|
||||
|
||||
def getDelay(self) -> float:
|
||||
return self._delay
|
||||
|
||||
def getDelayForView(self, view: Any) -> int:
|
||||
if self._order == LayoutAnimationController.ORDER_NORMAL:
|
||||
return 0
|
||||
elif self._order == LayoutAnimationController.ORDER_REVERSE:
|
||||
return 0
|
||||
elif self._order == LayoutAnimationController.ORDER_RANDOM:
|
||||
import random
|
||||
return int(random.random() * self._max_delay)
|
||||
return 0
|
||||
|
||||
def willOverlap(self) -> bool:
|
||||
return self._delay < 1.0
|
||||
|
||||
def start(self):
|
||||
self._initialized = True
|
||||
|
||||
def isDone(self) -> bool:
|
||||
return True
|
||||
|
||||
def getAnimationForView(self, view: Any) -> Optional[Animation]:
|
||||
return self._animation
|
||||
|
||||
def setInitialized(self, initialized: bool):
|
||||
self._initialized = initialized
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# GridLayoutAnimationController
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class GridLayoutAnimationController(LayoutAnimationController):
|
||||
"""GridLayoutAnimationController — 网格布局动画控制器"""
|
||||
|
||||
DIRECTION_LEFT_TO_RIGHT = 0x0
|
||||
DIRECTION_RIGHT_TO_LEFT = 0x1
|
||||
DIRECTION_TOP_TO_BOTTOM = 0x2
|
||||
DIRECTION_BOTTOM_TO_TOP = 0x3
|
||||
DIRECTION_HORIZONTAL_MASK = 0x1
|
||||
DIRECTION_VERTICAL_MASK = 0x2
|
||||
|
||||
def __init__(self, animation: Optional[Animation] = None):
|
||||
super().__init__(animation)
|
||||
self._column_delay: float = 0.5
|
||||
self._row_delay: float = 0.5
|
||||
self._direction: int = GridLayoutAnimationController.DIRECTION_LEFT_TO_RIGHT
|
||||
self._columns: int = 1
|
||||
self._rows: int = 1
|
||||
|
||||
def setColumnDelay(self, column_delay: float):
|
||||
self._column_delay = column_delay
|
||||
|
||||
def getColumnDelay(self) -> float:
|
||||
return self._column_delay
|
||||
|
||||
def setRowDelay(self, row_delay: float):
|
||||
self._row_delay = row_delay
|
||||
|
||||
def getRowDelay(self) -> float:
|
||||
return self._row_delay
|
||||
|
||||
def setDirection(self, direction: int):
|
||||
self._direction = direction
|
||||
|
||||
def getDirection(self) -> int:
|
||||
return self._direction
|
||||
|
||||
def setColumns(self, columns: int):
|
||||
self._columns = columns
|
||||
|
||||
def getColumns(self) -> int:
|
||||
return self._columns
|
||||
|
||||
def setRows(self, rows: int):
|
||||
self._rows = rows
|
||||
|
||||
def getRows(self) -> int:
|
||||
return self._rows
|
||||
|
||||
def getDelayForView(self, view: Any) -> int:
|
||||
duration = 0
|
||||
if self._animation:
|
||||
duration = self._animation.getDuration()
|
||||
return int(duration * self._column_delay)
|
||||
|
||||
def willOverlap(self) -> bool:
|
||||
return self._column_delay < 1.0 or self._row_delay < 1.0
|
||||
|
||||
def getAnimationForView(self, view: Any) -> Optional[Animation]:
|
||||
return self._animation
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"Animation",
|
||||
"AnimationSet",
|
||||
"AlphaAnimation",
|
||||
"ScaleAnimation",
|
||||
"TranslateAnimation",
|
||||
"RotateAnimation",
|
||||
"AnimationUtils",
|
||||
"Interpolator",
|
||||
"AccelerateInterpolator",
|
||||
"DecelerateInterpolator",
|
||||
"AccelerateDecelerateInterpolator",
|
||||
"LinearInterpolator",
|
||||
"OvershootInterpolator",
|
||||
"AnticipateInterpolator",
|
||||
"AnticipateOvershootInterpolator",
|
||||
"BounceInterpolator",
|
||||
"CycleInterpolator",
|
||||
"PathInterpolator",
|
||||
"Transformation",
|
||||
"LayoutAnimationController",
|
||||
"GridLayoutAnimationController",
|
||||
]
|
||||
@@ -0,0 +1,859 @@
|
||||
"""
|
||||
Android View InputMethod 组件 — InputMethodManager, EditorInfo,
|
||||
InputConnection, ExtractedText 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, android_context
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputMethodManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputMethodManager(Stub):
|
||||
"""InputMethodManager — 输入法管理器"""
|
||||
|
||||
SHOW_IMPLICIT = 0x0001
|
||||
SHOW_FORCED = 0x0002
|
||||
HIDE_IMPLICIT_ONLY = 0x0001
|
||||
HIDE_NOT_ALWAYS = 0x0002
|
||||
RESULT_SHOWN = 2
|
||||
RESULT_HIDDEN = 3
|
||||
RESULT_UNCHANGED_SHOWN = 4
|
||||
RESULT_UNCHANGED_HIDDEN = 5
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._active: bool = False
|
||||
self._current_input_connection: Optional["InputConnection"] = None
|
||||
self._is_accepting_text: bool = True
|
||||
self._fullscreen_mode: bool = False
|
||||
self._cursor_x: int = 0
|
||||
self._cursor_y: int = 0
|
||||
self._selection_start: int = -1
|
||||
self._selection_end: int = -1
|
||||
self._composing_start: int = -1
|
||||
self._composing_end: int = -1
|
||||
|
||||
def showSoftInput(self, view: Any, flags: int,
|
||||
result_receiver: Any = None) -> bool:
|
||||
self._active = True
|
||||
sys.stderr.write(f"[InputMethodManager] showSoftInput: flags={flags}\n")
|
||||
return True
|
||||
|
||||
def hideSoftInputFromWindow(self, window_token: Any, flags: int,
|
||||
result_receiver: Any = None) -> bool:
|
||||
self._active = False
|
||||
sys.stderr.write(f"[InputMethodManager] hideSoftInputFromWindow: flags={flags}\n")
|
||||
return True
|
||||
|
||||
def toggleSoftInput(self, show_flags: int, hide_flags: int):
|
||||
self._active = not self._active
|
||||
sys.stderr.write(f"[InputMethodManager] toggleSoftInput: "
|
||||
f"show={show_flags}, hide={hide_flags}\n")
|
||||
|
||||
def restartInput(self, view: Any):
|
||||
sys.stderr.write("[InputMethodManager] restartInput\n")
|
||||
|
||||
def startInput(self, view: Any) -> bool:
|
||||
sys.stderr.write("[InputMethodManager] startInput\n")
|
||||
return True
|
||||
|
||||
def updateSelection(self, view: Any, sel_start: int, sel_end: int,
|
||||
composing_start: int, composing_end: int):
|
||||
self._selection_start = sel_start
|
||||
self._selection_end = sel_end
|
||||
self._composing_start = composing_start
|
||||
self._composing_end = composing_end
|
||||
|
||||
def updateCursorAnchorInfo(self, view: Any, cursor_anchor_info: "CursorAnchorInfo"):
|
||||
self._cursor_x = cursor_anchor_info.getInsertionMarkerBaseline()
|
||||
self._cursor_y = cursor_anchor_info.getInsertionMarkerTop()
|
||||
|
||||
def displayCompletions(self, view: Any, completions: List["CompletionInfo"]):
|
||||
sys.stderr.write(f"[InputMethodManager] displayCompletions: "
|
||||
f"{len(completions) if completions else 0} items\n")
|
||||
|
||||
def sendKeyChar(self, key_char: str):
|
||||
sys.stderr.write(f"[InputMethodManager] sendKeyChar: '{key_char}'\n")
|
||||
|
||||
def clearMetaKeyStates(self, view: Any, states: int):
|
||||
sys.stderr.write(f"[InputMethodManager] clearMetaKeyStates: {states}\n")
|
||||
|
||||
def isAcceptingText(self) -> bool:
|
||||
return self._is_accepting_text
|
||||
|
||||
def isActive(self) -> bool:
|
||||
return self._active
|
||||
|
||||
def isFullscreenMode(self) -> bool:
|
||||
return self._fullscreen_mode
|
||||
|
||||
def setFullscreenMode(self, fullscreen: bool):
|
||||
self._fullscreen_mode = fullscreen
|
||||
|
||||
def getCurrentInputMethodSubtype(self) -> Optional["InputMethodSubtype"]:
|
||||
return None
|
||||
|
||||
def getShortcutInputMethodsAndSubtypes(self) -> List[Any]:
|
||||
return []
|
||||
|
||||
def showStatusIcon(self, icon: Any):
|
||||
pass
|
||||
|
||||
def hideStatusIcon(self):
|
||||
pass
|
||||
|
||||
def registerSuggestionSpansForNotification(self, spans: List[Any], sender: Any):
|
||||
pass
|
||||
|
||||
def notifySuggestionPicked(self, suggestion: Any):
|
||||
pass
|
||||
|
||||
def windowDismissed(self, window_token: Any):
|
||||
self._active = False
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"InputMethodManager(active={self._active})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# EditorInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class EditorInfo(Stub):
|
||||
"""EditorInfo — 编辑器信息"""
|
||||
|
||||
TYPE_CLASS_TEXT = 0x0001
|
||||
TYPE_CLASS_NUMBER = 0x0002
|
||||
TYPE_CLASS_PHONE = 0x0004
|
||||
TYPE_CLASS_DATETIME = 0x0008
|
||||
|
||||
TYPE_TEXT_VARIATION_NORMAL = 0x0000
|
||||
TYPE_TEXT_VARIATION_URI = 0x0010
|
||||
TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x0020
|
||||
TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x0030
|
||||
TYPE_TEXT_VARIATION_SHORT_MESSAGE = 0x0040
|
||||
TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x0050
|
||||
TYPE_TEXT_VARIATION_PERSON_NAME = 0x0060
|
||||
TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x0070
|
||||
TYPE_TEXT_VARIATION_PASSWORD = 0x0080
|
||||
TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x0090
|
||||
TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00A0
|
||||
TYPE_TEXT_VARIATION_FILTER = 0x00B0
|
||||
TYPE_TEXT_VARIATION_PHONETIC = 0x00C0
|
||||
TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = 0x00D0
|
||||
TYPE_TEXT_VARIATION_WEB_PASSWORD = 0x00E0
|
||||
|
||||
TYPE_NUMBER_VARIATION_NORMAL = 0x0000
|
||||
TYPE_NUMBER_VARIATION_PASSWORD = 0x0010
|
||||
|
||||
TYPE_DATETIME_VARIATION_NORMAL = 0x0000
|
||||
TYPE_DATETIME_VARIATION_DATE = 0x0010
|
||||
TYPE_DATETIME_VARIATION_TIME = 0x0020
|
||||
|
||||
TYPE_NULL = 0x0000
|
||||
|
||||
IME_ACTION_NONE = 0x0001
|
||||
IME_ACTION_GO = 0x0002
|
||||
IME_ACTION_SEARCH = 0x0003
|
||||
IME_ACTION_SEND = 0x0004
|
||||
IME_ACTION_NEXT = 0x0005
|
||||
IME_ACTION_DONE = 0x0006
|
||||
IME_ACTION_PREVIOUS = 0x0007
|
||||
|
||||
IME_FLAG_NO_EXTRACT_UI = 0x10000001
|
||||
IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000
|
||||
IME_FLAG_NO_ENTER_ACTION = 0x40000000
|
||||
IME_FLAG_NAVIGATE_NEXT = 0x08000000
|
||||
IME_FLAG_NAVIGATE_PREVIOUS = 0x04000000
|
||||
IME_FLAG_NO_FULLSCREEN = 0x02000000
|
||||
IME_FLAG_FORCE_ASCII = 0x80000000
|
||||
|
||||
IME_MASK_ACTION = 0x000000FF
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.inputType: int = EditorInfo.TYPE_CLASS_TEXT
|
||||
self.imeOptions: int = EditorInfo.IME_ACTION_NONE
|
||||
self.actionLabel: Optional[str] = None
|
||||
self.actionId: int = 0
|
||||
self.initialSelStart: int = -1
|
||||
self.initialSelEnd: int = -1
|
||||
self.initialCapsMode: int = 0
|
||||
self.hintText: Optional[str] = None
|
||||
self.label: Optional[str] = None
|
||||
self.packageName: Optional[str] = None
|
||||
self.fieldId: int = 0
|
||||
self.fieldName: Optional[str] = None
|
||||
self.extras: Optional[Any] = None
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"EditorInfo(inputType={hex(self.inputType)}, "
|
||||
f"imeOptions={hex(self.imeOptions)}, "
|
||||
f"actionLabel={self.actionLabel})")
|
||||
|
||||
def dump(self, printer: Any, prefix: str):
|
||||
sys.stderr.write(f"{prefix}EditorInfo: inputType={hex(self.inputType)}\n")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputConnection
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputConnection(Stub):
|
||||
"""InputConnection — 输入连接接口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._text: str = ""
|
||||
self._selection_start: int = 0
|
||||
self._selection_end: int = 0
|
||||
self._composing_start: int = -1
|
||||
self._composing_end: int = -1
|
||||
self._batch_edit_count: int = 0
|
||||
|
||||
def getTextBeforeCursor(self, length: int, flags: int) -> Optional[str]:
|
||||
end = self._selection_start
|
||||
start = max(0, end - length)
|
||||
return self._text[start:end]
|
||||
|
||||
def getTextAfterCursor(self, length: int, flags: int) -> Optional[str]:
|
||||
start = self._selection_end
|
||||
end = min(len(self._text), start + length)
|
||||
return self._text[start:end]
|
||||
|
||||
def getExtractedText(self, request: "ExtractedTextRequest",
|
||||
flags: int) -> Optional["ExtractedText"]:
|
||||
result = ExtractedText()
|
||||
result.text = self._text
|
||||
result.startOffset = 0
|
||||
result.selectionStart = self._selection_start
|
||||
result.selectionEnd = self._selection_end
|
||||
result.flags = 0
|
||||
return result
|
||||
|
||||
def commitText(self, text: Any, new_cursor_position: int) -> bool:
|
||||
if isinstance(text, str):
|
||||
committed = text
|
||||
else:
|
||||
committed = str(text) if text else ""
|
||||
|
||||
before = self._text[:self._selection_start]
|
||||
after = self._text[self._selection_end:]
|
||||
self._text = before + committed + after
|
||||
|
||||
if new_cursor_position > 0:
|
||||
self._selection_start = len(before) + new_cursor_position - 1
|
||||
else:
|
||||
self._selection_start = len(before) + len(committed) + new_cursor_position
|
||||
self._selection_end = self._selection_start
|
||||
self._composing_start = -1
|
||||
self._composing_end = -1
|
||||
return True
|
||||
|
||||
def setComposingText(self, text: Any, new_cursor_position: int) -> bool:
|
||||
if self._composing_start < 0:
|
||||
self._composing_start = self._selection_start
|
||||
self._composing_end = self._selection_end
|
||||
|
||||
before = self._text[:self._composing_start]
|
||||
after = self._text[self._composing_end:]
|
||||
|
||||
if isinstance(text, str):
|
||||
composing = text
|
||||
else:
|
||||
composing = str(text) if text else ""
|
||||
|
||||
self._text = before + composing + after
|
||||
self._composing_end = self._composing_start + len(composing)
|
||||
self._selection_start = self._composing_end + new_cursor_position
|
||||
self._selection_end = self._selection_start
|
||||
return True
|
||||
|
||||
def setSelection(self, start: int, end: int) -> bool:
|
||||
self._selection_start = max(0, min(start, len(self._text)))
|
||||
self._selection_end = max(0, min(end, len(self._text)))
|
||||
return True
|
||||
|
||||
def deleteSurroundingText(self, before_length: int, after_length: int) -> bool:
|
||||
start = max(0, self._selection_start - before_length)
|
||||
end = min(len(self._text), self._selection_end + after_length)
|
||||
self._text = self._text[:start] + self._text[end:]
|
||||
new_cursor = max(0, self._selection_start - before_length)
|
||||
self._selection_start = new_cursor
|
||||
self._selection_end = new_cursor
|
||||
return True
|
||||
|
||||
def commitCompletion(self, completion: "CompletionInfo") -> bool:
|
||||
sys.stderr.write(f"[InputConnection] commitCompletion: "
|
||||
f"{completion.getText()}\n")
|
||||
return True
|
||||
|
||||
def commitCorrection(self, correction: "CorrectionInfo") -> bool:
|
||||
sys.stderr.write(f"[InputConnection] commitCorrection: "
|
||||
f"{correction.getNewText()}\n")
|
||||
return True
|
||||
|
||||
def getSelectedText(self, flags: int) -> Optional[str]:
|
||||
if self._selection_start == self._selection_end:
|
||||
return ""
|
||||
return self._text[self._selection_start:self._selection_end]
|
||||
|
||||
def finishComposingText(self) -> bool:
|
||||
self._composing_start = -1
|
||||
self._composing_end = -1
|
||||
return True
|
||||
|
||||
def beginBatchEdit(self) -> bool:
|
||||
self._batch_edit_count += 1
|
||||
return True
|
||||
|
||||
def endBatchEdit(self) -> bool:
|
||||
if self._batch_edit_count > 0:
|
||||
self._batch_edit_count -= 1
|
||||
return True
|
||||
|
||||
def sendKeyEvent(self, event: Any) -> bool:
|
||||
sys.stderr.write(f"[InputConnection] sendKeyEvent\n")
|
||||
return True
|
||||
|
||||
def clearMetaKeyStates(self, states: int) -> bool:
|
||||
return True
|
||||
|
||||
def performEditorAction(self, action_code: int) -> bool:
|
||||
sys.stderr.write(f"[InputConnection] performEditorAction: {action_code}\n")
|
||||
return True
|
||||
|
||||
def performContextMenuAction(self, id: int) -> bool:
|
||||
sys.stderr.write(f"[InputConnection] performContextMenuAction: {id}\n")
|
||||
return True
|
||||
|
||||
def commitContent(self, input_content: "InputContentInfo",
|
||||
flags: int, opts: Optional[Any] = None) -> bool:
|
||||
sys.stderr.write("[InputConnection] commitContent\n")
|
||||
return True
|
||||
|
||||
def requestCursorUpdates(self, cursor_update_mode: int) -> bool:
|
||||
return True
|
||||
|
||||
def closeConnection(self):
|
||||
pass
|
||||
|
||||
def reportFullscreenMode(self, enabled: bool):
|
||||
pass
|
||||
|
||||
def getSurroundingText(self, before_length: int, after_length: int,
|
||||
flags: int) -> Optional["SurroundingText"]:
|
||||
start = max(0, self._selection_start - before_length)
|
||||
end = min(len(self._text), self._selection_end + after_length)
|
||||
return SurroundingText(
|
||||
text=self._text[start:end],
|
||||
selection_start=self._selection_start - start,
|
||||
selection_end=self._selection_end - start,
|
||||
offset=start,
|
||||
)
|
||||
|
||||
def takeSnapshot(self) -> Optional[Any]:
|
||||
"""获取输入快照"""
|
||||
return None
|
||||
|
||||
def setComposingRegion(self, start: int, end: int) -> bool:
|
||||
self._composing_start = start
|
||||
self._composing_end = end
|
||||
return True
|
||||
|
||||
def getCursorCapsMode(self, req_modes: int) -> int:
|
||||
return 0
|
||||
|
||||
def getTextBeforeCursor(self, length: int, flags: int) -> Optional[str]:
|
||||
end = self._selection_start
|
||||
start = max(0, end - length)
|
||||
return self._text[start:end]
|
||||
|
||||
def getTextAfterCursor(self, length: int, flags: int) -> Optional[str]:
|
||||
start = self._selection_end
|
||||
end = min(len(self._text), start + length)
|
||||
return self._text[start:end]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ExtractedText
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ExtractedText(Stub):
|
||||
"""ExtractedText — 提取的文本"""
|
||||
|
||||
FLAG_SINGLE_LINE = 0x0001
|
||||
FLAG_SELECTING = 0x0002
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.text: Optional[str] = None
|
||||
self.startOffset: int = 0
|
||||
self.partialStartOffset: int = -1
|
||||
self.partialEndOffset: int = -1
|
||||
self.selectionStart: int = -1
|
||||
self.selectionEnd: int = -1
|
||||
self.flags: int = 0
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"ExtractedText(startOffset={self.startOffset}, "
|
||||
f"text={self.text}, "
|
||||
f"selection=({self.selectionStart}, {self.selectionEnd}))")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ExtractedTextRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ExtractedTextRequest(Stub):
|
||||
"""ExtractedTextRequest — 提取文本请求"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.token: int = 0
|
||||
self.flags: int = 0
|
||||
self.hintMaxLines: int = 0
|
||||
self.hintMaxChars: int = 0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputBinding
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputBinding(Stub):
|
||||
"""InputBinding — 输入绑定"""
|
||||
|
||||
def __init__(self, connection: Optional["InputConnection"] = None,
|
||||
uid: int = -1, pid: int = -1,
|
||||
private_ime_options: str = ""):
|
||||
super().__init__()
|
||||
self._connection: Optional["InputConnection"] = connection
|
||||
self._uid: int = uid
|
||||
self._pid: int = pid
|
||||
self._private_ime_options: str = private_ime_options
|
||||
|
||||
def getConnection(self) -> Optional["InputConnection"]:
|
||||
return self._connection
|
||||
|
||||
def getUid(self) -> int:
|
||||
return self._uid
|
||||
|
||||
def getPid(self) -> int:
|
||||
return self._pid
|
||||
|
||||
def getPrivateImeOptions(self) -> str:
|
||||
return self._private_ime_options
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"InputBinding(uid={self._uid}, pid={self._pid}, "
|
||||
f"connection={self._connection})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputMethodSubtype
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputMethodSubtype(Stub):
|
||||
"""InputMethodSubtype — 输入法子类型"""
|
||||
|
||||
SUBTYPE_ID_NONE = -1
|
||||
|
||||
def __init__(self, locale: str = "", mode: str = "",
|
||||
extra_value: str = "", is_auxiliary: bool = False,
|
||||
overrides_ime_theme: bool = False,
|
||||
subtype_id: int = SUBTYPE_ID_NONE):
|
||||
super().__init__()
|
||||
self._locale: str = locale
|
||||
self._mode: str = mode
|
||||
self._extra_value: str = extra_value
|
||||
self._is_auxiliary: bool = is_auxiliary
|
||||
self._overrides_ime_theme: bool = overrides_ime_theme
|
||||
self._subtype_id: int = subtype_id
|
||||
self._display_name_res: int = 0
|
||||
self._icon_res: int = 0
|
||||
|
||||
def getLocale(self) -> str:
|
||||
return self._locale
|
||||
|
||||
def getMode(self) -> str:
|
||||
return self._mode
|
||||
|
||||
def getExtraValue(self) -> str:
|
||||
return self._extra_value
|
||||
|
||||
def getSubtypeId(self) -> int:
|
||||
return self._subtype_id
|
||||
|
||||
def containsExtraValueKey(self, key: str) -> bool:
|
||||
if not self._extra_value:
|
||||
return False
|
||||
pairs = self._extra_value.split(",")
|
||||
for pair in pairs:
|
||||
kv = pair.split("=", 1)
|
||||
if len(kv) == 2 and kv[0].strip() == key:
|
||||
return True
|
||||
return False
|
||||
|
||||
def getDisplayName(self, context: Any, package_name: str,
|
||||
app_info: Any) -> str:
|
||||
return self._locale or self._mode
|
||||
|
||||
def isAuxiliary(self) -> bool:
|
||||
return self._is_auxiliary
|
||||
|
||||
def overridesImeTheme(self) -> bool:
|
||||
return self._overrides_ime_theme
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._locale, self._mode, self._extra_value)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other: Any) -> bool:
|
||||
if isinstance(other, InputMethodSubtype):
|
||||
return (self._locale == other._locale and
|
||||
self._mode == other._mode and
|
||||
self._extra_value == other._extra_value)
|
||||
return False
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"InputMethodSubtype(locale={self._locale}, "
|
||||
f"mode={self._mode})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CursorAnchorInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CursorAnchorInfo(Stub):
|
||||
"""CursorAnchorInfo — 光标锚点信息"""
|
||||
|
||||
FLAG_HAS_VISIBLE_REGION = 0x01
|
||||
FLAG_IS_RTL = 0x02
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._insertion_marker_baseline: float = 0.0
|
||||
self._insertion_marker_top: float = 0.0
|
||||
self._insertion_marker_bottom: float = 0.0
|
||||
self._matrix: Optional[Any] = None
|
||||
self._character_bounds: Dict[int, Tuple[float, float, float, float]] = {}
|
||||
self._character_bounds_flags: Dict[int, int] = {}
|
||||
self._composing_text: Optional[str] = None
|
||||
self._composing_text_start: int = -1
|
||||
|
||||
def getInsertionMarkerBaseline(self) -> float:
|
||||
return self._insertion_marker_baseline
|
||||
|
||||
def setInsertionMarkerBaseline(self, baseline: float):
|
||||
self._insertion_marker_baseline = baseline
|
||||
|
||||
def getInsertionMarkerTop(self) -> float:
|
||||
return self._insertion_marker_top
|
||||
|
||||
def setInsertionMarkerTop(self, top: float):
|
||||
self._insertion_marker_top = top
|
||||
|
||||
def getInsertionMarkerBottom(self) -> float:
|
||||
return self._insertion_marker_bottom
|
||||
|
||||
def setInsertionMarkerBottom(self, bottom: float):
|
||||
self._insertion_marker_bottom = bottom
|
||||
|
||||
def getMatrix(self) -> Optional[Any]:
|
||||
return self._matrix
|
||||
|
||||
def setMatrix(self, matrix: Any):
|
||||
self._matrix = matrix
|
||||
|
||||
def getCharacterBounds(self, index: int) -> Optional[Tuple[float, float, float, float]]:
|
||||
return self._character_bounds.get(index)
|
||||
|
||||
def setCharacterBounds(self, index: int, left: float, top: float,
|
||||
right: float, bottom: float, flags: int):
|
||||
self._character_bounds[index] = (left, top, right, bottom)
|
||||
self._character_bounds_flags[index] = flags
|
||||
|
||||
def getCharacterBoundsFlags(self, index: int) -> int:
|
||||
return self._character_bounds_flags.get(index, 0)
|
||||
|
||||
def getComposingText(self) -> Optional[str]:
|
||||
return self._composing_text
|
||||
|
||||
def getComposingTextStart(self) -> int:
|
||||
return self._composing_text_start
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"CursorAnchorInfo(baseline={self._insertion_marker_baseline}, "
|
||||
f"top={self._insertion_marker_top})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CorrectionInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CorrectionInfo(Stub):
|
||||
"""CorrectionInfo — 校正信息"""
|
||||
|
||||
def __init__(self, old_text: str = "", new_text: str = ""):
|
||||
super().__init__()
|
||||
self._old_text: str = old_text
|
||||
self._new_text: str = new_text
|
||||
|
||||
def getOldText(self) -> str:
|
||||
return self._old_text
|
||||
|
||||
def getNewText(self) -> str:
|
||||
return self._new_text
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CorrectionInfo('{self._old_text}' -> '{self._new_text}')"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CompletionInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CompletionInfo(Stub):
|
||||
"""CompletionInfo — 补全信息"""
|
||||
|
||||
def __init__(self, id: int = 0, position: int = 0,
|
||||
text: str = "", label: str = ""):
|
||||
super().__init__()
|
||||
self._id: int = id
|
||||
self._position: int = position
|
||||
self._text: str = text
|
||||
self._label: str = label
|
||||
|
||||
def getId(self) -> int:
|
||||
return self._id
|
||||
|
||||
def getPosition(self) -> int:
|
||||
return self._position
|
||||
|
||||
def getText(self) -> str:
|
||||
return self._text
|
||||
|
||||
def getLabel(self) -> str:
|
||||
return self._label
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CompletionInfo(id={self._id}, text='{self._text}')"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SurroundingText
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SurroundingText(Stub):
|
||||
"""SurroundingText — 周围文本"""
|
||||
|
||||
def __init__(self, text: str = "", selection_start: int = 0,
|
||||
selection_end: int = 0, offset: int = 0):
|
||||
super().__init__()
|
||||
self._text: str = text
|
||||
self._selection_start: int = selection_start
|
||||
self._selection_end: int = selection_end
|
||||
self._offset: int = offset
|
||||
|
||||
def getText(self) -> str:
|
||||
return self._text
|
||||
|
||||
def getSelectionStart(self) -> int:
|
||||
return self._selection_start
|
||||
|
||||
def getSelectionEnd(self) -> int:
|
||||
return self._selection_end
|
||||
|
||||
def getOffset(self) -> int:
|
||||
return self._offset
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"SurroundingText(text='{self._text}', "
|
||||
f"selection=({self._selection_start}, {self._selection_end}), "
|
||||
f"offset={self._offset})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputContentInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputContentInfo(Stub):
|
||||
"""InputContentInfo — 输入内容信息"""
|
||||
|
||||
def __init__(self, uri: Any = None, description: Any = None,
|
||||
link_uri: Any = None):
|
||||
super().__init__()
|
||||
self._uri: Any = uri
|
||||
self._description: Any = description
|
||||
self._link_uri: Any = link_uri
|
||||
|
||||
def getUri(self) -> Any:
|
||||
return self._uri
|
||||
|
||||
def getDescription(self) -> Any:
|
||||
return self._description
|
||||
|
||||
def getLinkUri(self) -> Any:
|
||||
return self._link_uri
|
||||
|
||||
def requestPermission(self):
|
||||
pass
|
||||
|
||||
def releasePermission(self):
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"InputContentInfo(uri={self._uri}, "
|
||||
f"description={self._description})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputConnectionWrapper
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputConnectionWrapper(InputConnection):
|
||||
"""InputConnectionWrapper — 输入连接包装器"""
|
||||
|
||||
def __init__(self, target: InputConnection, mutable: bool = True):
|
||||
super().__init__()
|
||||
self._target: InputConnection = target
|
||||
self._mutable: bool = mutable
|
||||
|
||||
def setTarget(self, target: InputConnection):
|
||||
if self._mutable:
|
||||
self._target = target
|
||||
|
||||
def getTarget(self) -> InputConnection:
|
||||
return self._target
|
||||
|
||||
def getTextBeforeCursor(self, length: int, flags: int) -> Optional[str]:
|
||||
return self._target.getTextBeforeCursor(length, flags)
|
||||
|
||||
def getTextAfterCursor(self, length: int, flags: int) -> Optional[str]:
|
||||
return self._target.getTextAfterCursor(length, flags)
|
||||
|
||||
def getSelectedText(self, flags: int) -> Optional[str]:
|
||||
return self._target.getSelectedText(flags)
|
||||
|
||||
def getSurroundingText(self, before_length: int, after_length: int,
|
||||
flags: int) -> Optional["SurroundingText"]:
|
||||
return self._target.getSurroundingText(before_length, after_length, flags)
|
||||
|
||||
def getCursorCapsMode(self, req_modes: int) -> int:
|
||||
return self._target.getCursorCapsMode(req_modes)
|
||||
|
||||
def getExtractedText(self, request: "ExtractedTextRequest",
|
||||
flags: int) -> Optional["ExtractedText"]:
|
||||
return self._target.getExtractedText(request, flags)
|
||||
|
||||
def commitText(self, text: Any, new_cursor_position: int) -> bool:
|
||||
return self._target.commitText(text, new_cursor_position)
|
||||
|
||||
def commitCompletion(self, completion: "CompletionInfo") -> bool:
|
||||
return self._target.commitCompletion(completion)
|
||||
|
||||
def commitCorrection(self, correction: "CorrectionInfo") -> bool:
|
||||
return self._target.commitCorrection(correction)
|
||||
|
||||
def commitContent(self, input_content: "InputContentInfo",
|
||||
flags: int, opts: Optional[Any] = None) -> bool:
|
||||
return self._target.commitContent(input_content, flags, opts)
|
||||
|
||||
def setComposingText(self, text: Any, new_cursor_position: int) -> bool:
|
||||
return self._target.setComposingText(text, new_cursor_position)
|
||||
|
||||
def setComposingRegion(self, start: int, end: int) -> bool:
|
||||
return self._target.setComposingRegion(start, end)
|
||||
|
||||
def finishComposingText(self) -> bool:
|
||||
return self._target.finishComposingText()
|
||||
|
||||
def setSelection(self, start: int, end: int) -> bool:
|
||||
return self._target.setSelection(start, end)
|
||||
|
||||
def performEditorAction(self, action_code: int) -> bool:
|
||||
return self._target.performEditorAction(action_code)
|
||||
|
||||
def performContextMenuAction(self, id: int) -> bool:
|
||||
return self._target.performContextMenuAction(id)
|
||||
|
||||
def beginBatchEdit(self) -> bool:
|
||||
return self._target.beginBatchEdit()
|
||||
|
||||
def endBatchEdit(self) -> bool:
|
||||
return self._target.endBatchEdit()
|
||||
|
||||
def sendKeyEvent(self, event: Any) -> bool:
|
||||
return self._target.sendKeyEvent(event)
|
||||
|
||||
def clearMetaKeyStates(self, states: int) -> bool:
|
||||
return self._target.clearMetaKeyStates(states)
|
||||
|
||||
def deleteSurroundingText(self, before_length: int, after_length: int) -> bool:
|
||||
return self._target.deleteSurroundingText(before_length, after_length)
|
||||
|
||||
def requestCursorUpdates(self, cursor_update_mode: int) -> bool:
|
||||
return self._target.requestCursorUpdates(cursor_update_mode)
|
||||
|
||||
def closeConnection(self):
|
||||
self._target.closeConnection()
|
||||
|
||||
def reportFullscreenMode(self, enabled: bool):
|
||||
self._target.reportFullscreenMode(enabled)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BaseInputConnection
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BaseInputConnection(InputConnection):
|
||||
"""BaseInputConnection — 输入连接基类"""
|
||||
|
||||
def __init__(self, view: Any, full_editor: bool = True):
|
||||
super().__init__()
|
||||
self._view = view
|
||||
self._full_editor = full_editor
|
||||
self._text: str = ""
|
||||
self._selection_start: int = 0
|
||||
self._selection_end: int = 0
|
||||
self._composing_start: int = -1
|
||||
self._composing_end: int = -1
|
||||
|
||||
def getEditable(self) -> Optional[Any]:
|
||||
return None
|
||||
|
||||
def reportFullscreenMode(self, enabled: bool):
|
||||
pass
|
||||
|
||||
def requestCursorUpdates(self, cursor_update_mode: int) -> bool:
|
||||
return self._full_editor
|
||||
|
||||
def closeConnection(self):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"InputMethodManager",
|
||||
"EditorInfo",
|
||||
"InputConnection",
|
||||
"ExtractedText",
|
||||
"ExtractedTextRequest",
|
||||
"InputBinding",
|
||||
"InputMethodSubtype",
|
||||
"CursorAnchorInfo",
|
||||
"CorrectionInfo",
|
||||
"CompletionInfo",
|
||||
"SurroundingText",
|
||||
"InputContentInfo",
|
||||
"InputConnectionWrapper",
|
||||
"BaseInputConnection",
|
||||
]
|
||||
@@ -0,0 +1,992 @@
|
||||
"""
|
||||
Android WebKit 组件 — WebView, WebSettings, WebViewClient, WebChromeClient 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from .._core import (
|
||||
Stub, AndroidOnlyError, android_context,
|
||||
resolve_android_path, to_android_path,
|
||||
_sdcard_root, _app_data_dir, _package_name,
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WebResourceRequest / WebResourceResponse / WebResourceError
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WebResourceRequest(Stub):
|
||||
"""WebResourceRequest — 资源请求"""
|
||||
|
||||
def __init__(self, url: str = "", is_for_main_frame: bool = False,
|
||||
is_redirect: bool = False, method: str = "GET",
|
||||
request_headers: Optional[Dict[str, str]] = None):
|
||||
super().__init__()
|
||||
self._url = url
|
||||
self._is_for_main_frame = is_for_main_frame
|
||||
self._is_redirect = is_redirect
|
||||
self._method = method
|
||||
self._request_headers = request_headers or {}
|
||||
|
||||
def getUrl(self) -> str:
|
||||
return self._url
|
||||
|
||||
def isForMainFrame(self) -> bool:
|
||||
return self._is_for_main_frame
|
||||
|
||||
def isRedirect(self) -> bool:
|
||||
return self._is_redirect
|
||||
|
||||
def getMethod(self) -> str:
|
||||
return self._method
|
||||
|
||||
def getRequestHeaders(self) -> Dict[str, str]:
|
||||
return dict(self._request_headers)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"WebResourceRequest(url={self._url})"
|
||||
|
||||
|
||||
class WebResourceResponse(Stub):
|
||||
"""WebResourceResponse — 资源响应"""
|
||||
|
||||
def __init__(self, mime_type: str = "", encoding: str = "",
|
||||
data: Any = None, status_code: int = 200,
|
||||
reason_phrase: str = "OK",
|
||||
response_headers: Optional[Dict[str, str]] = None):
|
||||
super().__init__()
|
||||
self._mime_type = mime_type
|
||||
self._encoding = encoding
|
||||
self._data = data
|
||||
self._status_code = status_code
|
||||
self._reason_phrase = reason_phrase
|
||||
self._response_headers = response_headers or {}
|
||||
|
||||
def getMimeType(self) -> str:
|
||||
return self._mime_type
|
||||
|
||||
def getEncoding(self) -> str:
|
||||
return self._encoding
|
||||
|
||||
def getData(self) -> Any:
|
||||
return self._data
|
||||
|
||||
def getStatusCode(self) -> int:
|
||||
return self._status_code
|
||||
|
||||
def getReasonPhrase(self) -> str:
|
||||
return self._reason_phrase
|
||||
|
||||
def getResponseHeaders(self) -> Dict[str, str]:
|
||||
return dict(self._response_headers)
|
||||
|
||||
|
||||
class WebResourceError(Stub):
|
||||
"""WebResourceError — 资源错误"""
|
||||
|
||||
ERROR_UNKNOWN = -1
|
||||
ERROR_HOST_LOOKUP = -2
|
||||
ERROR_UNSUPPORTED_AUTH_SCHEME = -3
|
||||
ERROR_AUTHENTICATION = -4
|
||||
ERROR_PROXY_AUTHENTICATION = -5
|
||||
ERROR_CONNECT = -6
|
||||
ERROR_IO = -7
|
||||
ERROR_TIMEOUT = -8
|
||||
ERROR_REDIRECT_LOOP = -9
|
||||
ERROR_UNSUPPORTED_SCHEME = -10
|
||||
ERROR_FAILED_SSL_HANDSHAKE = -11
|
||||
ERROR_BAD_URL = -12
|
||||
ERROR_FILE = -13
|
||||
ERROR_FILE_NOT_FOUND = -14
|
||||
ERROR_TOO_MANY_REQUESTS = -15
|
||||
|
||||
def __init__(self, error_code: int = ERROR_UNKNOWN, description: str = ""):
|
||||
super().__init__()
|
||||
self._error_code = error_code
|
||||
self._description = description
|
||||
|
||||
def getErrorCode(self) -> int:
|
||||
return self._error_code
|
||||
|
||||
def getDescription(self) -> str:
|
||||
return self._description
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WebMessage / WebMessagePort
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WebMessage(Stub):
|
||||
"""WebMessage — Web 消息"""
|
||||
|
||||
def __init__(self, data: str = ""):
|
||||
super().__init__()
|
||||
self._data = data
|
||||
|
||||
def getData(self) -> str:
|
||||
return self._data
|
||||
|
||||
|
||||
class WebMessagePort(Stub):
|
||||
"""WebMessagePort — Web 消息端口"""
|
||||
|
||||
def postMessage(self, message: WebMessage):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def setWebMessageCallback(self, callback: Any):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# JavascriptInterface
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class JavascriptInterface(Stub):
|
||||
"""JavascriptInterface — JavaScript 接口注解"""
|
||||
|
||||
def __init__(self, value: str = ""):
|
||||
super().__init__()
|
||||
self._value = value
|
||||
|
||||
def getValue(self) -> str:
|
||||
return self._value
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# JsResult / JsPromptResult
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class JsResult(Stub):
|
||||
"""JsResult — JS 对话框结果"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._confirmed = False
|
||||
|
||||
def confirm(self):
|
||||
self._confirmed = True
|
||||
|
||||
def cancel(self):
|
||||
self._confirmed = False
|
||||
|
||||
|
||||
class JsPromptResult(JsResult):
|
||||
"""JsPromptResult — JS 提示结果"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._string_result: str = ""
|
||||
|
||||
def confirm(self, result: str = ""):
|
||||
self._confirmed = True
|
||||
self._string_result = result
|
||||
|
||||
def getStringResult(self) -> str:
|
||||
return self._string_result
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# HttpAuthHandler / SslErrorHandler
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class HttpAuthHandler(Stub):
|
||||
"""HttpAuthHandler — HTTP 认证处理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._proceeded = False
|
||||
self._cancelled = False
|
||||
|
||||
def proceed(self, username: str, password: str):
|
||||
self._proceeded = True
|
||||
|
||||
def cancel(self):
|
||||
self._cancelled = True
|
||||
|
||||
def useHttpAuthUsernamePassword(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class SslErrorHandler(Stub):
|
||||
"""SslErrorHandler — SSL 错误处理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._proceeded = False
|
||||
self._cancelled = False
|
||||
|
||||
def proceed(self):
|
||||
self._proceeded = True
|
||||
|
||||
def cancel(self):
|
||||
self._cancelled = True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PermissionRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PermissionRequest(Stub):
|
||||
"""PermissionRequest — 权限请求"""
|
||||
|
||||
RESOURCE_VIDEO_CAPTURE = "android.webkit.resource.VIDEO_CAPTURE"
|
||||
RESOURCE_AUDIO_CAPTURE = "android.webkit.resource.AUDIO_CAPTURE"
|
||||
RESOURCE_PROTECTED_MEDIA_ID = "android.webkit.resource.PROTECTED_MEDIA_ID"
|
||||
RESOURCE_MIDI_SYSEX = "android.webkit.resource.MIDI_SYSEX"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._origin: Any = None
|
||||
self._resources: List[str] = []
|
||||
self._granted_resources: List[str] = []
|
||||
|
||||
def getOrigin(self) -> Any:
|
||||
return self._origin
|
||||
|
||||
def getResources(self) -> List[str]:
|
||||
return list(self._resources)
|
||||
|
||||
def getGrantedResources(self) -> List[str]:
|
||||
return list(self._granted_resources)
|
||||
|
||||
def grant(self, resources: List[str]):
|
||||
self._granted_resources = list(resources)
|
||||
|
||||
def deny(self):
|
||||
self._granted_resources.clear()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# GeolocationPermissions / WebStorage
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class GeolocationPermissions(Stub):
|
||||
"""GeolocationPermissions — 地理位置权限"""
|
||||
|
||||
@staticmethod
|
||||
def getInstance() -> "GeolocationPermissions":
|
||||
return GeolocationPermissions()
|
||||
|
||||
def allow(self, origin: str):
|
||||
pass
|
||||
|
||||
def clear(self, origin: str):
|
||||
pass
|
||||
|
||||
def clearAll(self):
|
||||
pass
|
||||
|
||||
def getAllowed(self, origin: str, callback: Any):
|
||||
pass
|
||||
|
||||
def getOrigins(self, callback: Any):
|
||||
pass
|
||||
|
||||
|
||||
class WebStorage(Stub):
|
||||
"""WebStorage — Web 存储"""
|
||||
|
||||
@staticmethod
|
||||
def getInstance() -> "WebStorage":
|
||||
return WebStorage()
|
||||
|
||||
def deleteAllData(self):
|
||||
pass
|
||||
|
||||
def deleteOrigin(self, origin: str):
|
||||
pass
|
||||
|
||||
def getOrigins(self, callback: Any):
|
||||
pass
|
||||
|
||||
def getQuotaForOrigin(self, origin: str, callback: Any):
|
||||
pass
|
||||
|
||||
def getUsageForOrigin(self, origin: str, callback: Any):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WebBackForwardList / WebHistoryItem
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WebHistoryItem(Stub):
|
||||
"""WebHistoryItem — 历史项"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._url: str = ""
|
||||
self._original_url: str = ""
|
||||
self._title: str = ""
|
||||
self._favicon: Any = None
|
||||
|
||||
def getUrl(self) -> str:
|
||||
return self._url
|
||||
|
||||
def getOriginalUrl(self) -> str:
|
||||
return self._original_url
|
||||
|
||||
def getTitle(self) -> str:
|
||||
return self._title
|
||||
|
||||
def getFavicon(self) -> Any:
|
||||
return self._favicon
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"WebHistoryItem(url={self._url})"
|
||||
|
||||
|
||||
class WebBackForwardList(Stub):
|
||||
"""WebBackForwardList — 前进后退列表"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._items: List[WebHistoryItem] = []
|
||||
self._current_index: int = -1
|
||||
|
||||
def getCurrentItem(self) -> Optional[WebHistoryItem]:
|
||||
if 0 <= self._current_index < len(self._items):
|
||||
return self._items[self._current_index]
|
||||
return None
|
||||
|
||||
def getCurrentIndex(self) -> int:
|
||||
return self._current_index
|
||||
|
||||
def getItemAtIndex(self, index: int) -> Optional[WebHistoryItem]:
|
||||
if 0 <= index < len(self._items):
|
||||
return self._items[index]
|
||||
return None
|
||||
|
||||
def getSize(self) -> int:
|
||||
return len(self._items)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CookieManager / CookieSyncManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CookieManager(Stub):
|
||||
"""CookieManager — Cookie 管理器"""
|
||||
|
||||
_instance: Optional["CookieManager"] = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._accept_cookies: bool = True
|
||||
self._accept_third_party: bool = True
|
||||
self._cookies: Dict[str, str] = {}
|
||||
|
||||
@staticmethod
|
||||
def getInstance() -> "CookieManager":
|
||||
if CookieManager._instance is None:
|
||||
CookieManager._instance = CookieManager()
|
||||
return CookieManager._instance
|
||||
|
||||
def setAcceptCookies(self, accept: bool):
|
||||
self._accept_cookies = accept
|
||||
|
||||
def acceptCookies(self) -> bool:
|
||||
return self._accept_cookies
|
||||
|
||||
def setCookie(self, url: str, value: str):
|
||||
if self._accept_cookies:
|
||||
self._cookies[url] = value
|
||||
|
||||
def getCookie(self, url: str) -> Optional[str]:
|
||||
return self._cookies.get(url)
|
||||
|
||||
def setAcceptThirdPartyCookies(self, webview: Any, accept: bool):
|
||||
self._accept_third_party = accept
|
||||
|
||||
def acceptThirdPartyCookies(self) -> bool:
|
||||
return self._accept_third_party
|
||||
|
||||
def removeSessionCookie(self):
|
||||
pass
|
||||
|
||||
def removeAllCookies(self):
|
||||
self._cookies.clear()
|
||||
|
||||
def removeAllCookiesSync(self):
|
||||
self._cookies.clear()
|
||||
|
||||
def removeSessionCookies(self):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def hasCookies(self) -> bool:
|
||||
return bool(self._cookies)
|
||||
|
||||
def removeExpiredCookie(self):
|
||||
pass
|
||||
|
||||
|
||||
class CookieSyncManager(Stub):
|
||||
"""CookieSyncManager — Cookie 同步管理器"""
|
||||
|
||||
_instance: Optional["CookieSyncManager"] = None
|
||||
|
||||
@staticmethod
|
||||
def createInstance(context: Any) -> "CookieSyncManager":
|
||||
if CookieSyncManager._instance is None:
|
||||
CookieSyncManager._instance = CookieSyncManager()
|
||||
return CookieSyncManager._instance
|
||||
|
||||
@staticmethod
|
||||
def getInstance() -> "CookieSyncManager":
|
||||
if CookieSyncManager._instance is None:
|
||||
CookieSyncManager._instance = CookieSyncManager()
|
||||
return CookieSyncManager._instance
|
||||
|
||||
def sync(self):
|
||||
pass
|
||||
|
||||
def startSync(self):
|
||||
pass
|
||||
|
||||
def stopSync(self):
|
||||
pass
|
||||
|
||||
def resetSync(self):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WebSettings
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WebSettings(Stub):
|
||||
"""WebSettings — WebView 设置"""
|
||||
|
||||
LOAD_DEFAULT = -1
|
||||
LOAD_CACHE_ELSE_NETWORK = 1
|
||||
LOAD_NO_CACHE = 2
|
||||
LOAD_CACHE_ONLY = 3
|
||||
|
||||
MIXED_CONTENT_ALWAYS_ALLOW = 0
|
||||
MIXED_CONTENT_NEVER_ALLOW = 1
|
||||
MIXED_CONTENT_COMPATIBILITY_MODE = 2
|
||||
|
||||
class LayoutAlgorithm(Stub):
|
||||
NORMAL = 0
|
||||
SINGLE_COLUMN = 1
|
||||
NARROW_COLUMNS = 2
|
||||
TEXT_AUTOSIZING = 3
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._js_enabled: bool = True
|
||||
self._dom_storage_enabled: bool = True
|
||||
self._allow_file_access: bool = True
|
||||
self._allow_content_access: bool = True
|
||||
self._allow_file_access_from_urls: bool = False
|
||||
self._allow_universal_access_from_urls: bool = False
|
||||
self._built_in_zoom_controls: bool = True
|
||||
self._display_zoom_controls: bool = True
|
||||
self._support_zoom: bool = True
|
||||
self._load_with_overview_mode: bool = False
|
||||
self._use_wide_viewport: bool = False
|
||||
self._user_agent_string: str = "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36"
|
||||
self._default_text_encoding: str = "UTF-8"
|
||||
self._cache_mode: int = self.LOAD_DEFAULT
|
||||
self._app_cache_enabled: bool = False
|
||||
self._app_cache_path: str = ""
|
||||
self._database_enabled: bool = False
|
||||
self._geolocation_enabled: bool = False
|
||||
self._js_can_open_windows: bool = False
|
||||
self._loads_images_automatically: bool = True
|
||||
self._block_network_image: bool = False
|
||||
self._block_network_loads: bool = False
|
||||
self._minimum_font_size: int = 8
|
||||
self._default_font_size: int = 16
|
||||
self._text_zoom: int = 100
|
||||
self._layout_algorithm: int = WebSettings.LayoutAlgorithm.NORMAL
|
||||
self._mixed_content_mode: int = self.MIXED_CONTENT_NEVER_ALLOW
|
||||
self._offscreen_pre_raster: bool = False
|
||||
self._safe_browsing_enabled: bool = True
|
||||
self._javascript_can_open_windows_automatically: bool = False
|
||||
|
||||
def setJavaScriptEnabled(self, enabled: bool):
|
||||
self._js_enabled = enabled
|
||||
|
||||
def setDomStorageEnabled(self, enabled: bool):
|
||||
self._dom_storage_enabled = enabled
|
||||
|
||||
def setAllowFileAccess(self, allow: bool):
|
||||
self._allow_file_access = allow
|
||||
|
||||
def setAllowContentAccess(self, allow: bool):
|
||||
self._allow_content_access = allow
|
||||
|
||||
def setAllowFileAccessFromFileURLs(self, allow: bool):
|
||||
self._allow_file_access_from_urls = allow
|
||||
|
||||
def setAllowUniversalAccessFromFileURLs(self, allow: bool):
|
||||
self._allow_universal_access_from_urls = allow
|
||||
|
||||
def setBuiltInZoomControls(self, enabled: bool):
|
||||
self._built_in_zoom_controls = enabled
|
||||
|
||||
def setDisplayZoomControls(self, enabled: bool):
|
||||
self._display_zoom_controls = enabled
|
||||
|
||||
def setSupportZoom(self, support: bool):
|
||||
self._support_zoom = support
|
||||
|
||||
def setLoadWithOverviewMode(self, overview: bool):
|
||||
self._load_with_overview_mode = overview
|
||||
|
||||
def setUseWideViewPort(self, use: bool):
|
||||
self._use_wide_viewport = use
|
||||
|
||||
def setUserAgentString(self, ua: str):
|
||||
self._user_agent_string = ua
|
||||
|
||||
def getUserAgentString(self) -> str:
|
||||
return self._user_agent_string
|
||||
|
||||
def setDefaultTextEncodingName(self, encoding: str):
|
||||
self._default_text_encoding = encoding
|
||||
|
||||
def getDefaultTextEncodingName(self) -> str:
|
||||
return self._default_text_encoding
|
||||
|
||||
def setCacheMode(self, mode: int):
|
||||
self._cache_mode = mode
|
||||
|
||||
def getCacheMode(self) -> int:
|
||||
return self._cache_mode
|
||||
|
||||
def setAppCacheEnabled(self, enabled: bool):
|
||||
self._app_cache_enabled = enabled
|
||||
|
||||
def setAppCachePath(self, path: str):
|
||||
self._app_cache_path = path
|
||||
|
||||
def setDatabaseEnabled(self, enabled: bool):
|
||||
self._database_enabled = enabled
|
||||
|
||||
def setGeolocationEnabled(self, enabled: bool):
|
||||
self._geolocation_enabled = enabled
|
||||
|
||||
def setJavaScriptCanOpenWindowsAutomatically(self, auto: bool):
|
||||
self._javascript_can_open_windows_automatically = auto
|
||||
|
||||
def setLoadsImagesAutomatically(self, auto: bool):
|
||||
self._loads_images_automatically = auto
|
||||
|
||||
def setBlockNetworkImage(self, block: bool):
|
||||
self._block_network_image = block
|
||||
|
||||
def setBlockNetworkLoads(self, block: bool):
|
||||
self._block_network_loads = block
|
||||
|
||||
def setMinimumFontSize(self, size: int):
|
||||
self._minimum_font_size = size
|
||||
|
||||
def getMinimumFontSize(self) -> int:
|
||||
return self._minimum_font_size
|
||||
|
||||
def setDefaultFontSize(self, size: int):
|
||||
self._default_font_size = size
|
||||
|
||||
def getDefaultFontSize(self) -> int:
|
||||
return self._default_font_size
|
||||
|
||||
def setTextZoom(self, zoom: int):
|
||||
self._text_zoom = zoom
|
||||
|
||||
def getTextZoom(self) -> int:
|
||||
return self._text_zoom
|
||||
|
||||
def setLayoutAlgorithm(self, algo: int):
|
||||
self._layout_algorithm = algo
|
||||
|
||||
def getLayoutAlgorithm(self) -> int:
|
||||
return self._layout_algorithm
|
||||
|
||||
def setMixedContentMode(self, mode: int):
|
||||
self._mixed_content_mode = mode
|
||||
|
||||
def getMixedContentMode(self) -> int:
|
||||
return self._mixed_content_mode
|
||||
|
||||
def setOffscreenPreRaster(self, enabled: bool):
|
||||
self._offscreen_pre_raster = enabled
|
||||
|
||||
def getOffscreenPreRaster(self) -> bool:
|
||||
return self._offscreen_pre_raster
|
||||
|
||||
def setSafeBrowsingEnabled(self, enabled: bool):
|
||||
self._safe_browsing_enabled = enabled
|
||||
|
||||
def getSafeBrowsingEnabled(self) -> bool:
|
||||
return self._safe_browsing_enabled
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"WebSettings(js={self._js_enabled})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WebViewClient
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WebViewClient(Stub):
|
||||
"""WebViewClient — WebView 客户端"""
|
||||
|
||||
def onPageStarted(self, view: "WebView", url: str, favicon: Any):
|
||||
pass
|
||||
|
||||
def onPageFinished(self, view: "WebView", url: str):
|
||||
pass
|
||||
|
||||
def onLoadResource(self, view: "WebView", url: str):
|
||||
pass
|
||||
|
||||
def shouldOverrideUrlLoading(self, view: "WebView", request: Any) -> bool:
|
||||
return False
|
||||
|
||||
def shouldOverrideUrlLoading(self, view: "WebView", url: str) -> bool:
|
||||
return False
|
||||
|
||||
def shouldInterceptRequest(self, view: "WebView", request: WebResourceRequest) -> Optional[WebResourceResponse]:
|
||||
return None
|
||||
|
||||
def shouldInterceptRequest(self, view: "WebView", url: str) -> Optional[WebResourceResponse]:
|
||||
return None
|
||||
|
||||
def onReceivedHttpError(self, view: "WebView", request: WebResourceRequest,
|
||||
error_response: WebResourceResponse):
|
||||
pass
|
||||
|
||||
def onReceivedSslError(self, view: "WebView", handler: SslErrorHandler, error: Any):
|
||||
pass
|
||||
|
||||
def onReceivedClientCertRequest(self, view: "WebView", request: Any):
|
||||
pass
|
||||
|
||||
def onReceivedHttpAuthRequest(self, view: "WebView", handler: HttpAuthHandler,
|
||||
host: str, realm: str):
|
||||
pass
|
||||
|
||||
def onFormResubmission(self, view: "WebView", dont_resend: Any, resend: Any):
|
||||
pass
|
||||
|
||||
def doUpdateVisitedHistory(self, view: "WebView", url: str, is_reload: bool):
|
||||
pass
|
||||
|
||||
def onScaleChanged(self, view: "WebView", old_scale: float, new_scale: float):
|
||||
pass
|
||||
|
||||
def onUnhandledKeyEvent(self, view: "WebView", event: Any):
|
||||
pass
|
||||
|
||||
def shouldOverrideKeyEvent(self, view: "WebView", event: Any) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WebChromeClient
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WebChromeClient(Stub):
|
||||
"""WebChromeClient — WebView Chrome 客户端"""
|
||||
|
||||
def onProgressChanged(self, view: "WebView", new_progress: int):
|
||||
pass
|
||||
|
||||
def onReceivedTitle(self, view: "WebView", title: str):
|
||||
pass
|
||||
|
||||
def onReceivedIcon(self, view: "WebView", icon: Any):
|
||||
pass
|
||||
|
||||
def onReceivedTouchIconUrl(self, view: "WebView", url: str, precomposed: bool):
|
||||
pass
|
||||
|
||||
def onShowCustomView(self, view: Any, callback: Any):
|
||||
pass
|
||||
|
||||
def onHideCustomView(self):
|
||||
pass
|
||||
|
||||
def onCreateWindow(self, view: "WebView", is_dialog: bool,
|
||||
is_user_gesture: bool, result_msg: Any) -> bool:
|
||||
return False
|
||||
|
||||
def onRequestFocus(self, view: "WebView"):
|
||||
pass
|
||||
|
||||
def onCloseWindow(self, window: "WebView"):
|
||||
pass
|
||||
|
||||
def onJsAlert(self, view: "WebView", url: str, message: str,
|
||||
result: JsResult) -> bool:
|
||||
return False
|
||||
|
||||
def onJsConfirm(self, view: "WebView", url: str, message: str,
|
||||
result: JsResult) -> bool:
|
||||
return False
|
||||
|
||||
def onJsPrompt(self, view: "WebView", url: str, message: str,
|
||||
default_value: str, result: JsPromptResult) -> bool:
|
||||
return False
|
||||
|
||||
def onJsBeforeUnload(self, view: "WebView", url: str, message: str,
|
||||
result: JsResult) -> bool:
|
||||
return False
|
||||
|
||||
def onExceededDatabaseQuota(self, url: str, database_identifier: str,
|
||||
quota: int, estimated_database_size: int,
|
||||
total_quota: int, quota_updater: Any):
|
||||
pass
|
||||
|
||||
def onReachedMaxAppCacheSize(self, space_needed: int, total_quota: int,
|
||||
quota_updater: Any):
|
||||
pass
|
||||
|
||||
def onGeolocationPermissionsShowPrompt(self, origin: str,
|
||||
callback: Any):
|
||||
pass
|
||||
|
||||
def onGeolocationPermissionsHidePrompt(self):
|
||||
pass
|
||||
|
||||
def onPermissionRequest(self, request: PermissionRequest):
|
||||
pass
|
||||
|
||||
def onPermissionRequestCanceled(self, request: PermissionRequest):
|
||||
pass
|
||||
|
||||
def onConsoleMessage(self, message: Any) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WebView
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WebView(Stub):
|
||||
"""WebView — Web 视图"""
|
||||
|
||||
_instance_count = 0
|
||||
|
||||
def __init__(self, context: Any = None, attrs: Any = None):
|
||||
super().__init__()
|
||||
WebView._instance_count += 1
|
||||
self._context = context
|
||||
self._settings = WebSettings()
|
||||
self._webview_client: Optional[WebViewClient] = None
|
||||
self._chrome_client: Optional[WebChromeClient] = None
|
||||
self._current_url: str = ""
|
||||
self._original_url: str = ""
|
||||
self._title: str = ""
|
||||
self._progress: int = 100
|
||||
self._content_height: int = 0
|
||||
self._scale: float = 1.0
|
||||
self._background_color: int = 0xFFFFFFFF
|
||||
self._back_forward_list = WebBackForwardList()
|
||||
self._javascript_interfaces: Dict[str, Any] = {}
|
||||
self._paused: bool = False
|
||||
self._timers_paused: bool = False
|
||||
self._network_available: bool = True
|
||||
|
||||
def loadUrl(self, url: str, additional_headers: Optional[Dict[str, str]] = None):
|
||||
self._current_url = url
|
||||
self._original_url = url
|
||||
self._title = url
|
||||
self._progress = 0
|
||||
if self._webview_client:
|
||||
try:
|
||||
self._webview_client.onPageStarted(self, url, None)
|
||||
self._webview_client.onPageFinished(self, url)
|
||||
except Exception:
|
||||
pass
|
||||
self._progress = 100
|
||||
|
||||
def loadData(self, data: str, mime_type: str = "text/html",
|
||||
encoding: str = "utf-8"):
|
||||
self._current_url = "data:text/html;charset=utf-8,"
|
||||
self._progress = 100
|
||||
|
||||
def loadDataWithBaseURL(self, base_url: str, data: str,
|
||||
mime_type: str, encoding: str,
|
||||
history_url: str):
|
||||
self._current_url = history_url or base_url
|
||||
self._progress = 100
|
||||
|
||||
def setWebViewClient(self, client: WebViewClient):
|
||||
self._webview_client = client
|
||||
|
||||
def setWebChromeClient(self, client: WebChromeClient):
|
||||
self._chrome_client = client
|
||||
|
||||
def getSettings(self) -> WebSettings:
|
||||
return self._settings
|
||||
|
||||
def goBack(self):
|
||||
pass
|
||||
|
||||
def goForward(self):
|
||||
pass
|
||||
|
||||
def canGoBack(self) -> bool:
|
||||
return False
|
||||
|
||||
def canGoForward(self) -> bool:
|
||||
return False
|
||||
|
||||
def goBackOrForward(self, steps: int):
|
||||
pass
|
||||
|
||||
def reload(self):
|
||||
pass
|
||||
|
||||
def stopLoading(self):
|
||||
pass
|
||||
|
||||
def clearHistory(self):
|
||||
pass
|
||||
|
||||
def clearCache(self, include_disk_files: bool):
|
||||
pass
|
||||
|
||||
def clearView(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
WebView._instance_count -= 1
|
||||
|
||||
def getUrl(self) -> Optional[str]:
|
||||
return self._current_url or None
|
||||
|
||||
def getTitle(self) -> Optional[str]:
|
||||
return self._title or None
|
||||
|
||||
def getProgress(self) -> int:
|
||||
return self._progress
|
||||
|
||||
def getContentHeight(self) -> int:
|
||||
return self._content_height
|
||||
|
||||
def getScale(self) -> float:
|
||||
return self._scale
|
||||
|
||||
def addJavascriptInterface(self, obj: Any, interface_name: str):
|
||||
self._javascript_interfaces[interface_name] = obj
|
||||
|
||||
def removeJavascriptInterface(self, interface_name: str):
|
||||
if interface_name in self._javascript_interfaces:
|
||||
del self._javascript_interfaces[interface_name]
|
||||
|
||||
def evaluateJavascript(self, script: str, callback: Any = None):
|
||||
pass
|
||||
|
||||
def setInitialScale(self, scale_in_percent: int):
|
||||
self._scale = scale_in_percent / 100.0
|
||||
|
||||
def setBackgroundColor(self, color: int):
|
||||
self._background_color = color
|
||||
|
||||
def scrollTo(self, x: int, y: int):
|
||||
pass
|
||||
|
||||
def scrollBy(self, x: int, y: int):
|
||||
pass
|
||||
|
||||
def pageUp(self, top: bool) -> bool:
|
||||
return True
|
||||
|
||||
def pageDown(self, bottom: bool) -> bool:
|
||||
return True
|
||||
|
||||
def findNext(self, forward: bool):
|
||||
pass
|
||||
|
||||
def findAll(self, find: str) -> int:
|
||||
return 0
|
||||
|
||||
def findAllAsync(self, find: str):
|
||||
pass
|
||||
|
||||
def clearMatches(self):
|
||||
pass
|
||||
|
||||
def zoomIn(self):
|
||||
self._scale *= 1.25
|
||||
|
||||
def zoomOut(self):
|
||||
self._scale /= 1.25
|
||||
|
||||
def setNetworkAvailable(self, available: bool):
|
||||
self._network_available = available
|
||||
|
||||
def pauseTimers(self):
|
||||
self._timers_paused = True
|
||||
|
||||
def resumeTimers(self):
|
||||
self._timers_paused = False
|
||||
|
||||
def onPause(self):
|
||||
self._paused = True
|
||||
|
||||
def onResume(self):
|
||||
self._paused = False
|
||||
|
||||
def onWindowFocusChanged(self, has_focus: bool):
|
||||
pass
|
||||
|
||||
def setLayoutParams(self, params: Any):
|
||||
pass
|
||||
|
||||
def requestFocus(self, direction: int = 0):
|
||||
pass
|
||||
|
||||
def getOriginalUrl(self) -> Optional[str]:
|
||||
return self._original_url or None
|
||||
|
||||
def canZoomIn(self) -> bool:
|
||||
return True
|
||||
|
||||
def canZoomOut(self) -> bool:
|
||||
return self._scale > 0.25
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"WebView(url={self._current_url})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"WebView",
|
||||
"WebSettings",
|
||||
"WebViewClient",
|
||||
"WebChromeClient",
|
||||
"WebResourceRequest",
|
||||
"WebResourceResponse",
|
||||
"WebResourceError",
|
||||
"WebMessage",
|
||||
"WebMessagePort",
|
||||
"JavascriptInterface",
|
||||
"CookieManager",
|
||||
"CookieSyncManager",
|
||||
"JsResult",
|
||||
"JsPromptResult",
|
||||
"HttpAuthHandler",
|
||||
"SslErrorHandler",
|
||||
"PermissionRequest",
|
||||
"GeolocationPermissions",
|
||||
"WebStorage",
|
||||
"WebBackForwardList",
|
||||
"WebHistoryItem",
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
Whitespace-only changes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,628 @@
|
||||
"""
|
||||
Java crypto standard library APIs implemented in Python.
|
||||
javax.crypto package - Cipher, KeyGenerator, Mac, SecretKey, etc.
|
||||
"""
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from .lang import RuntimeException
|
||||
import os as _os
|
||||
|
||||
# Try to import cryptography library
|
||||
try:
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher as _Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms as _algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes as _modes
|
||||
from cryptography.hazmat.primitives import hashes as _hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa as _rsa
|
||||
from cryptography.hazmat.primitives.asymmetric import padding as _padding
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC as _PBKDF2
|
||||
from cryptography.hazmat.backends import default_backend as _crypto_backend
|
||||
HAS_CRYPTOGRAPHY = True
|
||||
except ImportError:
|
||||
HAS_CRYPTOGRAPHY = False
|
||||
|
||||
from .security import (
|
||||
GeneralSecurityException, InvalidKeyException,
|
||||
NoSuchAlgorithmException, NoSuchProviderException,
|
||||
Key, SecretKeySpec, AlgorithmParameters, AlgorithmParametersSpi,
|
||||
AlgorithmParameterGenerator, AlgorithmParameterGeneratorSpi,
|
||||
Provider, SecureRandom,
|
||||
)
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
AEADBadTagException = type("AEADBadTagException", (GeneralSecurityException,), {})
|
||||
BadPaddingException = type("BadPaddingException", (GeneralSecurityException,), {})
|
||||
IllegalBlockSizeException = type("IllegalBlockSizeException", (GeneralSecurityException,), {})
|
||||
NoSuchPaddingException = type("NoSuchPaddingException", (GeneralSecurityException,), {})
|
||||
ShortBufferException = type("ShortBufferException", (GeneralSecurityException,), {})
|
||||
ExemptionMechanismException = type("ExemptionMechanismException", (GeneralSecurityException,), {})
|
||||
|
||||
__all__ = [
|
||||
"Cipher", "CipherSpi",
|
||||
"KeyGenerator", "KeyGeneratorSpi",
|
||||
"SecretKey", "SecretKeyFactory", "SecretKeyFactorySpi",
|
||||
"Mac", "MacSpi",
|
||||
"IvParameterSpec", "PBEParameterSpec", "GCMParameterSpec",
|
||||
"OAEPParameterSpec", "PSource", "MGF1ParameterSpec",
|
||||
"DHParameterSpec", "DSAParameterSpec",
|
||||
"ECParameterSpec", "ECGenParameterSpec", "ECPoint",
|
||||
"ECField", "ECFieldF2m", "ECFieldFp", "EllipticCurve",
|
||||
"AlgorithmParameters", "AlgorithmParameterGenerator",
|
||||
"AlgorithmParametersSpi",
|
||||
"AEADBadTagException", "BadPaddingException",
|
||||
"IllegalBlockSizeException", "NoSuchPaddingException",
|
||||
"ShortBufferException",
|
||||
"ExemptionMechanism", "ExemptionMechanismException",
|
||||
"ExemptionMechanismSpi",
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SecretKey
|
||||
# ============================================================
|
||||
|
||||
class SecretKey(Key):
|
||||
def __init__(self, key_bytes=None, algorithm=""):
|
||||
super().__init__()
|
||||
self._key = key_bytes or b""
|
||||
self._algorithm = algorithm
|
||||
|
||||
def getAlgorithm(self):
|
||||
return self._algorithm
|
||||
|
||||
def getFormat(self):
|
||||
return "RAW"
|
||||
|
||||
def getEncoded(self):
|
||||
return self._key
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Cipher
|
||||
# ============================================================
|
||||
|
||||
class Cipher(Stub):
|
||||
ENCRYPT_MODE = 1
|
||||
DECRYPT_MODE = 2
|
||||
WRAP_MODE = 3
|
||||
UNWRAP_MODE = 4
|
||||
PUBLIC_KEY = 1
|
||||
PRIVATE_KEY = 2
|
||||
SECRET_KEY = 3
|
||||
|
||||
def __init__(self, transformation):
|
||||
super().__init__()
|
||||
self._transformation = transformation
|
||||
self._opmode = 0
|
||||
self._key = None
|
||||
self._iv = None
|
||||
self._provider = Provider("AndroidOpenSSL", 1.0, "Android OpenSSL provider")
|
||||
|
||||
@staticmethod
|
||||
def getInstance(transformation, provider=None):
|
||||
return Cipher(transformation)
|
||||
|
||||
def init(self, opmode, key_or_cert, params=None, random=None):
|
||||
self._opmode = opmode
|
||||
self._key = key_or_cert
|
||||
if isinstance(params, IvParameterSpec):
|
||||
self._iv = params.getIV()
|
||||
elif isinstance(params, GCMParameterSpec):
|
||||
self._iv = params.getIV()
|
||||
|
||||
def doFinal(self, input_data=None):
|
||||
if input_data is None:
|
||||
return b""
|
||||
if HAS_CRYPTOGRAPHY and self._key is not None:
|
||||
try:
|
||||
algo = self._transformation.split("/")[0].upper()
|
||||
key_bytes = self._key.getEncoded() if hasattr(self._key, "getEncoded") else bytes(self._key)
|
||||
iv = self._iv or b"\x00" * 16
|
||||
if algo == "AES":
|
||||
if len(iv) == 12:
|
||||
mode = _modes.GCM(iv)
|
||||
else:
|
||||
mode = _modes.CBC(iv)
|
||||
cipher = _Cipher(_algorithms.AES(key_bytes), mode, backend=_crypto_backend())
|
||||
elif algo == "DES":
|
||||
cipher = _Cipher(_algorithms.TripleDES(key_bytes), _modes.CBC(iv), backend=_crypto_backend())
|
||||
else:
|
||||
return self._basic_cipher(input_data)
|
||||
if self._opmode == Cipher.ENCRYPT_MODE:
|
||||
encryptor = cipher.encryptor()
|
||||
result = encryptor.update(input_data) + encryptor.finalize()
|
||||
return result
|
||||
else:
|
||||
decryptor = cipher.decryptor()
|
||||
result = decryptor.update(input_data) + decryptor.finalize()
|
||||
return result
|
||||
except Exception:
|
||||
return self._basic_cipher(input_data)
|
||||
else:
|
||||
return self._basic_cipher(input_data)
|
||||
|
||||
def _basic_cipher(self, data):
|
||||
if self._opmode == Cipher.ENCRYPT_MODE:
|
||||
if self._key is not None:
|
||||
key_bytes = self._key.getEncoded() if hasattr(self._key, "getEncoded") else bytes(self._key)
|
||||
return bytes([(b ^ key_bytes[i % len(key_bytes)]) & 0xFF for i, b in enumerate(data)])
|
||||
return data
|
||||
else:
|
||||
if self._key is not None:
|
||||
key_bytes = self._key.getEncoded() if hasattr(self._key, "getEncoded") else bytes(self._key)
|
||||
return bytes([(b ^ key_bytes[i % len(key_bytes)]) & 0xFF for i, b in enumerate(data)])
|
||||
return data
|
||||
|
||||
def update(self, input_data):
|
||||
return self._basic_cipher(input_data)
|
||||
|
||||
def getBlockSize(self):
|
||||
algo = self._transformation.split("/")[0].upper()
|
||||
if algo == "AES":
|
||||
return 16
|
||||
if algo == "DES" or algo == "DESede":
|
||||
return 8
|
||||
return 1
|
||||
|
||||
def getOutputSize(self, input_len):
|
||||
bs = self.getBlockSize()
|
||||
if bs > 1:
|
||||
return input_len + bs - (input_len % bs)
|
||||
return input_len
|
||||
|
||||
def getIV(self):
|
||||
return self._iv
|
||||
|
||||
def getParameters(self):
|
||||
if self._iv:
|
||||
return AlgorithmParameters(self._transformation.split("/")[0])
|
||||
return None
|
||||
|
||||
def getAlgorithm(self):
|
||||
return self._transformation
|
||||
|
||||
def getProvider(self):
|
||||
return self._provider
|
||||
|
||||
def getExemptionMechanism(self):
|
||||
return None
|
||||
|
||||
def unwrap(self, wrapped_key, wrapped_key_algorithm, wrapped_key_type):
|
||||
return SecretKey(wrapped_key, wrapped_key_algorithm)
|
||||
|
||||
def wrap(self, key):
|
||||
key_bytes = key.getEncoded() if hasattr(key, "getEncoded") else b""
|
||||
return self.doFinal(key_bytes)
|
||||
|
||||
@staticmethod
|
||||
def maxAllowedKeyLength(transformation):
|
||||
return 2147483647
|
||||
|
||||
|
||||
class CipherSpi(Stub):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# KeyGenerator
|
||||
# ============================================================
|
||||
|
||||
class KeyGenerator(Stub):
|
||||
def __init__(self, algorithm):
|
||||
super().__init__()
|
||||
self._algorithm = algorithm
|
||||
self._key_size = 128
|
||||
self._secure_random = None
|
||||
|
||||
@staticmethod
|
||||
def getInstance(algorithm, provider=None):
|
||||
return KeyGenerator(algorithm)
|
||||
|
||||
def init(self, size_or_random, random=None):
|
||||
if isinstance(size_or_random, int):
|
||||
self._key_size = size_or_random
|
||||
self._secure_random = random
|
||||
else:
|
||||
self._secure_random = size_or_random
|
||||
|
||||
def generateKey(self):
|
||||
key_bytes = _os.urandom(self._key_size // 8)
|
||||
return SecretKey(key_bytes, self._algorithm)
|
||||
|
||||
def getAlgorithm(self):
|
||||
return self._algorithm
|
||||
|
||||
def getProvider(self):
|
||||
return Provider("AndroidOpenSSL", 1.0, "Android OpenSSL provider")
|
||||
|
||||
|
||||
class KeyGeneratorSpi(Stub):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SecretKeyFactory
|
||||
# ============================================================
|
||||
|
||||
class SecretKeyFactory(Stub):
|
||||
def __init__(self, algorithm):
|
||||
super().__init__()
|
||||
self._algorithm = algorithm
|
||||
|
||||
@staticmethod
|
||||
def getInstance(algorithm, provider=None):
|
||||
return SecretKeyFactory(algorithm)
|
||||
|
||||
def generateSecret(self, key_spec):
|
||||
if isinstance(key_spec, SecretKeySpec):
|
||||
return SecretKey(key_spec.getEncoded(), self._algorithm)
|
||||
return SecretKey(b"", self._algorithm)
|
||||
|
||||
def getKeySpec(self, secret_key, key_spec_cls):
|
||||
return SecretKeySpec(secret_key.getEncoded(), self._algorithm)
|
||||
|
||||
def translateKey(self, key):
|
||||
return key
|
||||
|
||||
def getAlgorithm(self):
|
||||
return self._algorithm
|
||||
|
||||
|
||||
class SecretKeyFactorySpi(Stub):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Mac
|
||||
# ============================================================
|
||||
|
||||
class Mac(Stub):
|
||||
def __init__(self, algorithm):
|
||||
super().__init__()
|
||||
self._algorithm = algorithm
|
||||
self._key = None
|
||||
self._state = "UNINITIALIZED"
|
||||
self._data = b""
|
||||
|
||||
@staticmethod
|
||||
def getInstance(algorithm, provider=None):
|
||||
return Mac(algorithm)
|
||||
|
||||
def init(self, key, params=None):
|
||||
self._key = key
|
||||
self._state = "INITIALIZED"
|
||||
self._data = b""
|
||||
|
||||
def update(self, input_data, offset=0, length=-1):
|
||||
if length < 0:
|
||||
self._data += bytes(input_data)
|
||||
else:
|
||||
self._data += bytes(input_data[offset:offset+length])
|
||||
|
||||
def doFinal(self, input_data=None):
|
||||
if input_data is not None:
|
||||
self._data += bytes(input_data)
|
||||
if HAS_CRYPTOGRAPHY and self._key is not None:
|
||||
try:
|
||||
key_bytes = self._key.getEncoded() if hasattr(self._key, "getEncoded") else bytes(self._key)
|
||||
algo = self._algorithm.lower().replace("-", "")
|
||||
if "sha256" in algo or "sha-256" in self._algorithm:
|
||||
h = _hashlib.sha256(key_bytes + self._data)
|
||||
elif "sha1" in algo or "sha-1" in self._algorithm:
|
||||
h = _hashlib.sha1(key_bytes + self._data)
|
||||
elif "md5" in algo:
|
||||
h = _hashlib.md5(key_bytes + self._data)
|
||||
else:
|
||||
h = _hashlib.sha256(key_bytes + self._data)
|
||||
result = h.digest()
|
||||
self._data = b""
|
||||
return result
|
||||
except Exception:
|
||||
pass
|
||||
import hashlib as _hashlib
|
||||
key_bytes = self._key.getEncoded() if self._key and hasattr(self._key, "getEncoded") else b"key"
|
||||
h = _hashlib.sha256(key_bytes + self._data)
|
||||
result = h.digest()
|
||||
self._data = b""
|
||||
return result
|
||||
|
||||
def reset(self):
|
||||
self._data = b""
|
||||
|
||||
def getAlgorithm(self):
|
||||
return self._algorithm
|
||||
|
||||
def getMacLength(self):
|
||||
return 32
|
||||
|
||||
def getProvider(self):
|
||||
return Provider("AndroidOpenSSL", 1.0, "Android OpenSSL provider")
|
||||
|
||||
|
||||
class MacSpi(Stub):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Parameter specs
|
||||
# ============================================================
|
||||
|
||||
class IvParameterSpec(Stub):
|
||||
def __init__(self, iv):
|
||||
super().__init__()
|
||||
self._iv = bytes(iv)
|
||||
|
||||
def getIV(self):
|
||||
return self._iv
|
||||
|
||||
|
||||
class PBEParameterSpec(Stub):
|
||||
def __init__(self, salt, iteration_count):
|
||||
super().__init__()
|
||||
self._salt = bytes(salt)
|
||||
self._iteration_count = iteration_count
|
||||
|
||||
def getSalt(self):
|
||||
return self._salt
|
||||
|
||||
def getIterationCount(self):
|
||||
return self._iteration_count
|
||||
|
||||
|
||||
class GCMParameterSpec(Stub):
|
||||
def __init__(self, t_len, src):
|
||||
super().__init__()
|
||||
self._t_len = t_len
|
||||
self._iv = bytes(src)
|
||||
|
||||
def getTLen(self):
|
||||
return self._t_len
|
||||
|
||||
def getIV(self):
|
||||
return self._iv
|
||||
|
||||
|
||||
class OAEPParameterSpec(Stub):
|
||||
def __init__(self, md_name, mgf_name, mgf_spec, p_src):
|
||||
super().__init__()
|
||||
self._md_name = md_name
|
||||
self._mgf_name = mgf_name
|
||||
self._mgf_spec = mgf_spec
|
||||
self._p_src = p_src
|
||||
|
||||
def getDigestAlgorithm(self):
|
||||
return self._md_name
|
||||
|
||||
def getMGFAlgorithm(self):
|
||||
return self._mgf_name
|
||||
|
||||
def getMGFParameters(self):
|
||||
return self._mgf_spec
|
||||
|
||||
def getPSource(self):
|
||||
return self._p_src
|
||||
|
||||
|
||||
class PSource(Stub):
|
||||
class PSpecified(Stub):
|
||||
DEFAULT = None
|
||||
|
||||
def __init__(self, p):
|
||||
super().__init__()
|
||||
self._p = p
|
||||
|
||||
def getValue(self):
|
||||
return self._p
|
||||
|
||||
def __init__(self, p_src_id):
|
||||
super().__init__()
|
||||
self._p_src_id = p_src_id
|
||||
|
||||
def getAlgorithm(self):
|
||||
return self._p_src_id
|
||||
|
||||
|
||||
PSource.PSpecified.DEFAULT = PSource.PSpecified(b"")
|
||||
|
||||
|
||||
class MGF1ParameterSpec(Stub):
|
||||
SHA1 = None
|
||||
SHA256 = None
|
||||
SHA384 = None
|
||||
SHA512 = None
|
||||
|
||||
def __init__(self, md_name):
|
||||
super().__init__()
|
||||
self._md_name = md_name
|
||||
|
||||
def getDigestAlgorithm(self):
|
||||
return self._md_name
|
||||
|
||||
def toString(self):
|
||||
return f"MGF1 {self._md_name}"
|
||||
|
||||
|
||||
MGF1ParameterSpec.SHA1 = MGF1ParameterSpec("SHA-1")
|
||||
MGF1ParameterSpec.SHA256 = MGF1ParameterSpec("SHA-256")
|
||||
MGF1ParameterSpec.SHA384 = MGF1ParameterSpec("SHA-384")
|
||||
MGF1ParameterSpec.SHA512 = MGF1ParameterSpec("SHA-512")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DH / DSA / EC parameter specs (stubs)
|
||||
# ============================================================
|
||||
|
||||
class DHParameterSpec(Stub):
|
||||
def __init__(self, p, g, l=0):
|
||||
super().__init__()
|
||||
self._p = p
|
||||
self._g = g
|
||||
self._l = l
|
||||
|
||||
def getP(self):
|
||||
return self._p
|
||||
|
||||
def getG(self):
|
||||
return self._g
|
||||
|
||||
def getL(self):
|
||||
return self._l
|
||||
|
||||
|
||||
class DSAParameterSpec(Stub):
|
||||
def __init__(self, p, q, g):
|
||||
super().__init__()
|
||||
self._p = p
|
||||
self._q = q
|
||||
self._g = g
|
||||
|
||||
def getP(self):
|
||||
return self._p
|
||||
|
||||
def getQ(self):
|
||||
return self._q
|
||||
|
||||
def getG(self):
|
||||
return self._g
|
||||
|
||||
|
||||
class ECPoint(Stub):
|
||||
def __init__(self, affine_x, affine_y):
|
||||
super().__init__()
|
||||
self._affine_x = affine_x
|
||||
self._affine_y = affine_y
|
||||
|
||||
def getAffineX(self):
|
||||
return self._affine_x
|
||||
|
||||
def getAffineY(self):
|
||||
return self._affine_y
|
||||
|
||||
def equals(self, other):
|
||||
if isinstance(other, ECPoint):
|
||||
return self._affine_x == other._affine_x and self._affine_y == other._affine_y
|
||||
return False
|
||||
|
||||
def hashCode(self):
|
||||
return hash((self._affine_x, self._affine_y))
|
||||
|
||||
|
||||
class ECField(Stub):
|
||||
def getFieldSize(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ECFieldFp(ECField):
|
||||
def __init__(self, p):
|
||||
super().__init__()
|
||||
self._p = p
|
||||
|
||||
def getP(self):
|
||||
return self._p
|
||||
|
||||
def getFieldSize(self):
|
||||
return self._p.bit_length()
|
||||
|
||||
|
||||
class ECFieldF2m(ECField):
|
||||
def __init__(self, m, ks=None):
|
||||
super().__init__()
|
||||
self._m = m
|
||||
self._ks = ks
|
||||
|
||||
def getM(self):
|
||||
return self._m
|
||||
|
||||
def getMidTermsOfReductionPolynomial(self):
|
||||
return self._ks
|
||||
|
||||
def getFieldSize(self):
|
||||
return self._m
|
||||
|
||||
|
||||
class EllipticCurve(Stub):
|
||||
def __init__(self, field, a, b, seed=None):
|
||||
super().__init__()
|
||||
self._field = field
|
||||
self._a = a
|
||||
self._b = b
|
||||
self._seed = seed
|
||||
|
||||
def getField(self):
|
||||
return self._field
|
||||
|
||||
def getA(self):
|
||||
return self._a
|
||||
|
||||
def getB(self):
|
||||
return self._b
|
||||
|
||||
def getSeed(self):
|
||||
return self._seed
|
||||
|
||||
|
||||
class ECParameterSpec(Stub):
|
||||
def __init__(self, curve, generator, order, cofactor, seed=None):
|
||||
super().__init__()
|
||||
self._curve = curve
|
||||
self._generator = generator
|
||||
self._order = order
|
||||
self._cofactor = cofactor
|
||||
|
||||
def getCurve(self):
|
||||
return self._curve
|
||||
|
||||
def getGenerator(self):
|
||||
return self._generator
|
||||
|
||||
def getOrder(self):
|
||||
return self._order
|
||||
|
||||
def getCofactor(self):
|
||||
return self._cofactor
|
||||
|
||||
|
||||
class ECGenParameterSpec(Stub):
|
||||
def __init__(self, name):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
|
||||
def getName(self):
|
||||
return self._name
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ExemptionMechanism
|
||||
# ============================================================
|
||||
|
||||
class ExemptionMechanism(Stub):
|
||||
def __init__(self, name):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._state = False
|
||||
|
||||
@staticmethod
|
||||
def getInstance(name, provider=None):
|
||||
return ExemptionMechanism(name)
|
||||
|
||||
def init(self, key):
|
||||
self._state = False
|
||||
|
||||
def isCryptoAllowed(self, key):
|
||||
return True
|
||||
|
||||
def genExemptionBlob(self, data=None):
|
||||
return b""
|
||||
|
||||
def getName(self):
|
||||
return self._name
|
||||
|
||||
def getOutputSize(self, input_len):
|
||||
return input_len
|
||||
|
||||
def storeMechanism(self, mechanism):
|
||||
pass
|
||||
|
||||
|
||||
class ExemptionMechanismSpi(Stub):
|
||||
pass
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,683 @@
|
||||
"""
|
||||
Java math standard library APIs implemented in Python.
|
||||
java.math package - BigInteger, BigDecimal, MathContext, RoundingMode.
|
||||
"""
|
||||
|
||||
from .._core import Stub
|
||||
from .lang import Comparable
|
||||
from decimal import (
|
||||
Decimal as _Decimal,
|
||||
ROUND_DOWN as _DEC_ROUND_DOWN,
|
||||
ROUND_HALF_UP as _DEC_ROUND_HALF_UP,
|
||||
ROUND_HALF_EVEN as _DEC_ROUND_HALF_EVEN,
|
||||
ROUND_UP as _DEC_ROUND_UP,
|
||||
ROUND_CEILING as _DEC_ROUND_CEILING,
|
||||
ROUND_FLOOR as _DEC_ROUND_FLOOR,
|
||||
ROUND_HALF_DOWN as _DEC_ROUND_HALF_DOWN,
|
||||
localcontext as _local_context,
|
||||
getcontext as _get_context,
|
||||
)
|
||||
import random as _random
|
||||
import math as _py_math
|
||||
import sys as _sys
|
||||
|
||||
__all__ = [
|
||||
"BigInteger", "BigDecimal", "MathContext", "RoundingMode",
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# RoundingMode
|
||||
# ============================================================
|
||||
|
||||
class RoundingMode(Stub):
|
||||
UP = 0
|
||||
DOWN = 1
|
||||
CEILING = 2
|
||||
FLOOR = 3
|
||||
HALF_UP = 4
|
||||
HALF_DOWN = 5
|
||||
HALF_EVEN = 6
|
||||
UNNECESSARY = 7
|
||||
|
||||
_names = {
|
||||
0: "UP", 1: "DOWN", 2: "CEILING", 3: "FLOOR",
|
||||
4: "HALF_UP", 5: "HALF_DOWN", 6: "HALF_EVEN", 7: "UNNECESSARY",
|
||||
}
|
||||
|
||||
_py_map = {
|
||||
0: _DEC_ROUND_UP,
|
||||
1: _DEC_ROUND_DOWN,
|
||||
2: _DEC_ROUND_CEILING,
|
||||
3: _DEC_ROUND_FLOOR,
|
||||
4: _DEC_ROUND_HALF_UP,
|
||||
5: _DEC_ROUND_HALF_DOWN,
|
||||
6: _DEC_ROUND_HALF_EVEN,
|
||||
7: _DEC_ROUND_HALF_EVEN, # closest approximation
|
||||
}
|
||||
|
||||
def __init__(self, value):
|
||||
super().__init__()
|
||||
self._value = value
|
||||
|
||||
def toString(self):
|
||||
return self._names.get(self._value, "UNKNOWN")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MathContext
|
||||
# ============================================================
|
||||
|
||||
class MathContext(Stub):
|
||||
UNLIMITED = None
|
||||
DECIMAL32 = None
|
||||
DECIMAL64 = None
|
||||
DECIMAL128 = None
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__()
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], str):
|
||||
s = args[0].strip().lower()
|
||||
if s == "precision=7 roundingmode=half_even":
|
||||
self._precision = 7
|
||||
self._rounding_mode = RoundingMode.HALF_EVEN
|
||||
elif s == "precision=16 roundingmode=half_even":
|
||||
self._precision = 16
|
||||
self._rounding_mode = RoundingMode.HALF_EVEN
|
||||
elif s == "precision=34 roundingmode=half_even":
|
||||
self._precision = 34
|
||||
self._rounding_mode = RoundingMode.HALF_EVEN
|
||||
else:
|
||||
self._precision = 0
|
||||
self._rounding_mode = RoundingMode.HALF_UP
|
||||
else:
|
||||
self._precision = int(args[0])
|
||||
self._rounding_mode = RoundingMode.HALF_UP
|
||||
elif len(args) == 2:
|
||||
self._precision = int(args[0])
|
||||
self._rounding_mode = args[1]
|
||||
|
||||
def getPrecision(self):
|
||||
return self._precision
|
||||
|
||||
def getRoundingMode(self):
|
||||
return self._rounding_mode
|
||||
|
||||
def equals(self, other):
|
||||
if isinstance(other, MathContext):
|
||||
return (self._precision == other._precision and
|
||||
self._rounding_mode == other._rounding_mode)
|
||||
return False
|
||||
|
||||
def hashCode(self):
|
||||
return hash((self._precision, self._rounding_mode)) & 0x7FFFFFFF
|
||||
|
||||
def toString(self):
|
||||
return f"precision={self._precision} roundingMode={self._rounding_mode.toString()}"
|
||||
|
||||
|
||||
MathContext.UNLIMITED = MathContext(0)
|
||||
MathContext.DECIMAL32 = MathContext(7, RoundingMode.HALF_EVEN)
|
||||
MathContext.DECIMAL64 = MathContext(16, RoundingMode.HALF_EVEN)
|
||||
MathContext.DECIMAL128 = MathContext(34, RoundingMode.HALF_EVEN)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# BigInteger
|
||||
# ============================================================
|
||||
|
||||
class BigInteger(Comparable):
|
||||
ZERO = None
|
||||
ONE = None
|
||||
TEN = None
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__()
|
||||
if len(args) == 0:
|
||||
self._value = 0
|
||||
elif len(args) == 1:
|
||||
if isinstance(args[0], str):
|
||||
self._value = int(str(args[0]))
|
||||
elif isinstance(args[0], bytes):
|
||||
self._value = int.from_bytes(args[0], byteorder="big", signed=True)
|
||||
elif isinstance(args[0], bytearray):
|
||||
self._value = int.from_bytes(bytes(args[0]), byteorder="big", signed=True)
|
||||
elif isinstance(args[0], int):
|
||||
self._value = args[0]
|
||||
elif isinstance(args[0], BigInteger):
|
||||
self._value = args[0]._value
|
||||
else:
|
||||
self._value = int(args[0])
|
||||
elif len(args) == 2:
|
||||
if isinstance(args[1], _random.Random):
|
||||
bit_length, rng = args
|
||||
self._value = rng.getrandbits(bit_length)
|
||||
elif isinstance(args[1], int):
|
||||
val_str, radix = args
|
||||
self._value = int(str(val_str), radix)
|
||||
else:
|
||||
self._value = int.from_bytes(bytes(args[0]), byteorder="big", signed=True)
|
||||
else:
|
||||
self._value = 0
|
||||
|
||||
@staticmethod
|
||||
def valueOf(val):
|
||||
if isinstance(val, int):
|
||||
if val == 0:
|
||||
return BigInteger.ZERO
|
||||
if val == 1:
|
||||
return BigInteger.ONE
|
||||
if val == 10:
|
||||
return BigInteger.TEN
|
||||
return BigInteger(val)
|
||||
|
||||
def add(self, other):
|
||||
if isinstance(other, BigInteger):
|
||||
return BigInteger(self._value + other._value)
|
||||
return BigInteger(self._value + int(other))
|
||||
|
||||
def subtract(self, other):
|
||||
if isinstance(other, BigInteger):
|
||||
return BigInteger(self._value - other._value)
|
||||
return BigInteger(self._value - int(other))
|
||||
|
||||
def multiply(self, other):
|
||||
if isinstance(other, BigInteger):
|
||||
return BigInteger(self._value * other._value)
|
||||
return BigInteger(self._value * int(other))
|
||||
|
||||
def divide(self, other):
|
||||
if isinstance(other, BigInteger):
|
||||
return BigInteger(self._value // other._value)
|
||||
return BigInteger(self._value // int(other))
|
||||
|
||||
def pow(self, exponent):
|
||||
return BigInteger(self._value ** exponent)
|
||||
|
||||
def gcd(self, other):
|
||||
return BigInteger(_py_math.gcd(self._value, other._value if isinstance(other, BigInteger) else int(other)))
|
||||
|
||||
def abs(self):
|
||||
return BigInteger(abs(self._value))
|
||||
|
||||
def negate(self):
|
||||
return BigInteger(-self._value)
|
||||
|
||||
def signum(self):
|
||||
if self._value > 0:
|
||||
return 1
|
||||
if self._value < 0:
|
||||
return -1
|
||||
return 0
|
||||
|
||||
def mod(self, m):
|
||||
if isinstance(m, BigInteger):
|
||||
return BigInteger(self._value % m._value)
|
||||
return BigInteger(self._value % int(m))
|
||||
|
||||
def remainder(self, other):
|
||||
if isinstance(other, BigInteger):
|
||||
return BigInteger(self._value % other._value)
|
||||
return BigInteger(self._value % int(other))
|
||||
|
||||
def and_(self, other):
|
||||
return BigInteger(self._value & (other._value if isinstance(other, BigInteger) else int(other)))
|
||||
|
||||
def or_(self, other):
|
||||
return BigInteger(self._value | (other._value if isinstance(other, BigInteger) else int(other)))
|
||||
|
||||
def xor(self, other):
|
||||
return BigInteger(self._value ^ (other._value if isinstance(other, BigInteger) else int(other)))
|
||||
|
||||
def not_(self):
|
||||
return BigInteger(~self._value)
|
||||
|
||||
def andNot(self, other):
|
||||
return BigInteger(self._value & ~(other._value if isinstance(other, BigInteger) else int(other)))
|
||||
|
||||
def shiftLeft(self, n):
|
||||
return BigInteger(self._value << n)
|
||||
|
||||
def shiftRight(self, n):
|
||||
return BigInteger(self._value >> n)
|
||||
|
||||
def getLowestSetBit(self):
|
||||
if self._value == 0:
|
||||
return -1
|
||||
return (self._value & -self._value).bit_length() - 1
|
||||
|
||||
def bitLength(self):
|
||||
if self._value == 0:
|
||||
return 0
|
||||
return self._value.bit_length()
|
||||
|
||||
def bitCount(self):
|
||||
return bin(self._value & ((1 << self.bitLength()) - 1) if self._value >= 0 else self._value).count("1")
|
||||
|
||||
def isProbablePrime(self, certainty):
|
||||
if abs(self._value) < 2:
|
||||
return False
|
||||
if abs(self._value) == 2 or abs(self._value) == 3:
|
||||
return True
|
||||
if self._value % 2 == 0:
|
||||
return False
|
||||
d = self._value - 1
|
||||
s = 0
|
||||
while d % 2 == 0:
|
||||
d //= 2
|
||||
s += 1
|
||||
for _ in range(min(certainty, 20)):
|
||||
a = _random.randrange(2, min(self._value - 1, 1000000))
|
||||
x = pow(a, d, self._value)
|
||||
if x == 1 or x == self._value - 1:
|
||||
continue
|
||||
for _ in range(s - 1):
|
||||
x = pow(x, 2, self._value)
|
||||
if x == self._value - 1:
|
||||
break
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def nextProbablePrime(self):
|
||||
v = self._value + 1
|
||||
if v < 3:
|
||||
return BigInteger(2) if v <= 2 else BigInteger(3)
|
||||
while True:
|
||||
if v % 2 == 0:
|
||||
v += 1
|
||||
for i in range(3, int(_py_math.sqrt(v)) + 1, 2):
|
||||
if v % i == 0:
|
||||
break
|
||||
else:
|
||||
return BigInteger(v)
|
||||
v += 2
|
||||
|
||||
def compareTo(self, other):
|
||||
if isinstance(other, BigInteger):
|
||||
return (self._value > other._value) - (self._value < other._value)
|
||||
return -1
|
||||
|
||||
def equals(self, other):
|
||||
if isinstance(other, BigInteger):
|
||||
return self._value == other._value
|
||||
return False
|
||||
|
||||
def toByteArray(self):
|
||||
if self._value == 0:
|
||||
return b"\x00"
|
||||
byte_length = (self._value.bit_length() + 8) // 8
|
||||
return self._value.to_bytes(byte_length, byteorder="big", signed=True)
|
||||
|
||||
def toString(self, radix=10):
|
||||
if radix == 10:
|
||||
return str(self._value)
|
||||
if radix == 16:
|
||||
return hex(self._value)[2:]
|
||||
if radix == 8:
|
||||
return oct(self._value)[2:]
|
||||
if radix == 2:
|
||||
return bin(self._value)[2:]
|
||||
return str(self._value)
|
||||
|
||||
def intValue(self):
|
||||
return self._value & 0xFFFFFFFF
|
||||
|
||||
def longValue(self):
|
||||
return self._value
|
||||
|
||||
def floatValue(self):
|
||||
return float(self._value)
|
||||
|
||||
def doubleValue(self):
|
||||
return float(self._value)
|
||||
|
||||
def min(self, other):
|
||||
return BigInteger(min(self._value, other._value if isinstance(other, BigInteger) else int(other)))
|
||||
|
||||
def max(self, other):
|
||||
return BigInteger(max(self._value, other._value if isinstance(other, BigInteger) else int(other)))
|
||||
|
||||
def modPow(self, exponent, m):
|
||||
if isinstance(exponent, BigInteger):
|
||||
exp_val = exponent._value
|
||||
else:
|
||||
exp_val = int(exponent)
|
||||
mod_val = m._value if isinstance(m, BigInteger) else int(m)
|
||||
return BigInteger(pow(self._value, exp_val, mod_val))
|
||||
|
||||
def modInverse(self, m):
|
||||
mod_val = m._value if isinstance(m, BigInteger) else int(m)
|
||||
return BigInteger(pow(self._value, -1, mod_val))
|
||||
|
||||
def divideAndRemainder(self, other):
|
||||
if isinstance(other, BigInteger):
|
||||
divisor = other._value
|
||||
else:
|
||||
divisor = int(other)
|
||||
q = self._value // divisor
|
||||
r = self._value % divisor
|
||||
return [BigInteger(q), BigInteger(r)]
|
||||
|
||||
def __int__(self):
|
||||
return self._value
|
||||
|
||||
def __str__(self):
|
||||
return str(self._value)
|
||||
|
||||
def __neg__(self):
|
||||
return self.negate()
|
||||
|
||||
def __abs__(self):
|
||||
return self.abs()
|
||||
|
||||
def __add__(self, other):
|
||||
return self.add(other)
|
||||
|
||||
def __sub__(self, other):
|
||||
return self.subtract(other)
|
||||
|
||||
def __mul__(self, other):
|
||||
return self.multiply(other)
|
||||
|
||||
def __truediv__(self, other):
|
||||
return self.divide(other)
|
||||
|
||||
def __floordiv__(self, other):
|
||||
return self.divide(other)
|
||||
|
||||
def __mod__(self, other):
|
||||
return self.mod(other)
|
||||
|
||||
def __pow__(self, other):
|
||||
return self.pow(other)
|
||||
|
||||
def __and__(self, other):
|
||||
return self.and_(other)
|
||||
|
||||
def __or__(self, other):
|
||||
return self.or_(other)
|
||||
|
||||
def __xor__(self, other):
|
||||
return self.xor(other)
|
||||
|
||||
def __invert__(self):
|
||||
return self.not_()
|
||||
|
||||
def __lshift__(self, n):
|
||||
return self.shiftLeft(n)
|
||||
|
||||
def __rshift__(self, n):
|
||||
return self.shiftRight(n)
|
||||
|
||||
|
||||
BigInteger.ZERO = BigInteger(0)
|
||||
BigInteger.ONE = BigInteger(1)
|
||||
BigInteger.TEN = BigInteger(10)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# BigDecimal
|
||||
# ============================================================
|
||||
|
||||
class BigDecimal(Comparable):
|
||||
ZERO = None
|
||||
ONE = None
|
||||
TEN = None
|
||||
|
||||
ROUND_UP = 0
|
||||
ROUND_DOWN = 1
|
||||
ROUND_CEILING = 2
|
||||
ROUND_FLOOR = 3
|
||||
ROUND_HALF_UP = 4
|
||||
ROUND_HALF_DOWN = 5
|
||||
ROUND_HALF_EVEN = 6
|
||||
ROUND_UNNECESSARY = 7
|
||||
|
||||
_round_map = {
|
||||
0: _DEC_ROUND_UP,
|
||||
1: _DEC_ROUND_DOWN,
|
||||
2: _DEC_ROUND_CEILING,
|
||||
3: _DEC_ROUND_FLOOR,
|
||||
4: _DEC_ROUND_HALF_UP,
|
||||
5: _DEC_ROUND_HALF_DOWN,
|
||||
6: _DEC_ROUND_HALF_EVEN,
|
||||
7: _DEC_ROUND_HALF_EVEN,
|
||||
}
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__()
|
||||
if len(args) == 1:
|
||||
val = args[0]
|
||||
if isinstance(val, str):
|
||||
self._dec = _Decimal(str(val))
|
||||
elif isinstance(val, float):
|
||||
self._dec = _Decimal(repr(val))
|
||||
elif isinstance(val, int):
|
||||
self._dec = _Decimal(val)
|
||||
elif isinstance(val, BigInteger):
|
||||
self._dec = _Decimal(val._value)
|
||||
elif isinstance(val, BigDecimal):
|
||||
self._dec = _Decimal(val._dec)
|
||||
elif isinstance(val, _Decimal):
|
||||
self._dec = val
|
||||
else:
|
||||
self._dec = _Decimal(str(val))
|
||||
elif len(args) == 2:
|
||||
val, mc = args
|
||||
if isinstance(val, str):
|
||||
self._dec = _Decimal(str(val))
|
||||
elif isinstance(val, float):
|
||||
self._dec = _Decimal(repr(val))
|
||||
else:
|
||||
self._dec = _Decimal(val)
|
||||
if mc and hasattr(mc, 'getPrecision') and mc.getPrecision() > 0:
|
||||
self._dec = +self._dec
|
||||
|
||||
@staticmethod
|
||||
def valueOf(val):
|
||||
if isinstance(val, int):
|
||||
if val == 0:
|
||||
return BigDecimal.ZERO
|
||||
if val == 1:
|
||||
return BigDecimal.ONE
|
||||
if val == 10:
|
||||
return BigDecimal.TEN
|
||||
if isinstance(val, float):
|
||||
return BigDecimal(repr(val))
|
||||
return BigDecimal(val)
|
||||
|
||||
def add(self, other):
|
||||
if isinstance(other, BigDecimal):
|
||||
return BigDecimal(self._dec + other._dec)
|
||||
return BigDecimal(self._dec + _Decimal(str(other)))
|
||||
|
||||
def subtract(self, other):
|
||||
if isinstance(other, BigDecimal):
|
||||
return BigDecimal(self._dec - other._dec)
|
||||
return BigDecimal(self._dec - _Decimal(str(other)))
|
||||
|
||||
def multiply(self, other):
|
||||
if isinstance(other, BigDecimal):
|
||||
return BigDecimal(self._dec * other._dec)
|
||||
return BigDecimal(self._dec * _Decimal(str(other)))
|
||||
|
||||
def divide(self, *args):
|
||||
if len(args) == 1:
|
||||
other = args[0]
|
||||
if isinstance(other, BigDecimal):
|
||||
return BigDecimal(self._dec / other._dec)
|
||||
return BigDecimal(self._dec / _Decimal(str(other)))
|
||||
elif len(args) == 2:
|
||||
other, rounding_mode = args
|
||||
if isinstance(other, BigDecimal):
|
||||
result = self._dec / other._dec
|
||||
else:
|
||||
result = self._dec / _Decimal(str(other))
|
||||
return BigDecimal(result)
|
||||
elif len(args) == 3:
|
||||
other, scale, rounding_mode = args
|
||||
if isinstance(other, BigDecimal):
|
||||
result = self._dec / other._dec
|
||||
else:
|
||||
result = self._dec / _Decimal(str(other))
|
||||
rounded = BigDecimal(result)
|
||||
return rounded.setScale(scale, rounding_mode)
|
||||
|
||||
def divideToIntegralValue(self, other):
|
||||
if isinstance(other, BigDecimal):
|
||||
return BigDecimal(int(self._dec / other._dec))
|
||||
return BigDecimal(int(self._dec / _Decimal(str(other))))
|
||||
|
||||
def remainder(self, other):
|
||||
if isinstance(other, BigDecimal):
|
||||
return BigDecimal(self._dec % other._dec)
|
||||
return BigDecimal(self._dec % _Decimal(str(other)))
|
||||
|
||||
def pow(self, n):
|
||||
return BigDecimal(self._dec ** n)
|
||||
|
||||
def abs(self):
|
||||
return BigDecimal(abs(self._dec))
|
||||
|
||||
def negate(self):
|
||||
return BigDecimal(-self._dec)
|
||||
|
||||
def signum(self):
|
||||
if self._dec > 0:
|
||||
return 1
|
||||
if self._dec < 0:
|
||||
return -1
|
||||
return 0
|
||||
|
||||
def scale(self):
|
||||
return abs(self._dec.as_tuple().exponent)
|
||||
|
||||
def precision(self):
|
||||
return len(self._dec.as_tuple().digits)
|
||||
|
||||
def round(self, mc):
|
||||
if mc.getPrecision() > 0:
|
||||
with _local_context() as ctx:
|
||||
ctx.prec = mc.getPrecision()
|
||||
return BigDecimal(+self._dec)
|
||||
return BigDecimal(self._dec)
|
||||
|
||||
def setScale(self, new_scale, rounding_mode=None):
|
||||
if rounding_mode is None:
|
||||
qe = _Decimal(10) ** (-new_scale)
|
||||
return BigDecimal(self._dec.quantize(qe))
|
||||
py_mode = self._round_map.get(rounding_mode, _DEC_ROUND_HALF_UP)
|
||||
qe = _Decimal(10) ** (-new_scale)
|
||||
return BigDecimal(self._dec.quantize(qe, rounding=py_mode))
|
||||
|
||||
def ulp(self):
|
||||
exponent = self._dec.as_tuple().exponent
|
||||
return BigDecimal(_Decimal(10) ** exponent)
|
||||
|
||||
def unscaledValue(self):
|
||||
t = self._dec.as_tuple()
|
||||
digits = t.digits
|
||||
val = int("".join(str(d) for d in digits))
|
||||
return BigInteger(-val if t.sign else val)
|
||||
|
||||
def byteValueExact(self):
|
||||
val = self.intValueExact()
|
||||
if val < -128 or val > 127:
|
||||
raise ArithmeticError("Overflow")
|
||||
return val
|
||||
|
||||
def shortValueExact(self):
|
||||
val = self.intValueExact()
|
||||
if val < -32768 or val > 32767:
|
||||
raise ArithmeticError("Overflow")
|
||||
return val
|
||||
|
||||
def intValueExact(self):
|
||||
if self._dec != int(self._dec):
|
||||
raise ArithmeticError("Rounding necessary")
|
||||
return int(self._dec)
|
||||
|
||||
def longValueExact(self):
|
||||
if self._dec != int(self._dec):
|
||||
raise ArithmeticError("Rounding necessary")
|
||||
return int(self._dec)
|
||||
|
||||
def floatValue(self):
|
||||
return float(self._dec)
|
||||
|
||||
def doubleValue(self):
|
||||
return float(self._dec)
|
||||
|
||||
def intValue(self):
|
||||
return int(self._dec)
|
||||
|
||||
def longValue(self):
|
||||
return int(self._dec)
|
||||
|
||||
def toString(self):
|
||||
return str(self._dec)
|
||||
|
||||
def toEngineeringString(self):
|
||||
return format(self._dec, "e")
|
||||
|
||||
def toPlainString(self):
|
||||
return format(self._dec, "f")
|
||||
|
||||
def compareTo(self, other):
|
||||
if isinstance(other, BigDecimal):
|
||||
return (self._dec > other._dec) - (self._dec < other._dec)
|
||||
return -1
|
||||
|
||||
def equals(self, other):
|
||||
if isinstance(other, BigDecimal):
|
||||
return self._dec == other._dec
|
||||
return False
|
||||
|
||||
def hashCode(self):
|
||||
return hash(self._dec) & 0x7FFFFFFF
|
||||
|
||||
def min(self, other):
|
||||
return BigDecimal(min(self._dec, other._dec if isinstance(other, BigDecimal) else _Decimal(str(other))))
|
||||
|
||||
def max(self, other):
|
||||
return BigDecimal(max(self._dec, other._dec if isinstance(other, BigDecimal) else _Decimal(str(other))))
|
||||
|
||||
def __str__(self):
|
||||
return str(self._dec)
|
||||
|
||||
def __int__(self):
|
||||
return int(self._dec)
|
||||
|
||||
def __float__(self):
|
||||
return float(self._dec)
|
||||
|
||||
def __neg__(self):
|
||||
return self.negate()
|
||||
|
||||
def __abs__(self):
|
||||
return self.abs()
|
||||
|
||||
def __add__(self, other):
|
||||
return self.add(other)
|
||||
|
||||
def __sub__(self, other):
|
||||
return self.subtract(other)
|
||||
|
||||
def __mul__(self, other):
|
||||
return self.multiply(other)
|
||||
|
||||
def __truediv__(self, other):
|
||||
return self.divide(other)
|
||||
|
||||
def __mod__(self, other):
|
||||
return self.remainder(other)
|
||||
|
||||
def __pow__(self, other):
|
||||
return self.pow(other)
|
||||
|
||||
|
||||
BigDecimal.ZERO = BigDecimal(0)
|
||||
BigDecimal.ONE = BigDecimal(1)
|
||||
BigDecimal.TEN = BigDecimal(10)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,622 @@
|
||||
"""
|
||||
Java NIO standard library APIs implemented in Python.
|
||||
java.nio package - Buffer, ByteBuffer, CharBuffer, etc.
|
||||
"""
|
||||
|
||||
from .._core import Stub
|
||||
from .lang import RuntimeException
|
||||
from .io import IOException
|
||||
import struct as _struct
|
||||
import sys as _sys
|
||||
|
||||
IllegalStateException = type("IllegalStateException", (RuntimeException,), {})
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"Buffer", "ByteBuffer", "CharBuffer", "ShortBuffer",
|
||||
"IntBuffer", "LongBuffer", "FloatBuffer", "DoubleBuffer",
|
||||
"MappedByteBuffer", "ByteOrder",
|
||||
"BufferOverflowException", "BufferUnderflowException",
|
||||
"ReadOnlyBufferException", "InvalidMarkException",
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Exception classes
|
||||
# ============================================================
|
||||
|
||||
class BufferOverflowException(RuntimeException):
|
||||
pass
|
||||
|
||||
class BufferUnderflowException(RuntimeException):
|
||||
pass
|
||||
|
||||
class ReadOnlyBufferException(RuntimeException):
|
||||
pass
|
||||
|
||||
class InvalidMarkException(IllegalStateException):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ByteOrder
|
||||
# ============================================================
|
||||
|
||||
class ByteOrder(Stub):
|
||||
BIG_ENDIAN = None
|
||||
LITTLE_ENDIAN = None
|
||||
|
||||
def __init__(self, name):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
|
||||
@staticmethod
|
||||
def nativeOrder():
|
||||
if _sys.byteorder == "little":
|
||||
return ByteOrder.LITTLE_ENDIAN
|
||||
return ByteOrder.BIG_ENDIAN
|
||||
|
||||
def toString(self):
|
||||
return self._name
|
||||
|
||||
|
||||
ByteOrder.BIG_ENDIAN = ByteOrder("BIG_ENDIAN")
|
||||
ByteOrder.LITTLE_ENDIAN = ByteOrder("LITTLE_ENDIAN")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Buffer
|
||||
# ============================================================
|
||||
|
||||
class Buffer(Stub):
|
||||
def __init__(self, capacity, backing=None):
|
||||
super().__init__()
|
||||
self._capacity = capacity
|
||||
self._position = 0
|
||||
self._limit = capacity
|
||||
self._mark = -1
|
||||
self._backing = backing if backing is not None else bytearray(capacity)
|
||||
self._read_only = False
|
||||
|
||||
def capacity(self):
|
||||
return self._capacity
|
||||
|
||||
def position(self):
|
||||
return self._position
|
||||
|
||||
def position(self, new_position):
|
||||
if new_position < 0 or new_position > self._limit:
|
||||
raise IllegalArgumentException("Invalid position")
|
||||
self._position = new_position
|
||||
return self
|
||||
|
||||
def limit(self):
|
||||
return self._limit
|
||||
|
||||
def limit(self, new_limit):
|
||||
if new_limit < 0 or new_limit > self._capacity:
|
||||
raise IllegalArgumentException("Invalid limit")
|
||||
self._limit = new_limit
|
||||
if self._position > self._limit:
|
||||
self._position = self._limit
|
||||
if self._mark > self._limit:
|
||||
self._mark = -1
|
||||
return self
|
||||
|
||||
def mark(self):
|
||||
self._mark = self._position
|
||||
return self
|
||||
|
||||
def reset(self):
|
||||
if self._mark < 0:
|
||||
raise InvalidMarkException()
|
||||
self._position = self._mark
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
self._position = 0
|
||||
self._limit = self._capacity
|
||||
self._mark = -1
|
||||
return self
|
||||
|
||||
def flip(self):
|
||||
self._limit = self._position
|
||||
self._position = 0
|
||||
self._mark = -1
|
||||
return self
|
||||
|
||||
def rewind(self):
|
||||
self._position = 0
|
||||
self._mark = -1
|
||||
return self
|
||||
|
||||
def remaining(self):
|
||||
return self._limit - self._position
|
||||
|
||||
def hasRemaining(self):
|
||||
return self._position < self._limit
|
||||
|
||||
def isReadOnly(self):
|
||||
return self._read_only
|
||||
|
||||
def isDirect(self):
|
||||
return False
|
||||
|
||||
def array(self):
|
||||
if self._read_only:
|
||||
raise ReadOnlyBufferException()
|
||||
return self._backing
|
||||
|
||||
def arrayOffset(self):
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ByteBuffer
|
||||
# ============================================================
|
||||
|
||||
class ByteBuffer(Buffer):
|
||||
def __init__(self, capacity, backing=None, order=None):
|
||||
super().__init__(capacity, backing)
|
||||
self._order = order or ByteOrder.BIG_ENDIAN
|
||||
|
||||
@staticmethod
|
||||
def allocate(capacity):
|
||||
return ByteBuffer(capacity, bytearray(capacity))
|
||||
|
||||
@staticmethod
|
||||
def allocateDirect(capacity):
|
||||
return ByteBuffer(capacity, bytearray(capacity))
|
||||
|
||||
@staticmethod
|
||||
def wrap(array, offset=0, length=None):
|
||||
if length is None:
|
||||
length = len(array) - offset
|
||||
buf = ByteBuffer(length, bytearray(array))
|
||||
buf._position = offset
|
||||
buf._limit = offset + length
|
||||
return buf
|
||||
|
||||
def put(self, b, index=None):
|
||||
if self._read_only:
|
||||
raise ReadOnlyBufferException()
|
||||
if index is None:
|
||||
if isinstance(b, int):
|
||||
if self._position >= self._limit:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position] = b & 0xFF
|
||||
self._position += 1
|
||||
elif isinstance(b, ByteBuffer):
|
||||
n = b.remaining()
|
||||
if self.remaining() < n:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+n] = b._backing[b._position:b._limit]
|
||||
self._position += n
|
||||
b._position = b._limit
|
||||
elif isinstance(b, (bytes, bytearray)):
|
||||
if self.remaining() < len(b):
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+len(b)] = b
|
||||
self._position += len(b)
|
||||
else:
|
||||
self._backing[index] = b & 0xFF
|
||||
return self
|
||||
|
||||
def get(self, index=None):
|
||||
if index is None:
|
||||
if self._position >= self._limit:
|
||||
raise BufferUnderflowException()
|
||||
val = self._backing[self._position]
|
||||
self._position += 1
|
||||
return val & 0xFF
|
||||
return self._backing[index] & 0xFF
|
||||
|
||||
def putInt(self, value, index=None):
|
||||
data = _struct.pack(">i" if self._order == ByteOrder.BIG_ENDIAN else "<i", value)
|
||||
if index is not None:
|
||||
self._backing[index:index+4] = data
|
||||
else:
|
||||
if self.remaining() < 4:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+4] = data
|
||||
self._position += 4
|
||||
return self
|
||||
|
||||
def getInt(self, index=None):
|
||||
if index is not None:
|
||||
data = self._backing[index:index+4]
|
||||
else:
|
||||
if self.remaining() < 4:
|
||||
raise BufferUnderflowException()
|
||||
data = self._backing[self._position:self._position+4]
|
||||
self._position += 4
|
||||
return _struct.unpack(">i" if self._order == ByteOrder.BIG_ENDIAN else "<i", bytes(data))[0]
|
||||
|
||||
def putLong(self, value, index=None):
|
||||
data = _struct.pack(">q" if self._order == ByteOrder.BIG_ENDIAN else "<q", value)
|
||||
if index is not None:
|
||||
self._backing[index:index+8] = data
|
||||
else:
|
||||
if self.remaining() < 8:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+8] = data
|
||||
self._position += 8
|
||||
return self
|
||||
|
||||
def getLong(self, index=None):
|
||||
if index is not None:
|
||||
data = self._backing[index:index+8]
|
||||
else:
|
||||
if self.remaining() < 8:
|
||||
raise BufferUnderflowException()
|
||||
data = self._backing[self._position:self._position+8]
|
||||
self._position += 8
|
||||
return _struct.unpack(">q" if self._order == ByteOrder.BIG_ENDIAN else "<q", bytes(data))[0]
|
||||
|
||||
def putFloat(self, value, index=None):
|
||||
data = _struct.pack(">f" if self._order == ByteOrder.BIG_ENDIAN else "<f", value)
|
||||
if index is not None:
|
||||
self._backing[index:index+4] = data
|
||||
else:
|
||||
if self.remaining() < 4:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+4] = data
|
||||
self._position += 4
|
||||
return self
|
||||
|
||||
def getFloat(self, index=None):
|
||||
if index is not None:
|
||||
data = self._backing[index:index+4]
|
||||
else:
|
||||
if self.remaining() < 4:
|
||||
raise BufferUnderflowException()
|
||||
data = self._backing[self._position:self._position+4]
|
||||
self._position += 4
|
||||
return _struct.unpack(">f" if self._order == ByteOrder.BIG_ENDIAN else "<f", bytes(data))[0]
|
||||
|
||||
def putDouble(self, value, index=None):
|
||||
data = _struct.pack(">d" if self._order == ByteOrder.BIG_ENDIAN else "<d", value)
|
||||
if index is not None:
|
||||
self._backing[index:index+8] = data
|
||||
else:
|
||||
if self.remaining() < 8:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+8] = data
|
||||
self._position += 8
|
||||
return self
|
||||
|
||||
def getDouble(self, index=None):
|
||||
if index is not None:
|
||||
data = self._backing[index:index+8]
|
||||
else:
|
||||
if self.remaining() < 8:
|
||||
raise BufferUnderflowException()
|
||||
data = self._backing[self._position:self._position+8]
|
||||
self._position += 8
|
||||
return _struct.unpack(">d" if self._order == ByteOrder.BIG_ENDIAN else "<d", bytes(data))[0]
|
||||
|
||||
def putShort(self, value, index=None):
|
||||
data = _struct.pack(">h" if self._order == ByteOrder.BIG_ENDIAN else "<h", value)
|
||||
if index is not None:
|
||||
self._backing[index:index+2] = data
|
||||
else:
|
||||
if self.remaining() < 2:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+2] = data
|
||||
self._position += 2
|
||||
return self
|
||||
|
||||
def getShort(self, index=None):
|
||||
if index is not None:
|
||||
data = self._backing[index:index+2]
|
||||
else:
|
||||
if self.remaining() < 2:
|
||||
raise BufferUnderflowException()
|
||||
data = self._backing[self._position:self._position+2]
|
||||
self._position += 2
|
||||
return _struct.unpack(">h" if self._order == ByteOrder.BIG_ENDIAN else "<h", bytes(data))[0]
|
||||
|
||||
def putChar(self, value, index=None):
|
||||
return self.putShort(ord(value), index)
|
||||
|
||||
def getChar(self, index=None):
|
||||
return chr(self.getShort(index))
|
||||
|
||||
def asIntBuffer(self):
|
||||
return IntBuffer(self.remaining() // 4, self._backing, self._position, self._order)
|
||||
|
||||
def asLongBuffer(self):
|
||||
return LongBuffer(self.remaining() // 8, self._backing, self._position, self._order)
|
||||
|
||||
def asFloatBuffer(self):
|
||||
return FloatBuffer(self.remaining() // 4, self._backing, self._position, self._order)
|
||||
|
||||
def asDoubleBuffer(self):
|
||||
return DoubleBuffer(self.remaining() // 8, self._backing, self._position, self._order)
|
||||
|
||||
def asShortBuffer(self):
|
||||
return ShortBuffer(self.remaining() // 2, self._backing, self._position, self._order)
|
||||
|
||||
def asCharBuffer(self):
|
||||
return CharBuffer(self.remaining() // 2, self._backing, self._position, self._order)
|
||||
|
||||
def slice(self):
|
||||
n = self.remaining()
|
||||
buf = ByteBuffer(n, self._backing[self._position:self._limit])
|
||||
return buf
|
||||
|
||||
def duplicate(self):
|
||||
buf = ByteBuffer(self._capacity, bytearray(self._backing))
|
||||
buf._position = self._position
|
||||
buf._limit = self._limit
|
||||
buf._order = self._order
|
||||
return buf
|
||||
|
||||
def asReadOnlyBuffer(self):
|
||||
buf = self.duplicate()
|
||||
buf._read_only = True
|
||||
return buf
|
||||
|
||||
def order(self, order=None):
|
||||
if order is not None:
|
||||
self._order = order
|
||||
return self
|
||||
return self._order
|
||||
|
||||
def compact(self):
|
||||
if self._read_only:
|
||||
raise ReadOnlyBufferException()
|
||||
remaining = self.remaining()
|
||||
self._backing[:remaining] = self._backing[self._position:self._limit]
|
||||
self._position = remaining
|
||||
self._limit = self._capacity
|
||||
self._mark = -1
|
||||
return self
|
||||
|
||||
def toString(self):
|
||||
return f"ByteBuffer[pos={self._position} lim={self._limit} cap={self._capacity}]"
|
||||
|
||||
def hashCode(self):
|
||||
return hash(bytes(self._backing)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other):
|
||||
if isinstance(other, ByteBuffer):
|
||||
return bytes(self._backing[self._position:self._limit]) == bytes(other._backing[other._position:other._limit])
|
||||
return False
|
||||
|
||||
def compareTo(self, other):
|
||||
if isinstance(other, ByteBuffer):
|
||||
a = bytes(self._backing[self._position:self._limit])
|
||||
b = bytes(other._backing[other._position:other._limit])
|
||||
return (a > b) - (a < b)
|
||||
return -1
|
||||
|
||||
|
||||
# ============================================================
|
||||
# IntBuffer
|
||||
# ============================================================
|
||||
|
||||
class IntBuffer(Buffer):
|
||||
def __init__(self, capacity, backing, offset=0, order=None):
|
||||
super().__init__(capacity, backing)
|
||||
self._int_order = order or ByteOrder.BIG_ENDIAN
|
||||
self._int_offset = offset
|
||||
|
||||
def get(self, index=None):
|
||||
if index is not None:
|
||||
idx = self._int_offset + index * 4
|
||||
return _struct.unpack(">i" if self._int_order == ByteOrder.BIG_ENDIAN else "<i",
|
||||
bytes(self._backing[idx:idx+4]))[0]
|
||||
if self.remaining() < 4:
|
||||
raise BufferUnderflowException()
|
||||
val = _struct.unpack(">i" if self._int_order == ByteOrder.BIG_ENDIAN else "<i",
|
||||
bytes(self._backing[self._position:self._position+4]))[0]
|
||||
self._position += 4
|
||||
return val
|
||||
|
||||
def put(self, value, index=None):
|
||||
data = _struct.pack(">i" if self._int_order == ByteOrder.BIG_ENDIAN else "<i", value)
|
||||
if index is not None:
|
||||
idx = self._int_offset + index * 4
|
||||
self._backing[idx:idx+4] = data
|
||||
else:
|
||||
if self.remaining() < 4:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+4] = data
|
||||
self._position += 4
|
||||
return self
|
||||
|
||||
|
||||
# ============================================================
|
||||
# LongBuffer
|
||||
# ============================================================
|
||||
|
||||
class LongBuffer(Buffer):
|
||||
def __init__(self, capacity, backing, offset=0, order=None):
|
||||
super().__init__(capacity, backing)
|
||||
self._long_order = order or ByteOrder.BIG_ENDIAN
|
||||
self._long_offset = offset
|
||||
|
||||
def get(self, index=None):
|
||||
if index is not None:
|
||||
idx = self._long_offset + index * 8
|
||||
return _struct.unpack(">q" if self._long_order == ByteOrder.BIG_ENDIAN else "<q",
|
||||
bytes(self._backing[idx:idx+8]))[0]
|
||||
if self.remaining() < 8:
|
||||
raise BufferUnderflowException()
|
||||
val = _struct.unpack(">q" if self._long_order == ByteOrder.BIG_ENDIAN else "<q",
|
||||
bytes(self._backing[self._position:self._position+8]))[0]
|
||||
self._position += 8
|
||||
return val
|
||||
|
||||
def put(self, value, index=None):
|
||||
data = _struct.pack(">q" if self._long_order == ByteOrder.BIG_ENDIAN else "<q", value)
|
||||
if index is not None:
|
||||
idx = self._long_offset + index * 8
|
||||
self._backing[idx:idx+8] = data
|
||||
else:
|
||||
if self.remaining() < 8:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+8] = data
|
||||
self._position += 8
|
||||
return self
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FloatBuffer
|
||||
# ============================================================
|
||||
|
||||
class FloatBuffer(Buffer):
|
||||
def __init__(self, capacity, backing, offset=0, order=None):
|
||||
super().__init__(capacity, backing)
|
||||
self._float_order = order or ByteOrder.BIG_ENDIAN
|
||||
self._float_offset = offset
|
||||
|
||||
def get(self, index=None):
|
||||
if index is not None:
|
||||
idx = self._float_offset + index * 4
|
||||
return _struct.unpack(">f" if self._float_order == ByteOrder.BIG_ENDIAN else "<f",
|
||||
bytes(self._backing[idx:idx+4]))[0]
|
||||
if self.remaining() < 4:
|
||||
raise BufferUnderflowException()
|
||||
val = _struct.unpack(">f" if self._float_order == ByteOrder.BIG_ENDIAN else "<f",
|
||||
bytes(self._backing[self._position:self._position+4]))[0]
|
||||
self._position += 4
|
||||
return val
|
||||
|
||||
def put(self, value, index=None):
|
||||
data = _struct.pack(">f" if self._float_order == ByteOrder.BIG_ENDIAN else "<f", value)
|
||||
if index is not None:
|
||||
idx = self._float_offset + index * 4
|
||||
self._backing[idx:idx+4] = data
|
||||
else:
|
||||
if self.remaining() < 4:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+4] = data
|
||||
self._position += 4
|
||||
return self
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DoubleBuffer
|
||||
# ============================================================
|
||||
|
||||
class DoubleBuffer(Buffer):
|
||||
def __init__(self, capacity, backing, offset=0, order=None):
|
||||
super().__init__(capacity, backing)
|
||||
self._double_order = order or ByteOrder.BIG_ENDIAN
|
||||
self._double_offset = offset
|
||||
|
||||
def get(self, index=None):
|
||||
if index is not None:
|
||||
idx = self._double_offset + index * 8
|
||||
return _struct.unpack(">d" if self._double_order == ByteOrder.BIG_ENDIAN else "<d",
|
||||
bytes(self._backing[idx:idx+8]))[0]
|
||||
if self.remaining() < 8:
|
||||
raise BufferUnderflowException()
|
||||
val = _struct.unpack(">d" if self._double_order == ByteOrder.BIG_ENDIAN else "<d",
|
||||
bytes(self._backing[self._position:self._position+8]))[0]
|
||||
self._position += 8
|
||||
return val
|
||||
|
||||
def put(self, value, index=None):
|
||||
data = _struct.pack(">d" if self._double_order == ByteOrder.BIG_ENDIAN else "<d", value)
|
||||
if index is not None:
|
||||
idx = self._double_offset + index * 8
|
||||
self._backing[idx:idx+8] = data
|
||||
else:
|
||||
if self.remaining() < 8:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+8] = data
|
||||
self._position += 8
|
||||
return self
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ShortBuffer
|
||||
# ============================================================
|
||||
|
||||
class ShortBuffer(Buffer):
|
||||
def __init__(self, capacity, backing, offset=0, order=None):
|
||||
super().__init__(capacity, backing)
|
||||
self._short_order = order or ByteOrder.BIG_ENDIAN
|
||||
self._short_offset = offset
|
||||
|
||||
def get(self, index=None):
|
||||
if index is not None:
|
||||
idx = self._short_offset + index * 2
|
||||
return _struct.unpack(">h" if self._short_order == ByteOrder.BIG_ENDIAN else "<h",
|
||||
bytes(self._backing[idx:idx+2]))[0]
|
||||
if self.remaining() < 2:
|
||||
raise BufferUnderflowException()
|
||||
val = _struct.unpack(">h" if self._short_order == ByteOrder.BIG_ENDIAN else "<h",
|
||||
bytes(self._backing[self._position:self._position+2]))[0]
|
||||
self._position += 2
|
||||
return val
|
||||
|
||||
def put(self, value, index=None):
|
||||
data = _struct.pack(">h" if self._short_order == ByteOrder.BIG_ENDIAN else "<h", value)
|
||||
if index is not None:
|
||||
idx = self._short_offset + index * 2
|
||||
self._backing[idx:idx+2] = data
|
||||
else:
|
||||
if self.remaining() < 2:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+2] = data
|
||||
self._position += 2
|
||||
return self
|
||||
|
||||
|
||||
# ============================================================
|
||||
# CharBuffer
|
||||
# ============================================================
|
||||
|
||||
class CharBuffer(Buffer):
|
||||
def __init__(self, capacity, backing, offset=0, order=None):
|
||||
super().__init__(capacity, backing)
|
||||
self._char_order = order or ByteOrder.BIG_ENDIAN
|
||||
self._char_offset = offset
|
||||
|
||||
def get(self, index=None):
|
||||
if index is not None:
|
||||
idx = self._char_offset + index * 2
|
||||
val = _struct.unpack(">h" if self._char_order == ByteOrder.BIG_ENDIAN else "<h",
|
||||
bytes(self._backing[idx:idx+2]))[0]
|
||||
return chr(val)
|
||||
if self.remaining() < 2:
|
||||
raise BufferUnderflowException()
|
||||
val = _struct.unpack(">h" if self._char_order == ByteOrder.BIG_ENDIAN else "<h",
|
||||
bytes(self._backing[self._position:self._position+2]))[0]
|
||||
self._position += 2
|
||||
return chr(val)
|
||||
|
||||
def put(self, value, index=None):
|
||||
char_val = ord(value) if isinstance(value, str) else value
|
||||
data = _struct.pack(">h" if self._char_order == ByteOrder.BIG_ENDIAN else "<h", char_val)
|
||||
if index is not None:
|
||||
idx = self._char_offset + index * 2
|
||||
self._backing[idx:idx+2] = data
|
||||
else:
|
||||
if self.remaining() < 2:
|
||||
raise BufferOverflowException()
|
||||
self._backing[self._position:self._position+2] = data
|
||||
self._position += 2
|
||||
return self
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MappedByteBuffer
|
||||
# ============================================================
|
||||
|
||||
class MappedByteBuffer(ByteBuffer):
|
||||
def __init__(self, capacity, backing=None):
|
||||
super().__init__(capacity, backing)
|
||||
|
||||
def force(self):
|
||||
return self
|
||||
|
||||
def isLoaded(self):
|
||||
return True
|
||||
|
||||
def load(self):
|
||||
return self
|
||||
@@ -0,0 +1,703 @@
|
||||
"""
|
||||
Java NIO File standard library APIs implemented in Python.
|
||||
java.nio.file package - Path, Files, FileSystem, FileVisitor, etc.
|
||||
"""
|
||||
|
||||
from .._core import Stub
|
||||
from .io import IOException
|
||||
from .lang import RuntimeException, Comparable
|
||||
import os as _os
|
||||
import shutil as _shutil
|
||||
import stat as _stat
|
||||
from pathlib import Path as _PathLibPath
|
||||
import tempfile as _tempfile
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"Path", "Paths", "Files", "FileSystem", "FileSystems",
|
||||
"FileStore", "FileVisitResult", "FileVisitor", "SimpleFileVisitor",
|
||||
"DirectoryStream", "DirectoryStreamFilter",
|
||||
"PathMatcher", "WatchService", "WatchKey", "WatchEvent", "Watchable",
|
||||
"CopyOption", "StandardCopyOption", "LinkOption", "OpenOption",
|
||||
"StandardOpenOption",
|
||||
"FileAlreadyExistsException", "DirectoryNotEmptyException",
|
||||
"NoSuchFileException", "FileSystemLoopException",
|
||||
"FileSystemNotFoundException", "ProviderMismatchException",
|
||||
"ProviderNotFoundException", "NotDirectoryException", "NotLinkException",
|
||||
"AtomicMoveNotSupportedException", "AccessDeniedException",
|
||||
"AccessMode",
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Exception classes
|
||||
# ============================================================
|
||||
|
||||
class FileAlreadyExistsException(IOException):
|
||||
pass
|
||||
|
||||
class DirectoryNotEmptyException(IOException):
|
||||
pass
|
||||
|
||||
class NoSuchFileException(IOException):
|
||||
def __init__(self, file=None, other=None, reason=None):
|
||||
msg = str(file) if file else ""
|
||||
super().__init__(msg)
|
||||
|
||||
class FileSystemLoopException(IOException):
|
||||
pass
|
||||
|
||||
class FileSystemNotFoundException(IOException):
|
||||
pass
|
||||
|
||||
class ProviderMismatchException(IllegalArgumentException):
|
||||
pass
|
||||
|
||||
class ProviderNotFoundException(IllegalArgumentException):
|
||||
pass
|
||||
|
||||
class NotDirectoryException(IOException):
|
||||
pass
|
||||
|
||||
class NotLinkException(IOException):
|
||||
pass
|
||||
|
||||
class AtomicMoveNotSupportedException(IOException):
|
||||
pass
|
||||
|
||||
class AccessDeniedException(IOException):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Enums / Constants
|
||||
# ============================================================
|
||||
|
||||
class FileVisitResult:
|
||||
CONTINUE = 0
|
||||
TERMINATE = 1
|
||||
SKIP_SUBTREE = 2
|
||||
SKIP_SIBLINGS = 3
|
||||
|
||||
class StandardCopyOption:
|
||||
REPLACE_EXISTING = 0
|
||||
COPY_ATTRIBUTES = 1
|
||||
ATOMIC_MOVE = 2
|
||||
|
||||
class StandardOpenOption:
|
||||
READ = 0
|
||||
WRITE = 1
|
||||
APPEND = 2
|
||||
TRUNCATE_EXISTING = 3
|
||||
CREATE = 4
|
||||
CREATE_NEW = 5
|
||||
DELETE_ON_CLOSE = 6
|
||||
SPARSE = 7
|
||||
SYNC = 8
|
||||
DSYNC = 9
|
||||
|
||||
class LinkOption:
|
||||
NOFOLLOW_LINKS = 0
|
||||
|
||||
class CopyOption(Stub):
|
||||
pass
|
||||
|
||||
class OpenOption(Stub):
|
||||
pass
|
||||
|
||||
class AccessMode:
|
||||
READ = 0
|
||||
WRITE = 1
|
||||
EXECUTE = 2
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Path
|
||||
# ============================================================
|
||||
|
||||
class Path(Comparable):
|
||||
def __init__(self, path_str, *more):
|
||||
super().__init__()
|
||||
if more:
|
||||
path_str = _os.path.join(path_str, *more)
|
||||
self._path = str(path_str)
|
||||
self._pypath = _PathLibPath(self._path)
|
||||
|
||||
def getFileName(self):
|
||||
return Path(self._pypath.name)
|
||||
|
||||
def getParent(self):
|
||||
parent = self._pypath.parent
|
||||
if parent == self._pypath:
|
||||
return None
|
||||
return Path(str(parent))
|
||||
|
||||
def getRoot(self):
|
||||
root = self._pypath.anchor
|
||||
return Path(root) if root else None
|
||||
|
||||
def toAbsolutePath(self):
|
||||
return Path(str(self._pypath.absolute()))
|
||||
|
||||
def toRealPath(self):
|
||||
return Path(str(self._pypath.resolve()))
|
||||
|
||||
def getName(self, index):
|
||||
parts = self._pypath.parts
|
||||
if self.getRoot():
|
||||
parts = parts[1:]
|
||||
if index < 0 or index >= len(parts):
|
||||
raise IllegalArgumentException("Invalid index")
|
||||
return Path(parts[index])
|
||||
|
||||
def getNameCount(self):
|
||||
parts = self._pypath.parts
|
||||
if self.getRoot():
|
||||
parts = parts[1:]
|
||||
return len(parts)
|
||||
|
||||
def subpath(self, beginIndex, endIndex):
|
||||
parts = self._pypath.parts
|
||||
if self.getRoot():
|
||||
parts = parts[1:]
|
||||
return Path(_os.path.join(*parts[beginIndex:endIndex]))
|
||||
|
||||
def startsWith(self, other):
|
||||
if isinstance(other, Path):
|
||||
return str(self._pypath).startswith(str(other._pypath))
|
||||
return str(self._pypath).startswith(str(other))
|
||||
|
||||
def endsWith(self, other):
|
||||
if isinstance(other, Path):
|
||||
return str(self._pypath).endswith(str(other._pypath))
|
||||
return str(self._pypath).endswith(str(other))
|
||||
|
||||
def normalize(self):
|
||||
return Path(_os.path.normpath(self._path))
|
||||
|
||||
def resolve(self, other):
|
||||
if isinstance(other, Path):
|
||||
other_path = str(other)
|
||||
else:
|
||||
other_path = str(other)
|
||||
if _os.path.isabs(other_path):
|
||||
return Path(other_path)
|
||||
return Path(_os.path.join(self._path, other_path))
|
||||
|
||||
def resolveSibling(self, other):
|
||||
parent = self.getParent()
|
||||
if parent:
|
||||
return parent.resolve(other)
|
||||
return Path(other)
|
||||
|
||||
def relativize(self, other):
|
||||
if isinstance(other, Path):
|
||||
other_str = str(other._pypath)
|
||||
else:
|
||||
other_str = str(other)
|
||||
try:
|
||||
rel = _os.path.relpath(other_str, self._path)
|
||||
return Path(rel)
|
||||
except ValueError:
|
||||
return other
|
||||
|
||||
def compareTo(self, other):
|
||||
if isinstance(other, Path):
|
||||
return (self._path > other._path) - (self._path < other._path)
|
||||
return -1
|
||||
|
||||
def toString(self):
|
||||
return self._path
|
||||
|
||||
def toFile(self):
|
||||
from ..io import File
|
||||
return File(self._path)
|
||||
|
||||
def isAbsolute(self):
|
||||
return _os.path.isabs(self._path)
|
||||
|
||||
def getFileSystem(self):
|
||||
return FileSystems.getDefault()
|
||||
|
||||
def iterator(self):
|
||||
parts = self._pypath.parts
|
||||
if self.getRoot():
|
||||
parts = parts[1:]
|
||||
return iter([Path(p) for p in parts])
|
||||
|
||||
def register(self, watcher, events, *modifiers):
|
||||
raise AndroidOnlyError("WatchService nio")
|
||||
|
||||
def __str__(self):
|
||||
return self._path
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Path):
|
||||
return self._path == other._path
|
||||
return NotImplemented
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._path)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Paths
|
||||
# ============================================================
|
||||
|
||||
class Paths(Stub):
|
||||
@staticmethod
|
||||
def get(first, *more):
|
||||
parts = [str(first)]
|
||||
parts.extend(str(m) for m in more)
|
||||
return Path(_os.path.join(*parts))
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FileSystem
|
||||
# ============================================================
|
||||
|
||||
class FileSystem(Stub):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._separator = _os.sep
|
||||
self._open = True
|
||||
|
||||
def getPath(self, first, *more):
|
||||
return Paths.get(first, *more)
|
||||
|
||||
def getSeparator(self):
|
||||
return self._separator
|
||||
|
||||
def getRootDirectories(self):
|
||||
if _os.name == "nt":
|
||||
import string
|
||||
return [Path(d + ":\\") for d in string.ascii_uppercase if _os.path.exists(d + ":\\")]
|
||||
return [Path("/")]
|
||||
|
||||
def getUserPrincipalLookupService(self):
|
||||
raise AndroidOnlyError("UserPrincipalLookupService")
|
||||
|
||||
def newWatchService(self):
|
||||
raise AndroidOnlyError("WatchService")
|
||||
|
||||
def getFileStores(self):
|
||||
return [FileStore("/")]
|
||||
|
||||
def supportedFileAttributeViews(self):
|
||||
return ["basic", "owner", "posix"]
|
||||
|
||||
def close(self):
|
||||
self._open = False
|
||||
|
||||
def isOpen(self):
|
||||
return self._open
|
||||
|
||||
def isReadOnly(self):
|
||||
return False
|
||||
|
||||
|
||||
class FileSystems(Stub):
|
||||
_default = None
|
||||
|
||||
@staticmethod
|
||||
def getDefault():
|
||||
if FileSystems._default is None:
|
||||
FileSystems._default = FileSystem()
|
||||
return FileSystems._default
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FileStore
|
||||
# ============================================================
|
||||
|
||||
class FileStore(Stub):
|
||||
def __init__(self, path):
|
||||
super().__init__()
|
||||
self._path = path
|
||||
|
||||
def name(self):
|
||||
return _os.path.basename(self._path) or "/"
|
||||
|
||||
def type(self):
|
||||
return "ext4"
|
||||
|
||||
def isReadOnly(self):
|
||||
return False
|
||||
|
||||
def getTotalSpace(self):
|
||||
try:
|
||||
st = _os.statvfs(self._path)
|
||||
return st.f_frsize * st.f_blocks
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
def getUsableSpace(self):
|
||||
try:
|
||||
st = _os.statvfs(self._path)
|
||||
return st.f_frsize * st.f_bavail
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
def getUnallocatedSpace(self):
|
||||
try:
|
||||
st = _os.statvfs(self._path)
|
||||
return st.f_frsize * st.f_bfree
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
def toString(self):
|
||||
return f"{self.name()} ({self.type()})"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FileVisitor / SimpleFileVisitor
|
||||
# ============================================================
|
||||
|
||||
class FileVisitor(Stub):
|
||||
def preVisitDirectory(self, dir, attrs):
|
||||
return FileVisitResult.CONTINUE
|
||||
|
||||
def postVisitDirectory(self, dir, exc):
|
||||
return FileVisitResult.CONTINUE
|
||||
|
||||
def visitFile(self, file, attrs):
|
||||
return FileVisitResult.CONTINUE
|
||||
|
||||
def visitFileFailed(self, file, exc):
|
||||
return FileVisitResult.CONTINUE
|
||||
|
||||
|
||||
class SimpleFileVisitor(FileVisitor):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DirectoryStream / Filter
|
||||
# ============================================================
|
||||
|
||||
class DirectoryStream(Stub):
|
||||
def __init__(self, path, filter=None):
|
||||
super().__init__()
|
||||
self._path = path
|
||||
self._filter = filter
|
||||
|
||||
def __iter__(self):
|
||||
try:
|
||||
entries = _os.listdir(str(self._path))
|
||||
for entry in entries:
|
||||
p = Path(self._path, entry)
|
||||
if self._filter is None or self._filter.accept(p):
|
||||
yield p
|
||||
except OSError:
|
||||
raise IOException(f"Failed to list directory: {self._path}")
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class DirectoryStreamFilter(Stub):
|
||||
def accept(self, entry):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# ============================================================
|
||||
# PathMatcher
|
||||
# ============================================================
|
||||
|
||||
class PathMatcher(Stub):
|
||||
def __init__(self, pattern):
|
||||
super().__init__()
|
||||
self._pattern = pattern
|
||||
|
||||
def matches(self, path):
|
||||
import fnmatch
|
||||
return fnmatch.fnmatch(str(path), self._pattern)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# WatchService / WatchKey / WatchEvent / Watchable (stubs)
|
||||
# ============================================================
|
||||
|
||||
class Watchable(Stub):
|
||||
def register(self, watcher, events, *modifiers):
|
||||
raise AndroidOnlyError("WatchService")
|
||||
|
||||
class WatchService(Stub):
|
||||
def close(self):
|
||||
pass
|
||||
def poll(self):
|
||||
return None
|
||||
def take(self):
|
||||
raise AndroidOnlyError("WatchService.take")
|
||||
def poll(self, timeout, unit):
|
||||
return None
|
||||
|
||||
class WatchKey(Stub):
|
||||
def isValid(self):
|
||||
return True
|
||||
def pollEvents(self):
|
||||
return []
|
||||
def reset(self):
|
||||
return True
|
||||
def cancel(self):
|
||||
pass
|
||||
def watchable(self):
|
||||
return None
|
||||
|
||||
class WatchEvent(Stub):
|
||||
class Kind(Stub):
|
||||
def name(self):
|
||||
return ""
|
||||
def type(self):
|
||||
return None
|
||||
|
||||
def kind(self):
|
||||
return None
|
||||
def count(self):
|
||||
return 0
|
||||
def context(self):
|
||||
return None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Files
|
||||
# ============================================================
|
||||
|
||||
class Files(Stub):
|
||||
@staticmethod
|
||||
def copy(source, target, *options):
|
||||
src = str(source)
|
||||
dst = str(target)
|
||||
replace = any(isinstance(o, StandardCopyOption) and o == StandardCopyOption.REPLACE_EXISTING for o in options)
|
||||
try:
|
||||
if replace and _os.path.exists(dst):
|
||||
_os.remove(dst)
|
||||
if _os.path.isdir(src):
|
||||
_shutil.copytree(src, dst)
|
||||
else:
|
||||
_shutil.copy2(src, dst)
|
||||
except OSError as e:
|
||||
raise IOException(str(e))
|
||||
|
||||
@staticmethod
|
||||
def move(source, target, *options):
|
||||
src = str(source)
|
||||
dst = str(target)
|
||||
replace = any(isinstance(o, StandardCopyOption) and o == StandardCopyOption.REPLACE_EXISTING for o in options)
|
||||
try:
|
||||
if replace and _os.path.exists(dst):
|
||||
_os.remove(dst)
|
||||
_shutil.move(src, dst)
|
||||
except OSError as e:
|
||||
raise IOException(str(e))
|
||||
|
||||
@staticmethod
|
||||
def delete(path):
|
||||
try:
|
||||
p = str(path)
|
||||
if _os.path.isdir(p):
|
||||
_os.rmdir(p)
|
||||
else:
|
||||
_os.remove(p)
|
||||
except OSError as e:
|
||||
raise NoSuchFileException(str(path))
|
||||
|
||||
@staticmethod
|
||||
def deleteIfExists(path):
|
||||
try:
|
||||
Files.delete(path)
|
||||
return True
|
||||
except NoSuchFileException:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def createDirectories(path):
|
||||
_os.makedirs(str(path), exist_ok=True)
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def createDirectory(path, attrs=None):
|
||||
_os.makedirs(str(path), exist_ok=True)
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def createFile(path, attrs=None):
|
||||
p = str(path)
|
||||
if _os.path.exists(p):
|
||||
raise FileAlreadyExistsException(str(path))
|
||||
open(p, "a").close()
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def createTempFile(prefix, suffix, dir=None):
|
||||
f = _tempfile.NamedTemporaryFile(prefix=prefix, suffix=suffix,
|
||||
dir=str(dir) if dir else None, delete=False)
|
||||
p = f.name
|
||||
f.close()
|
||||
return Path(p)
|
||||
|
||||
@staticmethod
|
||||
def createTempDirectory(prefix, attrs=None):
|
||||
d = _tempfile.mkdtemp(prefix=prefix)
|
||||
return Path(d)
|
||||
|
||||
@staticmethod
|
||||
def exists(path, *options):
|
||||
return _os.path.exists(str(path))
|
||||
|
||||
@staticmethod
|
||||
def notExists(path, *options):
|
||||
return not _os.path.exists(str(path))
|
||||
|
||||
@staticmethod
|
||||
def isReadable(path):
|
||||
return _os.access(str(path), _os.R_OK)
|
||||
|
||||
@staticmethod
|
||||
def isWritable(path):
|
||||
return _os.access(str(path), _os.W_OK)
|
||||
|
||||
@staticmethod
|
||||
def isExecutable(path):
|
||||
return _os.access(str(path), _os.X_OK)
|
||||
|
||||
@staticmethod
|
||||
def isDirectory(path, *options):
|
||||
return _os.path.isdir(str(path))
|
||||
|
||||
@staticmethod
|
||||
def isRegularFile(path, *options):
|
||||
return _os.path.isfile(str(path))
|
||||
|
||||
@staticmethod
|
||||
def isSymbolicLink(path):
|
||||
return _os.path.islink(str(path))
|
||||
|
||||
@staticmethod
|
||||
def isHidden(path):
|
||||
return _os.path.basename(str(path)).startswith(".")
|
||||
|
||||
@staticmethod
|
||||
def getLastModifiedTime(path, *options):
|
||||
return int(_os.path.getmtime(str(path)) * 1000)
|
||||
|
||||
@staticmethod
|
||||
def setLastModifiedTime(path, time):
|
||||
_os.utime(str(path), (time / 1000, time / 1000))
|
||||
|
||||
@staticmethod
|
||||
def getAttribute(path, attribute, *options):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def setAttribute(path, attribute, value, *options):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def getPosixFilePermissions(path, *options):
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def setPosixFilePermissions(path, perms, *options):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def newInputStream(path, *options):
|
||||
from ..io import FileInputStream
|
||||
return FileInputStream(str(path))
|
||||
|
||||
@staticmethod
|
||||
def newOutputStream(path, *options):
|
||||
from ..io import FileOutputStream
|
||||
append = any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options)
|
||||
return FileOutputStream(str(path), append)
|
||||
|
||||
@staticmethod
|
||||
def newBufferedReader(path, charset=None):
|
||||
from ..io import BufferedReader, InputStreamReader, FileInputStream
|
||||
inp = FileInputStream(str(path))
|
||||
reader = InputStreamReader(inp, charset or "utf-8")
|
||||
return BufferedReader(reader)
|
||||
|
||||
@staticmethod
|
||||
def newBufferedWriter(path, charset=None, *options):
|
||||
from ..io import BufferedWriter, OutputStreamWriter, FileOutputStream
|
||||
append = any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options)
|
||||
out = FileOutputStream(str(path), append)
|
||||
writer = OutputStreamWriter(out, charset or "utf-8")
|
||||
return BufferedWriter(writer)
|
||||
|
||||
@staticmethod
|
||||
def readAllBytes(path):
|
||||
with open(str(path), "rb") as f:
|
||||
return f.read()
|
||||
|
||||
@staticmethod
|
||||
def readAllLines(path, charset=None):
|
||||
with open(str(path), "r", encoding=charset or "utf-8") as f:
|
||||
return f.read().splitlines()
|
||||
|
||||
@staticmethod
|
||||
def readString(path, charset=None):
|
||||
with open(str(path), "r", encoding=charset or "utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
@staticmethod
|
||||
def write(path, content, *options):
|
||||
mode = "wb" if isinstance(content, bytes) else "w"
|
||||
open_mode = mode
|
||||
if any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options):
|
||||
open_mode = "ab" if mode == "wb" else "a"
|
||||
with open(str(path), open_mode) as f:
|
||||
f.write(content)
|
||||
|
||||
@staticmethod
|
||||
def writeString(path, content, *options):
|
||||
open_mode = "w"
|
||||
if any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options):
|
||||
open_mode = "a"
|
||||
with open(str(path), open_mode, encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
@staticmethod
|
||||
def list(path):
|
||||
try:
|
||||
return [Path(_os.path.join(str(path), entry)) for entry in _os.listdir(str(path))]
|
||||
except OSError:
|
||||
raise IOException(f"Failed to list directory: {path}")
|
||||
|
||||
@staticmethod
|
||||
def walk(path, maxDepth=None):
|
||||
for root, dirs, files in _os.walk(str(path)):
|
||||
yield Path(root)
|
||||
|
||||
@staticmethod
|
||||
def find(start, maxDepth, matcher, *options):
|
||||
for root, dirs, files in _os.walk(str(start)):
|
||||
for f in files:
|
||||
p = Path(_os.path.join(root, f))
|
||||
if matcher.matches(p):
|
||||
yield p
|
||||
|
||||
@staticmethod
|
||||
def lines(path, charset=None):
|
||||
with open(str(path), "r", encoding=charset or "utf-8") as f:
|
||||
for line in f:
|
||||
yield line.rstrip("\n")
|
||||
|
||||
@staticmethod
|
||||
def getFileStore(path):
|
||||
return FileStore(str(path))
|
||||
|
||||
@staticmethod
|
||||
def probeContentType(path):
|
||||
return "application/octet-stream"
|
||||
|
||||
@staticmethod
|
||||
def getOwner(path, *options):
|
||||
raise AndroidOnlyError("getOwner")
|
||||
|
||||
@staticmethod
|
||||
def setOwner(path, owner, *options):
|
||||
raise AndroidOnlyError("setOwner")
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
Java regex standard library APIs implemented in Python.
|
||||
java.util.regex package - Pattern, Matcher, MatchResult, PatternSyntaxException.
|
||||
"""
|
||||
|
||||
from .._core import Stub
|
||||
from .io import IOException
|
||||
from .lang import RuntimeException
|
||||
import re as _re
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
IllegalStateException = type("IllegalStateException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"Pattern", "Matcher", "MatchResult", "PatternSyntaxException",
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# PatternSyntaxException
|
||||
# ============================================================
|
||||
|
||||
class PatternSyntaxException(IllegalArgumentException):
|
||||
def __init__(self, desc, regex, index):
|
||||
super().__init__(f"{desc}: {regex} at index {index}")
|
||||
self._desc = desc
|
||||
self._regex = regex
|
||||
self._index = index
|
||||
|
||||
def getDescription(self):
|
||||
return self._desc
|
||||
|
||||
def getPattern(self):
|
||||
return self._regex
|
||||
|
||||
def getIndex(self):
|
||||
return self._index
|
||||
|
||||
def getMessage(self):
|
||||
return f"{self._desc} near index {self._index}: {self._regex}"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MatchResult
|
||||
# ============================================================
|
||||
|
||||
class MatchResult(Stub):
|
||||
def start(self, group=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def end(self, group=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def group(self, group=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def groupCount(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Matcher
|
||||
# ============================================================
|
||||
|
||||
class Matcher(MatchResult):
|
||||
def __init__(self, pattern, input_str):
|
||||
super().__init__()
|
||||
self._pattern = pattern
|
||||
self._input = input_str
|
||||
self._re_pattern = pattern._pattern
|
||||
self._match = None
|
||||
self._region_start = 0
|
||||
self._region_end = len(input_str)
|
||||
self._transparent_bounds = False
|
||||
self._anchoring_bounds = True
|
||||
self._last_match = None
|
||||
|
||||
def matches(self):
|
||||
self._match = self._re_pattern.fullmatch(self._input, self._region_start, self._region_end)
|
||||
self._last_match = self._match
|
||||
return self._match is not None
|
||||
|
||||
def find(self, start=None):
|
||||
if start is not None:
|
||||
self._match = self._re_pattern.search(self._input, start, self._region_end)
|
||||
else:
|
||||
if self._match is None:
|
||||
self._match = self._re_pattern.search(self._input, self._region_start, self._region_end)
|
||||
else:
|
||||
pos = self._match.end()
|
||||
self._match = self._re_pattern.search(self._input, pos, self._region_end)
|
||||
if self._match:
|
||||
self._last_match = self._match
|
||||
return self._match is not None
|
||||
|
||||
def lookingAt(self):
|
||||
self._match = self._re_pattern.match(self._input, self._region_start, self._region_end)
|
||||
self._last_match = self._match
|
||||
return self._match is not None
|
||||
|
||||
def start(self, group=0):
|
||||
if self._match is None:
|
||||
raise IllegalStateException("No match available")
|
||||
if group == 0:
|
||||
return self._match.start()
|
||||
return self._match.start(group)
|
||||
|
||||
def end(self, group=0):
|
||||
if self._match is None:
|
||||
raise IllegalStateException("No match available")
|
||||
if group == 0:
|
||||
return self._match.end()
|
||||
return self._match.end(group)
|
||||
|
||||
def group(self, group=0):
|
||||
if self._match is None:
|
||||
raise IllegalStateException("No match available")
|
||||
if isinstance(group, str):
|
||||
return self._match.group(group)
|
||||
return self._match.group(group)
|
||||
|
||||
def groupCount(self):
|
||||
return self._re_pattern.groups
|
||||
|
||||
def replaceAll(self, replacement):
|
||||
return self._re_pattern.sub(replacement, self._input)
|
||||
|
||||
def replaceFirst(self, replacement):
|
||||
return self._re_pattern.sub(replacement, self._input, count=1)
|
||||
|
||||
def appendReplacement(self, sb, replacement):
|
||||
if self._match is None:
|
||||
raise IllegalStateException("No match available")
|
||||
self._append_pos = getattr(self, '_append_pos', 0)
|
||||
sb.append(self._input[self._append_pos:self._match.start()])
|
||||
sb.append(self._re_pattern.sub(replacement, self._match.group(), count=1))
|
||||
self._append_pos = self._match.end()
|
||||
return self
|
||||
|
||||
def appendTail(self, sb):
|
||||
self._append_pos = getattr(self, '_append_pos', 0)
|
||||
sb.append(self._input[self._append_pos:])
|
||||
return sb
|
||||
|
||||
def reset(self, input_str=None):
|
||||
if input_str is not None:
|
||||
self._input = input_str
|
||||
self._region_end = len(input_str)
|
||||
self._match = None
|
||||
self._last_match = None
|
||||
return self
|
||||
|
||||
def region(self, start, end):
|
||||
self._region_start = start
|
||||
self._region_end = end
|
||||
return self
|
||||
|
||||
def regionStart(self):
|
||||
return self._region_start
|
||||
|
||||
def regionEnd(self):
|
||||
return self._region_end
|
||||
|
||||
def hasTransparentBounds(self):
|
||||
return self._transparent_bounds
|
||||
|
||||
def hasAnchoringBounds(self):
|
||||
return self._anchoring_bounds
|
||||
|
||||
def useAnchoringBounds(self, value):
|
||||
self._anchoring_bounds = value
|
||||
return self
|
||||
|
||||
def useTransparentBounds(self, value):
|
||||
self._transparent_bounds = value
|
||||
return self
|
||||
|
||||
def hitEnd(self):
|
||||
return self._match is not None and self._match.end() >= self._region_end
|
||||
|
||||
def requireEnd(self):
|
||||
return self._match is not None and not self.hitEnd()
|
||||
|
||||
def toMatchResult(self):
|
||||
result = Matcher(self._pattern, self._input)
|
||||
result._match = self._match
|
||||
result._last_match = self._last_match
|
||||
return result
|
||||
|
||||
def pattern(self):
|
||||
return self._pattern
|
||||
|
||||
def toString(self):
|
||||
return f"Matcher[pattern={self._pattern.pattern()}]"
|
||||
|
||||
def namedGroups(self):
|
||||
return self._re_pattern.groupindex
|
||||
|
||||
|
||||
class IllegalStateException(RuntimeException):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Pattern
|
||||
# ============================================================
|
||||
|
||||
class Pattern(Stub):
|
||||
CANON_EQ = 128
|
||||
CASE_INSENSITIVE = 2
|
||||
COMMENTS = 4
|
||||
DOTALL = 32
|
||||
LITERAL = 16
|
||||
MULTILINE = 8
|
||||
UNICODE_CASE = 64
|
||||
UNICODE_CHARACTER_CLASS = 256
|
||||
UNIX_LINES = 1
|
||||
|
||||
_re_flags_map = {
|
||||
0: 0, # default
|
||||
2: _re.IGNORECASE,
|
||||
8: _re.MULTILINE,
|
||||
32: _re.DOTALL,
|
||||
64: _re.UNICODE,
|
||||
4: _re.VERBOSE,
|
||||
}
|
||||
|
||||
def __init__(self, regex, flags=0):
|
||||
super().__init__()
|
||||
self._regex = str(regex)
|
||||
self._flags = flags
|
||||
re_flags = 0
|
||||
for java_flag, py_flag in self._re_flags_map.items():
|
||||
if flags & java_flag:
|
||||
re_flags |= py_flag
|
||||
try:
|
||||
self._pattern = _re.compile(self._regex, re_flags)
|
||||
except _re.error as e:
|
||||
raise PatternSyntaxException(str(e), self._regex, e.pos if hasattr(e, 'pos') else 0)
|
||||
|
||||
@staticmethod
|
||||
def compile(regex, flags=0):
|
||||
return Pattern(regex, flags)
|
||||
|
||||
def matcher(self, input_str):
|
||||
return Matcher(self, input_str)
|
||||
|
||||
def split(self, input_str, limit=0):
|
||||
if limit <= 0:
|
||||
parts = self._pattern.split(str(input_str))
|
||||
if limit == 0:
|
||||
while parts and parts[-1] == "":
|
||||
parts.pop()
|
||||
else:
|
||||
parts = self._pattern.split(str(input_str), maxsplit=limit - 1)
|
||||
while len(parts) < limit:
|
||||
parts.append("")
|
||||
return parts
|
||||
|
||||
def splitAsStream(self, input_str):
|
||||
return iter(self.split(input_str))
|
||||
|
||||
@staticmethod
|
||||
def matches(regex, input_str):
|
||||
return bool(_re.fullmatch(regex, str(input_str)))
|
||||
|
||||
@staticmethod
|
||||
def quote(s):
|
||||
return _re.escape(str(s))
|
||||
|
||||
def flags(self):
|
||||
return self._flags
|
||||
|
||||
def pattern(self):
|
||||
return self._regex
|
||||
|
||||
def toString(self):
|
||||
return self._regex
|
||||
|
||||
def predicate(self):
|
||||
return lambda s: bool(self._pattern.fullmatch(str(s)))
|
||||
|
||||
def asPredicate(self):
|
||||
return self.predicate()
|
||||
|
||||
def asMatchPredicate(self):
|
||||
return lambda s: bool(self._pattern.fullmatch(str(s)))
|
||||
@@ -0,0 +1,697 @@
|
||||
"""
|
||||
Java zip standard library APIs implemented in Python.
|
||||
java.util.zip package - ZipFile, ZipEntry, GZIP, CRC, Deflater, Inflater.
|
||||
"""
|
||||
|
||||
from .._core import Stub
|
||||
from .io import IOException, Closeable, Flushable
|
||||
import zipfile as _zipfile
|
||||
import gzip as _gzip
|
||||
import zlib as _zlib
|
||||
import io as _pyio
|
||||
import os as _os
|
||||
from datetime import datetime as _datetime
|
||||
|
||||
__all__ = [
|
||||
"ZipFile", "ZipEntry", "ZipOutputStream", "ZipInputStream",
|
||||
"GZIPInputStream", "GZIPOutputStream",
|
||||
"CRC32", "CheckedInputStream", "CheckedOutputStream",
|
||||
"Adler32",
|
||||
"DataFormatException", "ZipException",
|
||||
"Deflater", "Inflater",
|
||||
"DeflaterOutputStream", "InflaterInputStream",
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Exception classes
|
||||
# ============================================================
|
||||
|
||||
class ZipException(IOException):
|
||||
pass
|
||||
|
||||
class DataFormatException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# CRC32
|
||||
# ============================================================
|
||||
|
||||
class CRC32(Stub):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._value = 0
|
||||
|
||||
def getValue(self):
|
||||
return self._value
|
||||
|
||||
def reset(self):
|
||||
self._value = 0
|
||||
|
||||
def update(self, b, off=None, length=None):
|
||||
if isinstance(b, int):
|
||||
self._value = _zlib.crc32(bytes([b & 0xFF]), self._value) & 0xFFFFFFFF
|
||||
elif isinstance(b, (bytes, bytearray)):
|
||||
if off is not None and length is not None:
|
||||
self._value = _zlib.crc32(bytes(b[off:off+length]), self._value) & 0xFFFFFFFF
|
||||
else:
|
||||
self._value = _zlib.crc32(bytes(b), self._value) & 0xFFFFFFFF
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Adler32
|
||||
# ============================================================
|
||||
|
||||
class Adler32(Stub):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._value = 1
|
||||
|
||||
def getValue(self):
|
||||
return self._value
|
||||
|
||||
def reset(self):
|
||||
self._value = 1
|
||||
|
||||
def update(self, b, off=None, length=None):
|
||||
if isinstance(b, int):
|
||||
self._value = _zlib.adler32(bytes([b & 0xFF]), self._value) & 0xFFFFFFFF
|
||||
elif isinstance(b, (bytes, bytearray)):
|
||||
if off is not None and length is not None:
|
||||
self._value = _zlib.adler32(bytes(b[off:off+length]), self._value) & 0xFFFFFFFF
|
||||
else:
|
||||
self._value = _zlib.adler32(bytes(b), self._value) & 0xFFFFFFFF
|
||||
|
||||
|
||||
# ============================================================
|
||||
# CheckedInputStream / CheckedOutputStream
|
||||
# ============================================================
|
||||
|
||||
class CheckedInputStream(Closeable):
|
||||
def __init__(self, inp, cksum):
|
||||
super().__init__()
|
||||
self._in = inp
|
||||
self._cksum = cksum
|
||||
|
||||
def read(self, b=None, off=None, length=None):
|
||||
if b is None:
|
||||
result = self._in.read()
|
||||
if result >= 0:
|
||||
self._cksum.update(result)
|
||||
return result
|
||||
if off is None:
|
||||
off = 0
|
||||
if length is None:
|
||||
length = len(b)
|
||||
result = self._in.read(b, off, length)
|
||||
if result > 0:
|
||||
self._cksum.update(b[off:off+result])
|
||||
return result
|
||||
|
||||
def getChecksum(self):
|
||||
return self._cksum
|
||||
|
||||
def close(self):
|
||||
self._in.close()
|
||||
|
||||
def skip(self, n):
|
||||
return self._in.skip(n)
|
||||
|
||||
|
||||
class CheckedOutputStream(Closeable, Flushable):
|
||||
def __init__(self, out, cksum):
|
||||
super().__init__()
|
||||
self._out = out
|
||||
self._cksum = cksum
|
||||
|
||||
def write(self, b, off=None, length=None):
|
||||
if isinstance(b, int):
|
||||
self._out.write(b)
|
||||
self._cksum.update(b)
|
||||
else:
|
||||
if off is not None and length is not None:
|
||||
data = bytes(b[off:off+length])
|
||||
else:
|
||||
data = bytes(b)
|
||||
self._out.write(data)
|
||||
self._cksum.update(data)
|
||||
|
||||
def getChecksum(self):
|
||||
return self._cksum
|
||||
|
||||
def flush(self):
|
||||
self._out.flush()
|
||||
|
||||
def close(self):
|
||||
self._out.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ZipEntry
|
||||
# ============================================================
|
||||
|
||||
class ZipEntry(Stub):
|
||||
STORED = 0
|
||||
DEFLATED = 8
|
||||
|
||||
def __init__(self, name):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._method = ZipEntry.DEFLATED
|
||||
self._size = 0
|
||||
self._compressed_size = 0
|
||||
self._crc = 0
|
||||
self._comment = ""
|
||||
self._time = 0
|
||||
self._extra = b""
|
||||
self._is_directory = name.endswith("/")
|
||||
|
||||
def getName(self):
|
||||
return self._name
|
||||
|
||||
def getMethod(self):
|
||||
return self._method
|
||||
|
||||
def setMethod(self, method):
|
||||
self._method = method
|
||||
|
||||
def getSize(self):
|
||||
return self._size
|
||||
|
||||
def setSize(self, size):
|
||||
self._size = size
|
||||
|
||||
def getCompressedSize(self):
|
||||
return self._compressed_size
|
||||
|
||||
def setCompressedSize(self, size):
|
||||
self._compressed_size = size
|
||||
|
||||
def getCrc(self):
|
||||
return self._crc
|
||||
|
||||
def setCrc(self, crc):
|
||||
self._crc = crc
|
||||
|
||||
def getComment(self):
|
||||
return self._comment
|
||||
|
||||
def setComment(self, comment):
|
||||
self._comment = comment
|
||||
|
||||
def getTime(self):
|
||||
return self._time
|
||||
|
||||
def setTime(self, time):
|
||||
self._time = time
|
||||
|
||||
def getExtra(self):
|
||||
return self._extra
|
||||
|
||||
def setExtra(self, extra):
|
||||
self._extra = extra
|
||||
|
||||
def isDirectory(self):
|
||||
return self._is_directory
|
||||
|
||||
def toString(self):
|
||||
return self._name
|
||||
|
||||
def __str__(self):
|
||||
return self._name
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ZipFile
|
||||
# ============================================================
|
||||
|
||||
class ZipFile(Closeable):
|
||||
OPEN_READ = 1
|
||||
OPEN_DELETE = 2
|
||||
|
||||
def __init__(self, file, mode="r"):
|
||||
super().__init__()
|
||||
if isinstance(file, str):
|
||||
path = file
|
||||
else:
|
||||
path = file.getPath()
|
||||
self._path = path
|
||||
self._mode = mode
|
||||
try:
|
||||
self._zfile = _zipfile.ZipFile(path, mode)
|
||||
except Exception as e:
|
||||
raise ZipException(str(e))
|
||||
|
||||
def entries(self):
|
||||
return iter(self._zfile.infolist())
|
||||
|
||||
def getEntry(self, name):
|
||||
try:
|
||||
info = self._zfile.getinfo(name)
|
||||
entry = ZipEntry(info.filename)
|
||||
entry.setMethod(info.compress_type)
|
||||
entry.setSize(info.file_size)
|
||||
entry.setCompressedSize(info.compress_size)
|
||||
entry.setCrc(info.CRC)
|
||||
entry.setTime(int(_datetime(*info.date_time).timestamp() * 1000) if info.date_time else 0)
|
||||
return entry
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def getInputStream(self, entry):
|
||||
try:
|
||||
data = self._zfile.read(entry.getName() if isinstance(entry, ZipEntry) else entry)
|
||||
from ..io import ByteArrayInputStream
|
||||
return ByteArrayInputStream(data)
|
||||
except Exception as e:
|
||||
raise ZipException(str(e))
|
||||
|
||||
def close(self):
|
||||
self._zfile.close()
|
||||
|
||||
def size(self):
|
||||
return len(self._zfile.namelist())
|
||||
|
||||
def getName(self):
|
||||
return self._path
|
||||
|
||||
def getComment(self):
|
||||
return ""
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ZipOutputStream
|
||||
# ============================================================
|
||||
|
||||
class ZipOutputStream(Closeable, Flushable):
|
||||
STORED = 0
|
||||
DEFLATED = 8
|
||||
|
||||
def __init__(self, out):
|
||||
super().__init__()
|
||||
self._out = out
|
||||
self._zfile = _zipfile.ZipFile(out, "w", _zipfile.ZIP_DEFLATED)
|
||||
self._method = ZipOutputStream.DEFLATED
|
||||
self._level = -1
|
||||
self._comment = ""
|
||||
|
||||
def putNextEntry(self, entry):
|
||||
zi = _zipfile.ZipInfo(entry.getName() if isinstance(entry, ZipEntry) else str(entry))
|
||||
if hasattr(entry, 'getTime') and entry.getTime():
|
||||
dt = _datetime.fromtimestamp(entry.getTime() / 1000.0)
|
||||
zi.date_time = dt.timetuple()[:6]
|
||||
self._zfile.writestr(zi, b"")
|
||||
|
||||
def closeEntry(self):
|
||||
pass
|
||||
|
||||
def write(self, b, off=None, length=None):
|
||||
if off is not None and length is not None:
|
||||
self._zfile._zout.write(bytes(b[off:off+length]))
|
||||
else:
|
||||
self._zfile._zout.write(bytes(b) if not isinstance(b, (bytes, bytearray)) else b)
|
||||
|
||||
def finish(self):
|
||||
self._zfile.close()
|
||||
|
||||
def close(self):
|
||||
self._zfile.close()
|
||||
|
||||
def setMethod(self, method):
|
||||
self._method = method
|
||||
|
||||
def setLevel(self, level):
|
||||
self._level = level
|
||||
|
||||
def setComment(self, comment):
|
||||
self._comment = comment
|
||||
|
||||
def flush(self):
|
||||
self._zfile._zout.flush() if hasattr(self._zfile._zout, 'flush') else None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ZipInputStream
|
||||
# ============================================================
|
||||
|
||||
class ZipInputStream(Closeable):
|
||||
def __init__(self, inp):
|
||||
super().__init__()
|
||||
self._inp = inp
|
||||
self._zfile = _zipfile.ZipFile(inp)
|
||||
self._entries = iter(self._zfile.infolist())
|
||||
self._current_entry = None
|
||||
self._current_data = b""
|
||||
self._current_pos = 0
|
||||
|
||||
def getNextEntry(self):
|
||||
try:
|
||||
info = next(self._entries)
|
||||
entry = ZipEntry(info.filename)
|
||||
entry.setMethod(info.compress_type)
|
||||
entry.setSize(info.file_size)
|
||||
entry.setCompressedSize(info.compress_size)
|
||||
entry.setCrc(info.CRC)
|
||||
self._current_entry = info
|
||||
self._current_data = self._zfile.read(info)
|
||||
self._current_pos = 0
|
||||
return entry
|
||||
except StopIteration:
|
||||
self._current_entry = None
|
||||
self._current_data = b""
|
||||
return None
|
||||
|
||||
def read(self, b=None, off=None, length=None):
|
||||
if self._current_entry is None:
|
||||
return -1
|
||||
avail = len(self._current_data) - self._current_pos
|
||||
if avail <= 0:
|
||||
return -1
|
||||
if b is None:
|
||||
val = self._current_data[self._current_pos]
|
||||
self._current_pos += 1
|
||||
return val
|
||||
if off is None:
|
||||
off = 0
|
||||
if length is None:
|
||||
length = len(b)
|
||||
n = min(length, avail)
|
||||
if isinstance(b, bytearray):
|
||||
b[off:off+n] = self._current_data[self._current_pos:self._current_pos+n]
|
||||
self._current_pos += n
|
||||
return n
|
||||
|
||||
def closeEntry(self):
|
||||
self._current_entry = None
|
||||
self._current_data = b""
|
||||
|
||||
def available(self):
|
||||
if self._current_entry is None:
|
||||
return 0
|
||||
return len(self._current_data) - self._current_pos
|
||||
|
||||
def close(self):
|
||||
self._zfile.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# GZIPInputStream
|
||||
# ============================================================
|
||||
|
||||
class GZIPInputStream(Closeable):
|
||||
def __init__(self, inp, size=512):
|
||||
super().__init__()
|
||||
self._inp = inp
|
||||
self._gzip_file = _gzip.GzipFile(fileobj=_pyio.BufferedReader(inp) if hasattr(inp, 'read') else inp)
|
||||
|
||||
def read(self, b=None, off=None, length=None):
|
||||
if b is None:
|
||||
data = self._gzip_file.read(1)
|
||||
return data[0] if data else -1
|
||||
if off is None:
|
||||
off = 0
|
||||
if length is None:
|
||||
length = len(b)
|
||||
data = self._gzip_file.read(length)
|
||||
if not data:
|
||||
return -1
|
||||
n = len(data)
|
||||
if isinstance(b, bytearray):
|
||||
b[off:off+n] = data
|
||||
else:
|
||||
ba = bytearray(b)
|
||||
ba[off:off+n] = data
|
||||
b[:] = bytes(ba)
|
||||
return n
|
||||
|
||||
def close(self):
|
||||
self._gzip_file.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# GZIPOutputStream
|
||||
# ============================================================
|
||||
|
||||
class GZIPOutputStream(Closeable, Flushable):
|
||||
def __init__(self, out, size=512):
|
||||
super().__init__()
|
||||
self._out = out
|
||||
self._gzip_file = _gzip.GzipFile(fileobj=out, mode="wb")
|
||||
|
||||
def write(self, b, off=None, length=None):
|
||||
if isinstance(b, int):
|
||||
self._gzip_file.write(bytes([b & 0xFF]))
|
||||
elif isinstance(b, (bytes, bytearray)):
|
||||
if off is not None and length is not None:
|
||||
self._gzip_file.write(bytes(b[off:off+length]))
|
||||
else:
|
||||
self._gzip_file.write(bytes(b))
|
||||
|
||||
def finish(self):
|
||||
self._gzip_file.close()
|
||||
|
||||
def close(self):
|
||||
self._gzip_file.close()
|
||||
|
||||
def flush(self):
|
||||
self._gzip_file.flush()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Deflater
|
||||
# ============================================================
|
||||
|
||||
class Deflater(Stub):
|
||||
BEST_COMPRESSION = 9
|
||||
BEST_SPEED = 1
|
||||
DEFAULT_COMPRESSION = -1
|
||||
DEFAULT_STRATEGY = 0
|
||||
FILTERED = 1
|
||||
HUFFMAN_ONLY = 2
|
||||
NO_COMPRESSION = 0
|
||||
NO_FLUSH = 0
|
||||
SYNC_FLUSH = 2
|
||||
FULL_FLUSH = 3
|
||||
|
||||
def __init__(self, level=-1, nowrap=False):
|
||||
super().__init__()
|
||||
self._level = level
|
||||
self._nowrap = nowrap
|
||||
self._compress = _zlib.compressobj(level=level if level >= 0 else 6,
|
||||
method=_zlib.DEFLATED,
|
||||
wbits=-_zlib.MAX_WBITS if nowrap else _zlib.MAX_WBITS)
|
||||
self._input_buffer = b""
|
||||
self._finished = False
|
||||
|
||||
def setInput(self, data, off=0, length=-1):
|
||||
if length < 0:
|
||||
length = len(data) - off
|
||||
self._input_buffer = bytes(data[off:off+length])
|
||||
|
||||
def needsInput(self):
|
||||
return len(self._input_buffer) == 0
|
||||
|
||||
def setDictionary(self, dictionary):
|
||||
pass
|
||||
|
||||
def getTotalIn(self):
|
||||
return len(self._input_buffer)
|
||||
|
||||
def getTotalOut(self):
|
||||
return 0
|
||||
|
||||
def deflate(self, output, off=0, length=-1):
|
||||
if length < 0:
|
||||
length = len(output) - off
|
||||
data = self._compress.compress(self._input_buffer)
|
||||
self._input_buffer = b""
|
||||
n = min(len(data), length)
|
||||
if isinstance(output, bytearray):
|
||||
output[off:off+n] = data[:n]
|
||||
else:
|
||||
ba = bytearray(output)
|
||||
ba[off:off+n] = data[:n]
|
||||
output[:] = bytes(ba)
|
||||
return n
|
||||
|
||||
def finish(self):
|
||||
self._finished = True
|
||||
|
||||
def finished(self):
|
||||
return self._finished
|
||||
|
||||
def end(self):
|
||||
pass
|
||||
|
||||
def getAdler(self):
|
||||
return 1
|
||||
|
||||
def reset(self):
|
||||
self._compress = _zlib.compressobj(level=self._level if self._level >= 0 else 6,
|
||||
method=_zlib.DEFLATED,
|
||||
wbits=-_zlib.MAX_WBITS if self._nowrap else _zlib.MAX_WBITS)
|
||||
self._input_buffer = b""
|
||||
self._finished = False
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Inflater
|
||||
# ============================================================
|
||||
|
||||
class Inflater(Stub):
|
||||
def __init__(self, nowrap=False):
|
||||
super().__init__()
|
||||
self._nowrap = nowrap
|
||||
wbits = -_zlib.MAX_WBITS if nowrap else _zlib.MAX_WBITS
|
||||
self._decompress = _zlib.decompressobj(wbits)
|
||||
self._input_buffer = b""
|
||||
self._finished = False
|
||||
|
||||
def setInput(self, data, off=0, length=-1):
|
||||
if length < 0:
|
||||
length = len(data) - off
|
||||
self._input_buffer = bytes(data[off:off+length])
|
||||
|
||||
def needsInput(self):
|
||||
return len(self._input_buffer) == 0
|
||||
|
||||
def setDictionary(self, dictionary):
|
||||
pass
|
||||
|
||||
def getTotalIn(self):
|
||||
return len(self._input_buffer)
|
||||
|
||||
def getTotalOut(self):
|
||||
return 0
|
||||
|
||||
def inflate(self, output, off=0, length=-1):
|
||||
if length < 0:
|
||||
length = len(output) - off
|
||||
try:
|
||||
data = self._decompress.decompress(self._input_buffer)
|
||||
self._input_buffer = b""
|
||||
n = min(len(data), length)
|
||||
if isinstance(output, bytearray):
|
||||
output[off:off+n] = data[:n]
|
||||
else:
|
||||
ba = bytearray(output)
|
||||
ba[off:off+n] = data[:n]
|
||||
output[:] = bytes(ba)
|
||||
return n
|
||||
except _zlib.error as e:
|
||||
raise DataFormatException(str(e))
|
||||
|
||||
def finished(self):
|
||||
return self._finished
|
||||
|
||||
def end(self):
|
||||
pass
|
||||
|
||||
def getAdler(self):
|
||||
return 1
|
||||
|
||||
def needsDictionary(self):
|
||||
return False
|
||||
|
||||
def reset(self):
|
||||
wbits = -_zlib.MAX_WBITS if self._nowrap else _zlib.MAX_WBITS
|
||||
self._decompress = _zlib.decompressobj(wbits)
|
||||
self._input_buffer = b""
|
||||
self._finished = False
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DeflaterOutputStream
|
||||
# ============================================================
|
||||
|
||||
class DeflaterOutputStream(Closeable, Flushable):
|
||||
def __init__(self, out, deflater=None, size=512):
|
||||
super().__init__()
|
||||
self._out = out
|
||||
self._deflater = deflater or Deflater()
|
||||
self._buffer = bytearray(size)
|
||||
|
||||
def write(self, b, off=None, length=None):
|
||||
if isinstance(b, int):
|
||||
self._out.write(bytes([b & 0xFF]))
|
||||
else:
|
||||
if off is not None and length is not None:
|
||||
self._out.write(bytes(b[off:off+length]))
|
||||
else:
|
||||
self._deflater.setInput(bytes(b) if isinstance(b, (bytes, bytearray)) else bytes(b))
|
||||
while not self._deflater.needsInput():
|
||||
n = self._deflater.deflate(self._buffer)
|
||||
if n > 0:
|
||||
self._out.write(bytes(self._buffer[:n]))
|
||||
|
||||
def finish(self):
|
||||
self._deflater.finish()
|
||||
while not self._deflater.finished():
|
||||
n = self._deflater.deflate(self._buffer)
|
||||
if n > 0:
|
||||
self._out.write(bytes(self._buffer[:n]))
|
||||
|
||||
def close(self):
|
||||
self.finish()
|
||||
self._deflater.end()
|
||||
self._out.close()
|
||||
|
||||
def flush(self):
|
||||
self._out.flush()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# InflaterInputStream
|
||||
# ============================================================
|
||||
|
||||
class InflaterInputStream(Closeable):
|
||||
def __init__(self, inp, inflater=None, size=512):
|
||||
super().__init__()
|
||||
self._in = inp
|
||||
self._inflater = inflater or Inflater()
|
||||
self._buffer = bytearray(size)
|
||||
|
||||
def read(self, b=None, off=None, length=None):
|
||||
if b is None:
|
||||
buf = bytearray(1)
|
||||
n = self._read_from_inflater(buf, 0, 1)
|
||||
return buf[0] if n > 0 else -1
|
||||
if off is None:
|
||||
off = 0
|
||||
if length is None:
|
||||
length = len(b)
|
||||
return self._read_from_inflater(b, off, length)
|
||||
|
||||
def _read_from_inflater(self, b, off, length):
|
||||
while not self._inflater.finished():
|
||||
if self._inflater.needsInput():
|
||||
data = self._in.read(self._buffer)
|
||||
if data is None or data == -1 or data == 0:
|
||||
break
|
||||
if isinstance(data, int):
|
||||
break
|
||||
self._inflater.setInput(data)
|
||||
try:
|
||||
n = self._inflater.inflate(b, off, length)
|
||||
if n > 0:
|
||||
return n
|
||||
except DataFormatException:
|
||||
break
|
||||
return -1
|
||||
|
||||
def available(self):
|
||||
return 0
|
||||
|
||||
def close(self):
|
||||
self._inflater.end()
|
||||
self._in.close()
|
||||
|
||||
def mark(self, readlimit):
|
||||
pass
|
||||
|
||||
def reset(self):
|
||||
raise IOException("mark/reset not supported")
|
||||
|
||||
def markSupported(self):
|
||||
return False
|
||||
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user