feat: 新增 42 个 Android API 模块
新增模块覆盖: 系统层 (14): system, security, print, opengl, renderscript, transition, test, service_notification, service_controls, service_quicksettings, service_wallpaper, service_autofill, service_dreams, service_vr 应用层 (13): app_admin, app_backup, app_job, app_usage, app_role, app_people, appwidget, companion, credentials, mtp, sip, telecom, speech 硬件媒体层 (15): hardware_display, hardware_fingerprint, hardware_usb, hardware_input, media_audiofx, media_browse, media_session, media_tv, media_midi, media_projection, uwb, se, view_autofill, view_textclassifier, view_translation 总计 94 个模块,75,342 行代码
This commit is contained in:
1 parent
8ad62be068
commit
506f9ac725
43 files changed
+16704
-23
No files matched your search
@@ -0,0 +1,424 @@
|
||||
"""
|
||||
Android Device Administration API — DevicePolicyManager, DeviceAdminInfo, DeviceAdminReceiver, SystemUpdatePolicy
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os as _os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
from typing import Any, Dict, List, Optional, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"DevicePolicyManager",
|
||||
"DeviceAdminInfo",
|
||||
"DeviceAdminReceiver",
|
||||
"SystemUpdatePolicy",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DevicePolicyManager 常量
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DevicePolicyManager(Stub):
|
||||
"""DevicePolicyManager — 设备策略管理器 (桌面存根)"""
|
||||
|
||||
# --- Action 常量 ---
|
||||
ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN"
|
||||
ACTION_DEVICE_ADMIN_SET_DOWNLOAD_URL = "android.app.action.DEVICE_ADMIN_SET_DOWNLOAD_URL"
|
||||
ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE"
|
||||
ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE"
|
||||
ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE"
|
||||
ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"
|
||||
ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED"
|
||||
ACTION_PROFILE_OWNER_CHANGED = "android.app.action.PROFILE_OWNER_CHANGED"
|
||||
ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD"
|
||||
ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD"
|
||||
ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION"
|
||||
ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED"
|
||||
ACTION_APPLICATION_VERB = "android.app.action.APPLICATION_VERB"
|
||||
ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED"
|
||||
ACTION_ORG_OWNED_PROVISIONING = "android.app.action.ORG_OWNED_PROVISIONING"
|
||||
ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE"
|
||||
ACTION_ADMIN_SERVICE = "android.app.action.ADMIN_SERVICE"
|
||||
|
||||
# --- Password quality constants ---
|
||||
PASSWORD_QUALITY_UNSPECIFIED = 0
|
||||
PASSWORD_QUALITY_BIOMETRIC_WEAK = 0x8000
|
||||
PASSWORD_QUALITY_SOMETHING = 0x10000
|
||||
PASSWORD_QUALITY_NUMERIC = 0x20000
|
||||
PASSWORD_QUALITY_NUMERIC_COMPLEX = 0x30000
|
||||
PASSWORD_QUALITY_ALPHABETIC = 0x40000
|
||||
PASSWORD_QUALITY_ALPHANUMERIC = 0x50000
|
||||
PASSWORD_QUALITY_COMPLEX = 0x60000
|
||||
PASSWORD_QUALITY_MANAGED = 0x80000
|
||||
|
||||
# --- Encryption status ---
|
||||
ENCRYPTION_STATUS_UNSUPPORTED = 0
|
||||
ENCRYPTION_STATUS_INACTIVE = 1
|
||||
ENCRYPTION_STATUS_ACTIVATING = 2
|
||||
ENCRYPTION_STATUS_ACTIVE = 3
|
||||
ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4
|
||||
|
||||
# --- Keyguard disable flags ---
|
||||
KEYGUARD_DISABLE_FEATURES_NONE = 0
|
||||
KEYGUARD_DISABLE_FEATURES_ALL = 0x7FFFFFFF
|
||||
KEYGUARD_DISABLE_SECURE_CAMERA = 2
|
||||
KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4
|
||||
KEYGUARD_DISABLE_TRUST_AGENTS = 8
|
||||
KEYGUARD_DISABLE_FINGERPRINT = 16
|
||||
KEYGUARD_DISABLE_REMOTE_INPUT = 32
|
||||
KEYGUARD_DISABLE_FACE = 64
|
||||
KEYGUARD_DISABLE_IRIS = 128
|
||||
KEYGUARD_DISABLE_PASSCODE = 256
|
||||
|
||||
# --- Lock screen info flags ---
|
||||
FLAG_EVOLVING_DEVICE_ADMIN = 1 << 0
|
||||
FLAG_PARENT_CAN_ACCESS_LOCK_SCREEN = 1 << 1
|
||||
FLAG_SELF_PROFILE_DISABLED = 1 << 2
|
||||
|
||||
# --- Other constants ---
|
||||
PERMISSION_GRANT_STATE_DEFAULT = 0
|
||||
PERMISSION_GRANT_STATE_GRANTED = 1
|
||||
PERMISSION_GRANT_STATE_DENIED = 2
|
||||
STATE_USER_SETUP_COMPLETE = 1
|
||||
STATE_USER_UNMANAGED = 0
|
||||
STATE_USER_MANAGED = 2
|
||||
LEAVE_ALL_KEYS = 0xFFFFFFFF
|
||||
WIPE_EXTERNAL_STORAGE = 1
|
||||
WIPE_RESET_PROTECTION_DATA = 2
|
||||
WIPE_SILENTLY = 4
|
||||
SKIP_SETUP_WIZARD = 1
|
||||
SETTINGS_HOME = "settings_home"
|
||||
SETTINGS_GLOBAL = "settings_global"
|
||||
SETTINGS_SECURE = "settings_secure"
|
||||
SETTINGS_SYSTEM = "settings_system"
|
||||
LOCK_TASK_FEATURE_NONE = 0
|
||||
LOCK_TASK_FEATURE_SYSTEM_INFO = 1
|
||||
LOCK_TASK_FEATURE_KEYGUARD = 2
|
||||
LOCK_TASK_FEATURE_HOME = 4
|
||||
LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 8
|
||||
LOCK_TASK_FEATURE_NOTIFICATIONS = 16
|
||||
LOCK_TASK_FEATURE_OVERVIEW = 32
|
||||
LOCK_TASK_FEATURE_CREDENTIALS = 64
|
||||
LOCK_TASK_FEATURE_ACCESSIBILITY = 128
|
||||
LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START = 256
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._active_admins: List[Dict[str, Any]] = []
|
||||
self._password_quality = self.PASSWORD_QUALITY_UNSPECIFIED
|
||||
self._camera_disabled = False
|
||||
self._keyguard_disabled = False
|
||||
|
||||
def isAdminActive(self, admin: "DeviceAdminInfo") -> bool:
|
||||
"""检查设备管理器是否已激活"""
|
||||
return any(a.getComponent() == admin.getComponent() for a in self._active_admins)
|
||||
|
||||
def isDeviceOwnerApp(self, packageName: str) -> bool:
|
||||
"""检查应用是否为设备所有者"""
|
||||
return False
|
||||
|
||||
def isProfileOwnerApp(self, packageName: str) -> bool:
|
||||
"""检查应用是否为配置文件所有者"""
|
||||
return False
|
||||
|
||||
def resetPassword(self, password: str, flags: int = 0) -> bool:
|
||||
"""重置设备密码 (桌面存根)"""
|
||||
raise AndroidOnlyError("DevicePolicyManager.resetPassword() 仅在 Android 设备上可用")
|
||||
|
||||
def lockNow(self):
|
||||
"""立即锁定设备"""
|
||||
raise AndroidOnlyError("DevicePolicyManager.lockNow() 仅在 Android 设备上可用")
|
||||
|
||||
def wipeData(self, flags: int = 0):
|
||||
"""擦除设备数据"""
|
||||
raise AndroidOnlyError("DevicePolicyManager.wipeData() 仅在 Android 设备上可用")
|
||||
|
||||
def setCameraDisabled(self, admin: "DeviceAdminInfo", disabled: bool):
|
||||
"""设置相机禁用状态"""
|
||||
self._camera_disabled = disabled
|
||||
|
||||
def setKeyguardDisabled(self, admin: "DeviceAdminInfo", disabled: bool) -> bool:
|
||||
"""设置锁屏禁用状态"""
|
||||
self._keyguard_disabled = disabled
|
||||
return True
|
||||
|
||||
def installCaCert(self, admin: "DeviceAdminInfo", certBuffer: bytes) -> bool:
|
||||
"""安装 CA 证书 (桌面存根)"""
|
||||
raise AndroidOnlyError("DevicePolicyManager.installCaCert() 仅在 Android 设备上可用")
|
||||
|
||||
def uninstallCaCert(self, admin: "DeviceAdminInfo", certBuffer: bytes):
|
||||
"""卸载 CA 证书 (桌面存根)"""
|
||||
raise AndroidOnlyError("DevicePolicyManager.uninstallCaCert() 仅在 Android 设备上可用")
|
||||
|
||||
def getActiveAdmins(self) -> Optional[List["DeviceAdminInfo"]]:
|
||||
"""获取活跃的管理器列表"""
|
||||
return list(self._active_admins) if self._active_admins else None
|
||||
|
||||
def createAdminProfileInfo(
|
||||
self,
|
||||
packageName: str,
|
||||
localizedName: str,
|
||||
parentComponent: Any = None,
|
||||
adminInfo: Any = None,
|
||||
profileOwner: bool = False,
|
||||
) -> "DeviceAdminInfo":
|
||||
"""创建管理员配置文件信息"""
|
||||
return DeviceAdminInfo(
|
||||
packageName=packageName,
|
||||
localizedName=localizedName,
|
||||
parentComponent=parentComponent,
|
||||
adminInfo=adminInfo,
|
||||
profileOwner=profileOwner,
|
||||
)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"DevicePolicyManager@active_admins={len(self._active_admins)}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DeviceAdminInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DeviceAdminInfo(Stub):
|
||||
"""DeviceAdminInfo — 设备管理员信息"""
|
||||
|
||||
USES_POLICY_WATCH_LOG = "uses_policy_watch_log"
|
||||
USES_POLICY_DISABLE_KEYGUARD_FEATURES = "uses_policy_disable_keyguard_features"
|
||||
USES_POLICY_DISABLE_CAMERA = "uses_policy_disable_camera"
|
||||
USES_POLICY_RESET_PASSWORD = "uses_policy_reset_password"
|
||||
USES_POLICY_FORCE_LOCK = "uses_policy_force_lock"
|
||||
USES_POLICY_WIPE_DATA = "uses_policy_wipe_data"
|
||||
USES_POLICY_EXPIRE_PASSWORD = "uses_policy_expire_password"
|
||||
USES_POLICY_LIMIT_PASSWORD = "uses_policy_limit_password"
|
||||
USES_ENCRYPTED_STORAGE = "uses_encrypted_storage"
|
||||
USES_POLICY_SETS_DEVICE_OWNER = "uses_policy_sets_device_owner"
|
||||
USES_POLICY_SETS_PROFILE_OWNER = "uses_policy_sets_profile_owner"
|
||||
USES_POLICY_INSTALL_CERT = "uses_policy_install_cert"
|
||||
USES_POLICY_UNINSTALL_CERT = "uses_policy_uninstall_cert"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
packageName: str = "",
|
||||
localizedName: str = "",
|
||||
parentComponent: Any = None,
|
||||
adminInfo: Any = None,
|
||||
profileOwner: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self._packageName = packageName
|
||||
self._localizedName = localizedName
|
||||
self._parentComponent = parentComponent
|
||||
self._adminInfo = adminInfo
|
||||
self._profileOwner = profileOwner
|
||||
|
||||
def getComponent(self) -> Optional[str]:
|
||||
"""获取组件标识"""
|
||||
return self._parentComponent
|
||||
|
||||
def getPackageName(self) -> str:
|
||||
return self._packageName
|
||||
|
||||
def isVisible(self) -> bool:
|
||||
return True
|
||||
|
||||
def isProfileOwner(self) -> bool:
|
||||
return self._profileOwner
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"DeviceAdminInfo(package={self._packageName})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, DeviceAdminInfo):
|
||||
return False
|
||||
return self._packageName == other._packageName and self._parentComponent == other._parentComponent
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._packageName) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DeviceAdminReceiver
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DeviceAdminReceiver(Stub):
|
||||
"""DeviceAdminReceiver — 设备管理员广播接收器"""
|
||||
|
||||
ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"
|
||||
ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"
|
||||
ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"
|
||||
ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED"
|
||||
ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED"
|
||||
ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED"
|
||||
ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING"
|
||||
ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE"
|
||||
ACTION_BUGREPORT_SHARING_ACCEPTED = "android.app.action.BUGREPORT_SHARING_ACCEPTED"
|
||||
ACTION_BUGREPORT_SHARING_DECLINED = "android.app.action.BUGREPORT_SHARING_DECLINED"
|
||||
ACTION_BUGREPORT_FAILED = "android.app.action.BUGREPORT_FAILED"
|
||||
ACTION_NOTIFY_PENDING_SYSTEM_UPDATE = "android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE"
|
||||
ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"
|
||||
ACTION_NETWORK_LOGS_AVAILABLE = "android.app.action.NETWORK_LOGS_AVAILABLE"
|
||||
ACTION_SECURITY_LOGS_AVAILABLE = "android.app.action.SECURITY_LOGS_AVAILABLE"
|
||||
ACTION_USER_ADDED = "android.app.action.USER_ADDED"
|
||||
ACTION_USER_REMOVED = "android.app.action.USER_REMOVED"
|
||||
ACTION_USER_STARTED = "android.app.action.USER_STARTED"
|
||||
ACTION_USER_STOPPED = "android.app.action.USER_STOPPED"
|
||||
ACTION_USER_SWITCHED = "android.app.action.USER_SWITCHED"
|
||||
ACTION_LOCK_TASK_ENTERING = "android.app.action.LOCK_TASK_ENTERING"
|
||||
ACTION_LOCK_TASK_EXITING = "android.app.action.LOCK_TASK_EXITING"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onEnabled(self, context: Any, intent: Any):
|
||||
"""管理员启用回调"""
|
||||
pass
|
||||
|
||||
def onDisabled(self, context: Any, intent: Any):
|
||||
"""管理员禁用回调"""
|
||||
pass
|
||||
|
||||
def onPasswordChanged(self, context: Any, intent: Any):
|
||||
"""密码更改回调"""
|
||||
pass
|
||||
|
||||
def onPasswordExpiring(self, context: Any, intent: Any):
|
||||
"""密码过期回调"""
|
||||
pass
|
||||
|
||||
def onPasswordFailed(self, context: Any, intent: Any):
|
||||
"""密码失败回调"""
|
||||
pass
|
||||
|
||||
def onPasswordSucceeded(self, context: Any, intent: Any):
|
||||
"""密码成功回调"""
|
||||
pass
|
||||
|
||||
def onProfileProvisioningComplete(self, context: Any, intent: Any):
|
||||
"""配置文件配置完成回调"""
|
||||
pass
|
||||
|
||||
def onBugreportSharingAccepted(self, context: Any, intent: Any, bugreportHash: bytes):
|
||||
"""错误报告分享接受回调"""
|
||||
pass
|
||||
|
||||
def onBugreportSharingDeclined(self, context: Any, intent: Any):
|
||||
"""错误报告分享拒绝回调"""
|
||||
pass
|
||||
|
||||
def onBugreportFailed(self, context: Any, intent: Any, bugreportHash: bytes):
|
||||
"""错误报告失败回调"""
|
||||
pass
|
||||
|
||||
def onNotifyPendingSystemUpdate(self, context: Any, intent: Any, updateReceivedTime: int):
|
||||
"""系统更新待处理通知回调"""
|
||||
pass
|
||||
|
||||
def onNetworkLogsAvailable(self, context: Any, intent: Any, batchToken: int, networkLogsCount: int):
|
||||
"""网络日志可用回调"""
|
||||
pass
|
||||
|
||||
def onSecurityLogsAvailable(self, context: Any, intent: Any):
|
||||
"""安全日志可用回调"""
|
||||
pass
|
||||
|
||||
def onUserAdded(self, context: Any, intent: Any, newUserHandle: Any):
|
||||
"""用户添加回调"""
|
||||
pass
|
||||
|
||||
def onUserRemoved(self, context: Any, intent: Any, removedUserHandle: Any):
|
||||
"""用户移除回调"""
|
||||
pass
|
||||
|
||||
def onUserStarted(self, context: Any, intent: Any, userHandle: Any):
|
||||
"""用户启动回调"""
|
||||
pass
|
||||
|
||||
def onUserStopped(self, context: Any, intent: Any, userHandle: Any):
|
||||
"""用户停止回调"""
|
||||
pass
|
||||
|
||||
def onUserSwitched(self, context: Any, intent: Any, switchedUserHandle: Any):
|
||||
"""用户切换回调"""
|
||||
pass
|
||||
|
||||
def onLockTaskModeEntering(self, context: Any, intent: Any, pkg: str):
|
||||
"""锁任务模式进入回调"""
|
||||
pass
|
||||
|
||||
def onLockTaskModeExiting(self, context: Any, intent: Any):
|
||||
"""锁任务模式退出回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"DeviceAdminReceiver@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SystemUpdatePolicy
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SystemUpdatePolicy(Stub):
|
||||
"""SystemUpdatePolicy — 系统更新策略"""
|
||||
|
||||
TYPE_INSTALL_AUTOMATIC = 1
|
||||
TYPE_INSTALL_WINDOWED = 2
|
||||
TYPE_POSTPONE = 3
|
||||
|
||||
def __init__(self, policyType: int = TYPE_INSTALL_AUTOMATIC):
|
||||
super().__init__()
|
||||
self._policyType = policyType
|
||||
self._startTime: Optional[str] = None
|
||||
self._endTime: Optional[str] = None
|
||||
|
||||
def getPolicyType(self) -> int:
|
||||
return self._policyType
|
||||
|
||||
def setPolicyType(self, policyType: int):
|
||||
self._policyType = policyType
|
||||
|
||||
def setInstallWindow(self, startTime: str, endTime: str):
|
||||
"""设置安装窗口时间段 (格式 HH:mm)"""
|
||||
self._startTime = startTime
|
||||
self._endTime = endTime
|
||||
|
||||
def getStartTime(self) -> Optional[str]:
|
||||
return self._startTime
|
||||
|
||||
def getEndTime(self) -> Optional[str]:
|
||||
return self._endTime
|
||||
|
||||
@staticmethod
|
||||
def createPolicy(policyType: int) -> "SystemUpdatePolicy":
|
||||
return SystemUpdatePolicy(policyType=policyType)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SystemUpdatePolicy(type={self._policyType})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, SystemUpdatePolicy):
|
||||
return False
|
||||
return self._policyType == other._policyType
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._policyType & 0x7FFFFFFF
|
||||
@@ -0,0 +1,477 @@
|
||||
"""
|
||||
Android Backup API — BackupManager, BackupAgent, BackupDataInput/Output, RestoreObserver 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import json
|
||||
import os as _os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable, BinaryIO
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, resolve_android_path, _app_data_dir
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"BackupManager",
|
||||
"BackupAgent",
|
||||
"BackupDataInput",
|
||||
"BackupDataOutput",
|
||||
"FullBackupDataOutput",
|
||||
"BackupHelper",
|
||||
"FileBackupHelper",
|
||||
"SharedPreferencesBackupHelper",
|
||||
"RestoreObserver",
|
||||
"BackupDataInputStream",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 备份存储路径
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
_BACKUP_DIR = _os.path.join(_app_data_dir, "backups")
|
||||
|
||||
|
||||
def _ensure_backup_dir():
|
||||
_os.makedirs(_BACKUP_DIR, exist_ok=True)
|
||||
|
||||
|
||||
_ensure_backup_dir()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupManager(Stub):
|
||||
"""BackupManager — 备份管理器 (桌面文件备份模拟)"""
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._backup_enabled = True
|
||||
|
||||
def dataChanged(self, packageName: str = None):
|
||||
"""通知系统数据已更改,需要备份"""
|
||||
if packageName:
|
||||
flag_path = _os.path.join(_BACKUP_DIR, f"{packageName}.pending")
|
||||
elif self._context:
|
||||
flag_path = _os.path.join(_BACKUP_DIR, f"{self._context.getPackageName()}.pending")
|
||||
else:
|
||||
flag_path = _os.path.join(_BACKUP_DIR, "default.pending")
|
||||
with open(flag_path, "w") as f:
|
||||
f.write(time.time())
|
||||
|
||||
def requestRestore(self, observer: "RestoreObserver") -> int:
|
||||
"""请求恢复备份数据"""
|
||||
raise AndroidOnlyError("BackupManager.requestRestore() 仅在 Android 设备上可用")
|
||||
|
||||
def getCurrentTransport(self) -> Optional[str]:
|
||||
"""获取当前备份传输名称"""
|
||||
return "com.android.localtransport.LocalTransport"
|
||||
|
||||
def listAllTransports(self) -> List[str]:
|
||||
"""列出所有可用备份传输"""
|
||||
return ["com.android.localtransport.LocalTransport"]
|
||||
|
||||
def isBackupEnabled(self) -> bool:
|
||||
"""检查备份是否启用"""
|
||||
return self._backup_enabled
|
||||
|
||||
def setBackupEnabled(self, isEnabled: bool):
|
||||
"""设置备份启用状态"""
|
||||
self._backup_enabled = isEnabled
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupManager(backup_enabled={self._backup_enabled})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupAgent
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupAgent(Stub):
|
||||
"""BackupAgent — 备份代理基类"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onCreate(self):
|
||||
"""创建备份代理时调用"""
|
||||
pass
|
||||
|
||||
def onBackup(
|
||||
self,
|
||||
oldState: "BackupDataInput",
|
||||
data: "BackupDataOutput",
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行备份操作"""
|
||||
pass
|
||||
|
||||
def onRestore(
|
||||
self,
|
||||
data: "BackupDataInput",
|
||||
appVersionCode: int,
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行恢复操作"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupAgent@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupDataInput
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupDataInput(Stub):
|
||||
"""BackupDataInput — 备份数据输入流"""
|
||||
|
||||
def __init__(self, filepath: str = ""):
|
||||
super().__init__()
|
||||
self._filepath = filepath
|
||||
self._data: Dict[str, Any] = {}
|
||||
self._keys: List[str] = []
|
||||
self._index = 0
|
||||
if filepath and _os.path.exists(filepath):
|
||||
with open(filepath, "r") as f:
|
||||
self._data = json.load(f)
|
||||
self._keys = list(self._data.keys())
|
||||
|
||||
def readNextHeader(self) -> bool:
|
||||
"""读取下一个数据头"""
|
||||
if self._index < len(self._keys):
|
||||
return True
|
||||
return False
|
||||
|
||||
def getKey(self) -> str:
|
||||
"""获取当前键名"""
|
||||
if self._index < len(self._keys):
|
||||
return self._keys[self._index]
|
||||
return ""
|
||||
|
||||
def getDataSize(self) -> int:
|
||||
"""获取当前数据大小"""
|
||||
if self._index < len(self._keys):
|
||||
val = self._data[self._keys[self._index]]
|
||||
return len(str(val))
|
||||
return 0
|
||||
|
||||
def readEntityData(self, data: bytearray, offset: int, size: int) -> int:
|
||||
"""读取实体数据"""
|
||||
if self._index < len(self._keys):
|
||||
val = str(self._data[self._keys[self._index]])
|
||||
raw = val.encode("utf-8")
|
||||
copy_len = min(size, len(raw) - offset)
|
||||
for i in range(copy_len):
|
||||
data[offset + i] = raw[i]
|
||||
self._index += 1
|
||||
return copy_len
|
||||
return -1
|
||||
|
||||
def skipEntityData(self):
|
||||
"""跳过当前实体数据"""
|
||||
self._index += 1
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupDataInput(file={self._filepath})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupDataOutput
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupDataOutput(Stub):
|
||||
"""BackupDataOutput — 备份数据输出流"""
|
||||
|
||||
def __init__(self, filepath: str = ""):
|
||||
super().__init__()
|
||||
self._filepath = filepath
|
||||
self._data: Dict[str, str] = {}
|
||||
|
||||
def writeEntityHeader(self, key: str, dataSize: int):
|
||||
"""写入实体数据头"""
|
||||
pass
|
||||
|
||||
def writeEntityData(self, data: bytes, size: int):
|
||||
"""写入实体数据"""
|
||||
key = f"entity_{len(self._data)}"
|
||||
self._data[key] = data[:size].decode("utf-8", errors="replace")
|
||||
if self._filepath:
|
||||
_os.makedirs(_os.path.dirname(self._filepath), exist_ok=True)
|
||||
with open(self._filepath, "w") as f:
|
||||
json.dump(self._data, f)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupDataOutput(file={self._filepath})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FullBackupDataOutput
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FullBackupDataOutput(Stub):
|
||||
"""FullBackupDataOutput — 完整备份数据输出"""
|
||||
|
||||
def __init__(self, quota: int = 0, transportFlags: int = 0):
|
||||
super().__init__()
|
||||
self._quota = quota
|
||||
self._transportFlags = transportFlags
|
||||
|
||||
def getQuota(self) -> int:
|
||||
return self._quota
|
||||
|
||||
def getTransportFlags(self) -> int:
|
||||
return self._transportFlags
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"FullBackupDataOutput(quota={self._quota})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupHelper
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupHelper(Stub):
|
||||
"""BackupHelper — 备份辅助接口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def performBackup(
|
||||
self,
|
||||
oldState: "BackupDataInput",
|
||||
data: "BackupDataOutput",
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行备份"""
|
||||
raise AndroidOnlyError("BackupHelper.performBackup() 仅在 Android 设备上可用")
|
||||
|
||||
def restoreEntity(self, data: "BackupDataInput", appVersionCode: int):
|
||||
"""恢复实体"""
|
||||
raise AndroidOnlyError("BackupHelper.restoreEntity() 仅在 Android 设备上可用")
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupHelper@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FileBackupHelper
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FileBackupHelper(BackupHelper):
|
||||
"""FileBackupHelper — 文件备份辅助"""
|
||||
|
||||
def __init__(self, context: Any, files: List[str]):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._files = list(files)
|
||||
|
||||
def performBackup(
|
||||
self,
|
||||
oldState: "BackupDataInput",
|
||||
data: "BackupDataOutput",
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行文件备份"""
|
||||
for fname in self._files:
|
||||
if self._context:
|
||||
filepath = _os.path.join(self._context.getFilesDir(), fname)
|
||||
if _os.path.exists(filepath):
|
||||
with open(filepath, "rb") as f:
|
||||
content = f.read()
|
||||
data.writeEntityHeader(fname, len(content))
|
||||
data.writeEntityData(content, len(content))
|
||||
|
||||
def restoreEntity(self, data: "BackupDataInput", appVersionCode: int):
|
||||
"""恢复文件实体"""
|
||||
key = data.getKey()
|
||||
if self._context and key in self._files:
|
||||
filepath = _os.path.join(self._context.getFilesDir(), key)
|
||||
size = data.getDataSize()
|
||||
buf = bytearray(size)
|
||||
data.readEntityData(buf, 0, size)
|
||||
_os.makedirs(_os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"FileBackupHelper(files={self._files})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, FileBackupHelper):
|
||||
return False
|
||||
return self._files == other._files
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(tuple(self._files)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SharedPreferencesBackupHelper
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SharedPreferencesBackupHelper(BackupHelper):
|
||||
"""SharedPreferencesBackupHelper — SharedPreferences 备份辅助"""
|
||||
|
||||
def __init__(self, context: Any, prefNames: List[str]):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._prefNames = list(prefNames)
|
||||
|
||||
def performBackup(
|
||||
self,
|
||||
oldState: "BackupDataInput",
|
||||
data: "BackupDataOutput",
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行 SharedPreferences 备份"""
|
||||
if not self._context:
|
||||
return
|
||||
prefs_dir = _os.path.join(_app_data_dir, "shared_prefs")
|
||||
for name in self._prefNames:
|
||||
filepath = _os.path.join(prefs_dir, f"{name}.xml")
|
||||
if _os.path.exists(filepath):
|
||||
with open(filepath, "rb") as f:
|
||||
content = f.read()
|
||||
data.writeEntityHeader(name, len(content))
|
||||
data.writeEntityData(content, len(content))
|
||||
|
||||
def restoreEntity(self, data: "BackupDataInput", appVersionCode: int):
|
||||
"""恢复 SharedPreferences"""
|
||||
key = data.getKey()
|
||||
if self._context and key in self._prefNames:
|
||||
prefs_dir = _os.path.join(_app_data_dir, "shared_prefs")
|
||||
filepath = _os.path.join(prefs_dir, f"{key}.xml")
|
||||
size = data.getDataSize()
|
||||
buf = bytearray(size)
|
||||
data.readEntityData(buf, 0, size)
|
||||
_os.makedirs(prefs_dir, exist_ok=True)
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SharedPreferencesBackupHelper(prefs={self._prefNames})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, SharedPreferencesBackupHelper):
|
||||
return False
|
||||
return self._prefNames == other._prefNames
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(tuple(self._prefNames)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RestoreObserver
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RestoreObserver(Stub):
|
||||
"""RestoreObserver — 恢复过程观察者"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onUpdate(self, nowBeingRestored: int, currentPackage: str):
|
||||
"""恢复进度更新回调"""
|
||||
pass
|
||||
|
||||
def onRestoreStarting(self, numPackages: int):
|
||||
"""恢复开始回调"""
|
||||
pass
|
||||
|
||||
def onRestoreFinished(self, error: int):
|
||||
"""恢复完成回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"RestoreObserver@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupDataInputStream
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupDataInputStream(Stub):
|
||||
"""BackupDataInputStream — 备份数据输入流包装器"""
|
||||
|
||||
def __init__(self, dataInput: "BackupDataInput" = None):
|
||||
super().__init__()
|
||||
self._input = dataInput
|
||||
self._pos = 0
|
||||
self._size = 0
|
||||
|
||||
def read(self) -> int:
|
||||
"""读取一个字节"""
|
||||
if self._input and self._pos < self._size:
|
||||
self._pos += 1
|
||||
return 0
|
||||
return -1
|
||||
|
||||
def readFully(self, data: bytearray, offset: int, length: int):
|
||||
"""读取完整数据块"""
|
||||
if self._input:
|
||||
self._input.readEntityData(data, offset, length)
|
||||
|
||||
def size(self) -> int:
|
||||
return self._size
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupDataInputStream(size={self._size})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,444 @@
|
||||
"""
|
||||
Android Job Scheduler API — JobScheduler, JobInfo, JobService, JobParameters, JobWorkItem
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import heapq
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"JobScheduler",
|
||||
"JobInfo",
|
||||
"JobService",
|
||||
"JobParameters",
|
||||
"JobWorkItem",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# JobScheduler
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class JobScheduler(Stub):
|
||||
"""JobScheduler — 作业调度器 (桌面线程模拟)"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._lock = threading.Lock()
|
||||
self._jobs: Dict[int, "JobInfo"] = {}
|
||||
self._next_id = 1
|
||||
self._worker_thread: Optional[threading.Thread] = None
|
||||
self._running = False
|
||||
self._start_worker()
|
||||
|
||||
def _start_worker(self):
|
||||
"""启动后台工作线程"""
|
||||
if self._worker_thread is None or not self._worker_thread.is_alive():
|
||||
self._running = True
|
||||
self._worker_thread = threading.Thread(target=self._run_loop, daemon=True)
|
||||
self._worker_thread.start()
|
||||
|
||||
def _run_loop(self):
|
||||
"""运行作业调度循环"""
|
||||
while self._running:
|
||||
with self._lock:
|
||||
now = time.time()
|
||||
for job_id, info in list(self._jobs.items()):
|
||||
if info._deadline and now >= info._deadline:
|
||||
self._jobs.pop(job_id, None)
|
||||
|
||||
time.sleep(1.0)
|
||||
|
||||
def schedule(self, job: "JobInfo") -> int:
|
||||
"""调度一个作业"""
|
||||
with self._lock:
|
||||
job_id = self._next_id
|
||||
self._next_id += 1
|
||||
job._id = job_id
|
||||
if job._periodic:
|
||||
job._deadline = time.time() + job._period_ms / 1000.0
|
||||
elif job._deadline_ms:
|
||||
job._deadline = time.time() + job._deadline_ms / 1000.0
|
||||
else:
|
||||
job._deadline = None
|
||||
self._jobs[job_id] = job
|
||||
return job_id
|
||||
|
||||
def enqueue(self, job: "JobInfo", workItem: "JobWorkItem") -> int:
|
||||
"""将作业加入队列"""
|
||||
return self.schedule(job)
|
||||
|
||||
def cancel(self, jobId: int):
|
||||
"""取消指定作业"""
|
||||
with self._lock:
|
||||
self._jobs.pop(jobId, None)
|
||||
|
||||
def cancelAll(self):
|
||||
"""取消所有作业"""
|
||||
with self._lock:
|
||||
self._jobs.clear()
|
||||
|
||||
def getAllPendingJobs(self) -> List["JobInfo"]:
|
||||
"""获取所有待处理作业"""
|
||||
with self._lock:
|
||||
return list(self._jobs.values())
|
||||
|
||||
def getPendingJob(self, jobId: int) -> Optional["JobInfo"]:
|
||||
"""获取指定待处理作业"""
|
||||
with self._lock:
|
||||
return self._jobs.get(jobId)
|
||||
|
||||
def getTransientNetwork(self) -> Optional[Any]:
|
||||
"""获取瞬时网络"""
|
||||
return None
|
||||
|
||||
def getStartedJobs(self) -> List["JobInfo"]:
|
||||
"""获取已开始的作业"""
|
||||
return []
|
||||
|
||||
def toString(self) -> str:
|
||||
with self._lock:
|
||||
return f"JobScheduler(pending={len(self._jobs)})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# JobInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class JobInfo(Stub):
|
||||
"""JobInfo — 作业信息"""
|
||||
|
||||
# --- NetworkType 常量 ---
|
||||
NETWORK_TYPE_NONE = 0
|
||||
NETWORK_TYPE_ANY = 1
|
||||
NETWORK_TYPE_UNMETERED = 2
|
||||
NETWORK_TYPE_NOT_ROAMING = 3
|
||||
NETWORK_TYPE_CELLULAR = 4
|
||||
|
||||
# --- Backoff 策略 ---
|
||||
BACKOFF_POLICY_LINEAR = 0
|
||||
BACKOFF_POLICY_EXPONENTIAL = 1
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._id = 0
|
||||
self._service = None
|
||||
self._required_network_type = self.NETWORK_TYPE_NONE
|
||||
self._requires_charging = False
|
||||
self._requires_device_idle = False
|
||||
self._deadline_ms = 0
|
||||
self._min_latency_ms = 0
|
||||
self._period_ms = 0
|
||||
self._periodic = False
|
||||
self._backoff_policy = self.BACKOFF_POLICY_EXPONENTIAL
|
||||
self._backoff_delay_ms = 30000
|
||||
self._extras = None
|
||||
self._transient_network = False
|
||||
self._estimated_network_bytes = -1
|
||||
self._prefetch_network_bytes = -1
|
||||
self._requires_storage_not_low = False
|
||||
|
||||
def getId(self) -> int:
|
||||
return self._id
|
||||
|
||||
def getService(self) -> Optional[Any]:
|
||||
return self._service
|
||||
|
||||
def getRequiredNetworkType(self) -> int:
|
||||
return self._required_network_type
|
||||
|
||||
def isRequiresCharging(self) -> bool:
|
||||
return self._requires_charging
|
||||
|
||||
def isRequiresDeviceIdle(self) -> bool:
|
||||
return self._requires_device_idle
|
||||
|
||||
def isRequiresStorageNotLow(self) -> bool:
|
||||
return self._requires_storage_not_low
|
||||
|
||||
def getMinLatencyMillis(self) -> int:
|
||||
return self._min_latency_ms
|
||||
|
||||
def getMaxExecutionDelayMillis(self) -> int:
|
||||
return self._deadline_ms
|
||||
|
||||
def isPeriodic(self) -> bool:
|
||||
return self._periodic
|
||||
|
||||
def getBackoffPolicy(self) -> int:
|
||||
return self._backoff_policy
|
||||
|
||||
def getInitialBackoffMillis(self) -> int:
|
||||
return self._backoff_delay_ms
|
||||
|
||||
def getEstimatedNetworkBytes(self) -> int:
|
||||
return self._estimated_network_bytes
|
||||
|
||||
def getPrefetchNetworkBytes(self) -> int:
|
||||
return self._prefetch_network_bytes
|
||||
|
||||
def getTransientNetwork(self) -> Optional[Any]:
|
||||
return None
|
||||
|
||||
def isPersisted(self) -> bool:
|
||||
return False
|
||||
|
||||
def getExtras(self) -> Optional[Any]:
|
||||
return self._extras
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Builder
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Builder(Stub):
|
||||
"""JobInfo.Builder — 作业构建器"""
|
||||
|
||||
def __init__(self, jobId: int, jobService: Any):
|
||||
super().__init__()
|
||||
self._info = JobInfo()
|
||||
self._info._id = jobId
|
||||
self._info._service = jobService
|
||||
|
||||
def setRequiredNetworkType(self, networkType: int) -> "JobInfo.Builder":
|
||||
self._info._required_network_type = networkType
|
||||
return self
|
||||
|
||||
def setRequiresCharging(self, requiresCharging: bool) -> "JobInfo.Builder":
|
||||
self._info._requires_charging = requiresCharging
|
||||
return self
|
||||
|
||||
def setRequiresDeviceIdle(self, requiresDeviceIdle: bool) -> "JobInfo.Builder":
|
||||
self._info._requires_device_idle = requiresDeviceIdle
|
||||
return self
|
||||
|
||||
def setRequiresStorageNotLow(self, requiresStorageNotLow: bool) -> "JobInfo.Builder":
|
||||
self._info._requires_storage_not_low = requiresStorageNotLow
|
||||
return self
|
||||
|
||||
def setOverrideDeadline(self, deadlineMillis: int) -> "JobInfo.Builder":
|
||||
self._info._deadline_ms = deadlineMillis
|
||||
return self
|
||||
|
||||
def setMinimumLatency(self, minLatencyMillis: int) -> "JobInfo.Builder":
|
||||
self._info._min_latency_ms = minLatencyMillis
|
||||
return self
|
||||
|
||||
def setBackoffCriteria(
|
||||
self, initialBackoffMillis: int, backoffPolicy: int
|
||||
) -> "JobInfo.Builder":
|
||||
self._info._backoff_delay_ms = initialBackoffMillis
|
||||
self._info._backoff_policy = backoffPolicy
|
||||
return self
|
||||
|
||||
def setPeriodic(self, intervalMillis: int) -> "JobInfo.Builder":
|
||||
self._info._periodic = True
|
||||
self._info._period_ms = intervalMillis
|
||||
return self
|
||||
|
||||
def setTransientNetwork(self, transient: bool) -> "JobInfo.Builder":
|
||||
self._info._transient_network = transient
|
||||
return self
|
||||
|
||||
def setEstimatedNetworkBytes(self, estimatedBytes: int) -> "JobInfo.Builder":
|
||||
self._info._estimated_network_bytes = estimatedBytes
|
||||
return self
|
||||
|
||||
def setPrefetchNetworkBytes(self, prefetchBytes: int) -> "JobInfo.Builder":
|
||||
self._info._prefetch_network_bytes = prefetchBytes
|
||||
return self
|
||||
|
||||
def setExtras(self, extras: Any) -> "JobInfo.Builder":
|
||||
self._info._extras = extras
|
||||
return self
|
||||
|
||||
def build(self) -> "JobInfo":
|
||||
return self._info
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Builder(jobId={self._info._id})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"JobInfo(id={self._id}, network={self._required_network_type})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, JobInfo):
|
||||
return False
|
||||
return self._id == other._id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._id & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# JobService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class JobService(Stub):
|
||||
"""JobService — 作业服务基类"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onStartJob(self, params: "JobParameters") -> bool:
|
||||
"""作业开始时调用。返回 True 表示需要后台线程处理。"""
|
||||
return False
|
||||
|
||||
def onStopJob(self, params: "JobParameters") -> bool:
|
||||
"""作业停止时调用。返回 True 表示应重试。"""
|
||||
return False
|
||||
|
||||
def jobFinished(self, params: "JobParameters", needsReschedule: bool):
|
||||
"""通知系统作业已完成"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"JobService@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# JobParameters
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class JobParameters(Stub):
|
||||
"""JobParameters — 作业参数"""
|
||||
|
||||
# --- StopReason 常量 ---
|
||||
STOP_REASON_UNDEFINED = 0
|
||||
STOP_REASON_DATA_CLEARED = 1
|
||||
STOP_REASON_APP_BACKGROUND = 2
|
||||
STOP_REASON_APP_STANDBY = 3
|
||||
STOP_REASON_DEVICE_IDLE = 4
|
||||
STOP_REASON_PREEXISTING = 5
|
||||
STOP_REASON_ESTIMATED_NETWORK_BYTES_EXCEEDED = 6
|
||||
STOP_REASON_QUOTA = 7
|
||||
STOP_REASON_TAPPED_CANCEL = 8
|
||||
STOP_REASON_USER = 9
|
||||
STOP_REASON_CONSTRAINT_BATTERY_NOT_OK = 10
|
||||
STOP_REASON_CONSTRAINT_NETWORK_NOT_OK = 11
|
||||
STOP_REASON_CONSTRAINT_CONNECTIVITY_NOT_OK = 12
|
||||
STOP_REASON_CONSTRAINT_STORAGE_NOT_OK = 13
|
||||
STOP_REASON_CONSTRAINT_CHARGING_NOT_OK = 14
|
||||
STOP_REASON_CONSTRAINT_IDLE_NOT_OK = 15
|
||||
STOP_REASON_CANCELED = 16
|
||||
STOP_REASON_CANCELLED_BY_APP = 17
|
||||
STOP_REASON_DEVICE_STATE = 18
|
||||
STOP_REASON_SYSTEM_PROCESSING = 19
|
||||
STOP_REASON_WORK_POLICY = 20
|
||||
STOP_REASON_SYSTEM_EXECUTION = 21
|
||||
STOP_REASON_RESTRICTED_BACKGROUND = 22
|
||||
STOP_REASON_TIMEOUT = 23
|
||||
|
||||
def __init__(self, jobId: int = 0):
|
||||
super().__init__()
|
||||
self._job_id = jobId
|
||||
self._extras = None
|
||||
self._network = None
|
||||
self._transient_network = None
|
||||
self._stop_reason = 0
|
||||
self._override_deadline_expired = False
|
||||
self._requires_charging = False
|
||||
self._requires_device_idle = False
|
||||
self._requires_storage_not_low = False
|
||||
self._required_network_type = JobInfo.NETWORK_TYPE_NONE
|
||||
|
||||
def getJobId(self) -> int:
|
||||
return self._job_id
|
||||
|
||||
def getExtras(self) -> Optional[Any]:
|
||||
return self._extras
|
||||
|
||||
def getNetwork(self) -> Optional[Any]:
|
||||
return self._network
|
||||
|
||||
def getTransientNetwork(self) -> Optional[Any]:
|
||||
return self._transient_network
|
||||
|
||||
def getStopReason(self) -> int:
|
||||
return self._stop_reason
|
||||
|
||||
def isOverrideDeadlineExpired(self) -> bool:
|
||||
return self._override_deadline_expired
|
||||
|
||||
def isRequiresCharging(self) -> bool:
|
||||
return self._requires_charging
|
||||
|
||||
def isRequiresDeviceIdle(self) -> bool:
|
||||
return self._requires_device_idle
|
||||
|
||||
def isRequiresStorageNotLow(self) -> bool:
|
||||
return self._requires_storage_not_low
|
||||
|
||||
def getRequiredNetworkType(self) -> int:
|
||||
return self._required_network_type
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"JobParameters(jobId={self._job_id})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, JobParameters):
|
||||
return False
|
||||
return self._job_id == other._job_id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._job_id & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# JobWorkItem
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class JobWorkItem(Stub):
|
||||
"""JobWorkItem — 作业工作项"""
|
||||
|
||||
def __init__(self, intent: Any = None):
|
||||
super().__init__()
|
||||
self._intent = intent
|
||||
self._delivery_count = 0
|
||||
self._latest_delivery_intent = None
|
||||
|
||||
def getIntent(self) -> Optional[Any]:
|
||||
return self._intent
|
||||
|
||||
def getDeliveryCount(self) -> int:
|
||||
return self._delivery_count
|
||||
|
||||
def getLatestDeliveryIntent(self) -> Optional[Any]:
|
||||
return self._latest_delivery_intent
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"JobWorkItem(deliveryCount={self._delivery_count})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, JobWorkItem):
|
||||
return False
|
||||
return self._intent == other._intent
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(str(self._intent)) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,268 @@
|
||||
"""
|
||||
Android People API — PeopleManager, ConversationStatus, PeopleManager.Person, PeopleManager.Conversation
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"PeopleManager",
|
||||
"ConversationStatus",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PeopleManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PeopleManager(Stub):
|
||||
"""PeopleManager — 人物管理器 (桌面存根)"""
|
||||
|
||||
ACTION_PEOPLE_CONVERSATION = "android.app.people.action.PEOPLE_CONVERSATION"
|
||||
ACTION_REMOVE_STATUS = "android.app.people.action.REMOVE_STATUS"
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._statuses: Dict[str, "ConversationStatus"] = {}
|
||||
self._people: Dict[str, "PeopleManager.Person"] = {}
|
||||
self._conversations: Dict[str, "PeopleManager.Conversation"] = {}
|
||||
self._listeners: List[Callable] = []
|
||||
|
||||
def getStatus(self, packageName: str, personUri: str) -> Optional["ConversationStatus"]:
|
||||
"""获取指定人的状态"""
|
||||
key = f"{packageName}:{personUri}"
|
||||
return self._statuses.get(key)
|
||||
|
||||
def getDataIdFromPhoneNumber(self, phoneNumber: str) -> Optional[str]:
|
||||
"""根据电话号码获取数据 ID"""
|
||||
return None
|
||||
|
||||
def addOrUpdateStatus(
|
||||
self, packageName: str, status: "ConversationStatus"
|
||||
):
|
||||
"""添加或更新状态"""
|
||||
key = f"{packageName}:{status.getPersonUri()}"
|
||||
self._statuses[key] = status
|
||||
|
||||
def removeStatus(self, packageName: str, personUri: str):
|
||||
"""移除状态"""
|
||||
key = f"{packageName}:{personUri}"
|
||||
self._statuses.pop(key, None)
|
||||
|
||||
def getStoredPeople(self) -> List["PeopleManager.Person"]:
|
||||
"""获取已存储的人物列表"""
|
||||
return list(self._people.values())
|
||||
|
||||
def isConversation(self, packageName: str, personUri: str) -> bool:
|
||||
"""检查是否为对话"""
|
||||
key = f"{packageName}:{personUri}"
|
||||
return key in self._conversations
|
||||
|
||||
def getConversation(
|
||||
self, packageName: str, personUri: str
|
||||
) -> Optional["PeopleManager.Conversation"]:
|
||||
"""获取对话信息"""
|
||||
key = f"{packageName}:{personUri}"
|
||||
return self._conversations.get(key)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Status - 状态内部类
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Status(Stub):
|
||||
"""PeopleManager.Status — 状态基类"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Status@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Person - 人物内部类
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Person(Stub):
|
||||
"""PeopleManager.Person — 人物信息"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "",
|
||||
key: str = "",
|
||||
uri: str = "",
|
||||
importantForPeople: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._key = key
|
||||
self._uri = uri
|
||||
self._important_for_people = importantForPeople
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getKey(self) -> str:
|
||||
return self._key
|
||||
|
||||
def getUri(self) -> str:
|
||||
return self._uri
|
||||
|
||||
def getImportantForPeople(self) -> bool:
|
||||
return self._important_for_people
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Person(name={self._name}, key={self._key})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, PeopleManager.Person):
|
||||
return False
|
||||
return self._key == other._key
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._key) & 0x7FFFFFFF
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Conversation - 对话内部类
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Conversation(Stub):
|
||||
"""PeopleManager.Conversation — 对话信息"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
conv_id: str = "",
|
||||
person: "PeopleManager.Person" = None,
|
||||
notificationChannelId: str = "",
|
||||
shortcutInfo: Any = None,
|
||||
parentUserHandle: Any = None,
|
||||
):
|
||||
super().__init__()
|
||||
self._id = conv_id
|
||||
self._person = person
|
||||
self._notification_channel_id = notificationChannelId
|
||||
self._shortcut_info = shortcutInfo
|
||||
self._parent_user_handle = parentUserHandle
|
||||
|
||||
def getId(self) -> str:
|
||||
return self._id
|
||||
|
||||
def getPerson(self) -> Optional["PeopleManager.Person"]:
|
||||
return self._person
|
||||
|
||||
def getNotificationChannelId(self) -> str:
|
||||
return self._notification_channel_id
|
||||
|
||||
def getShortcutInfo(self) -> Optional[Any]:
|
||||
return self._shortcut_info
|
||||
|
||||
def getParentUserHandle(self) -> Optional[Any]:
|
||||
return self._parent_user_handle
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Conversation(id={self._id})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, PeopleManager.Conversation):
|
||||
return False
|
||||
return self._id == other._id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._id) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PeopleManager@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ConversationStatus
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ConversationStatus(Stub):
|
||||
"""ConversationStatus — 对话状态"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
userId: int = 0,
|
||||
packageName: str = "",
|
||||
personUri: str = "",
|
||||
status: int = 0,
|
||||
statusIcon: int = 0,
|
||||
notificationKey: str = "",
|
||||
notificationContentDescription: str = "",
|
||||
):
|
||||
super().__init__()
|
||||
self._user_id = userId
|
||||
self._package_name = packageName
|
||||
self._person_uri = personUri
|
||||
self._status = status
|
||||
self._status_icon = statusIcon
|
||||
self._notification_key = notificationKey
|
||||
self._notification_content_description = notificationContentDescription
|
||||
self._active = False
|
||||
self._muted = False
|
||||
|
||||
def getUserId(self) -> int:
|
||||
return self._user_id
|
||||
|
||||
def getPackageName(self) -> str:
|
||||
return self._package_name
|
||||
|
||||
def getPersonUri(self) -> str:
|
||||
return self._person_uri
|
||||
|
||||
def getStatus(self) -> int:
|
||||
return self._status
|
||||
|
||||
def getStatusIcon(self) -> int:
|
||||
return self._status_icon
|
||||
|
||||
def getNotificationKey(self) -> str:
|
||||
return self._notification_key
|
||||
|
||||
def getNotificationContentDescription(self) -> str:
|
||||
return self._notification_content_description
|
||||
|
||||
def isActive(self) -> bool:
|
||||
return self._active
|
||||
|
||||
def isMuted(self) -> bool:
|
||||
return self._muted
|
||||
|
||||
def setActive(self, active: bool):
|
||||
self._active = active
|
||||
|
||||
def setMuted(self, muted: bool):
|
||||
self._muted = muted
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"ConversationStatus(pkg={self._package_name}, "
|
||||
f"uri={self._person_uri}, active={self._active})")
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, ConversationStatus):
|
||||
return False
|
||||
return (self._package_name == other._package_name
|
||||
and self._person_uri == other._person_uri)
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._package_name, self._person_uri)) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Android Role API — RoleManager
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"RoleManager",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RoleManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RoleManager(Stub):
|
||||
"""RoleManager — 角色管理器 (桌面存根)"""
|
||||
|
||||
ACTION_REQUEST_ROLE = "android.app.role.action.REQUEST_ROLE"
|
||||
|
||||
# --- 角色常量 ---
|
||||
ROLE_ASSISTANT = "android.app.role.ASSISTANT"
|
||||
ROLE_BROWSER = "android.app.role.BROWSER"
|
||||
ROLE_DIALER = "android.app.role.DIALER"
|
||||
ROLE_SMS = "android.app.role.SMS"
|
||||
ROLE_HOME = "android.app.role.HOME"
|
||||
ROLE_EMERGENCY = "android.app.role.EMERGENCY"
|
||||
ROLE_CALL_REDIRECTION = "android.app.role.CALL_REDIRECTION"
|
||||
ROLE_SYSTEM_EXCLUSIVE = "android.app.role.SYSTEM_EXCLUSIVE"
|
||||
ROLE_WALLPAPER = "android.app.role.WALLPAPER"
|
||||
ROLE_NOTIFICATION_ACCESS = "android.app.role.NOTIFICATION_ACCESS"
|
||||
ROLE_FOREGROUND_SERVICE = "android.app.role.FOREGROUND_SERVICE"
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
# 模拟桌面角色持有者 (key=角色名, value=持有者包名列表)
|
||||
self._role_holders: Dict[str, List[str]] = {
|
||||
self.ROLE_HOME: ["com.example.launcher"],
|
||||
self.ROLE_BROWSER: ["org.mozilla.firefox"],
|
||||
self.ROLE_DIALER: ["com.google.android.dialer"],
|
||||
self.ROLE_SMS: ["com.google.android.messaging"],
|
||||
}
|
||||
self._listeners: List[Callable] = []
|
||||
|
||||
def isRoleAvailable(self, roleName: str) -> bool:
|
||||
"""检查角色是否可用"""
|
||||
available_roles = [
|
||||
self.ROLE_ASSISTANT, self.ROLE_BROWSER, self.ROLE_DIALER,
|
||||
self.ROLE_SMS, self.ROLE_HOME, self.ROLE_EMERGENCY,
|
||||
self.ROLE_CALL_REDIRECTION, self.ROLE_SYSTEM_EXCLUSIVE,
|
||||
self.ROLE_WALLPAPER, self.ROLE_NOTIFICATION_ACCESS,
|
||||
self.ROLE_FOREGROUND_SERVICE,
|
||||
]
|
||||
return roleName in available_roles
|
||||
|
||||
def isRoleHeld(self, roleName: str) -> bool:
|
||||
"""检查当前应用是否持有指定角色"""
|
||||
if not self._context:
|
||||
return False
|
||||
pkg = self._context.getPackageName() if hasattr(self._context, "getPackageName") else ""
|
||||
holders = self._role_holders.get(roleName, [])
|
||||
return pkg in holders
|
||||
|
||||
def createRequestRoleIntent(self, roleName: str) -> Any:
|
||||
"""创建请求角色的 Intent"""
|
||||
from .app import Intent
|
||||
intent = Intent(self.ACTION_REQUEST_ROLE)
|
||||
intent.putExtra("android.app.role.extra.ROLE_NAME", roleName)
|
||||
return intent
|
||||
|
||||
def getRoleHolders(self, roleName: str) -> List[str]:
|
||||
"""获取角色持有者列表"""
|
||||
return list(self._role_holders.get(roleName, []))
|
||||
|
||||
def addOnRoleHoldersChangedListener(
|
||||
self, executor: Any, listener: Callable
|
||||
):
|
||||
"""添加角色持有者变更监听器"""
|
||||
if listener not in self._listeners:
|
||||
self._listeners.append(listener)
|
||||
|
||||
def removeOnRoleHoldersChangedListener(self, listener: Callable):
|
||||
"""移除角色持有者变更监听器"""
|
||||
if listener in self._listeners:
|
||||
self._listeners.remove(listener)
|
||||
|
||||
def getRoleNamesFromController(self) -> List[str]:
|
||||
"""获取控制器管理的角色名列表"""
|
||||
return [
|
||||
self.ROLE_ASSISTANT, self.ROLE_BROWSER, self.ROLE_DIALER,
|
||||
self.ROLE_SMS, self.ROLE_HOME, self.ROLE_EMERGENCY,
|
||||
self.ROLE_CALL_REDIRECTION, self.ROLE_SYSTEM_EXCLUSIVE,
|
||||
self.ROLE_WALLPAPER, self.ROLE_NOTIFICATION_ACCESS,
|
||||
self.ROLE_FOREGROUND_SERVICE,
|
||||
]
|
||||
|
||||
def getApplicationVisibleRoleNames(self) -> List[str]:
|
||||
"""获取应用可见的角色名列表"""
|
||||
return self.getRoleNamesFromController()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"RoleManager@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,527 @@
|
||||
"""
|
||||
Android Usage Stats API — UsageStatsManager, UsageStats, UsageEvents, ConfigurationStats, StorageStatsManager
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import json
|
||||
import os as _os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
from collections import defaultdict
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, resolve_android_path, _app_data_dir, _sdcard_root
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"UsageStatsManager",
|
||||
"UsageStats",
|
||||
"UsageEvents",
|
||||
"ConfigurationStats",
|
||||
"CacheUsageStats",
|
||||
"NetworkUsageStats",
|
||||
"UsageStatsManagerInternal",
|
||||
"StorageStatsManager",
|
||||
"StorageStats",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsageStatsManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsageStatsManager(Stub):
|
||||
"""UsageStatsManager — 使用统计管理器 (桌面模拟)"""
|
||||
|
||||
INTERVAL_DAILY = 0
|
||||
INTERVAL_WEEKLY = 1
|
||||
INTERVAL_MONTHLY = 2
|
||||
INTERVAL_YEARLY = 3
|
||||
INTERVAL_BEST = 4
|
||||
INTERVAL_COUNT = 5
|
||||
|
||||
USAGE_SOURCE_UNSPECIFIED = 0
|
||||
USAGE_SOURCE_CURRENT_ACTIVITY = 1
|
||||
USAGE_SOURCE_TASK_ROOT_ACTIVITY = 2
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._app_inactive: Dict[str, bool] = {}
|
||||
|
||||
def queryUsageStats(
|
||||
self, intervalType: int, beginTime: int, endTime: int
|
||||
) -> List["UsageStats"]:
|
||||
"""查询使用统计信息"""
|
||||
result: List[UsageStats] = []
|
||||
stats_dir = _os.path.join(_app_data_dir, "usagestats")
|
||||
if _os.path.exists(stats_dir):
|
||||
for fname in _os.listdir(stats_dir):
|
||||
if fname.endswith(".json"):
|
||||
filepath = _os.path.join(stats_dir, fname)
|
||||
try:
|
||||
with open(filepath, "r") as f:
|
||||
data = json.load(f)
|
||||
stats = UsageStats()
|
||||
stats._package_name = data.get("package", fname[:-5])
|
||||
stats._first_time = data.get("first_time", beginTime)
|
||||
stats._last_time = data.get("last_time", endTime)
|
||||
stats._last_used = data.get("last_used", beginTime)
|
||||
stats._total_foreground = data.get("total_foreground", 0)
|
||||
stats._launch_count = data.get("launch_count", 0)
|
||||
if stats._last_time >= beginTime and stats._last_time <= endTime:
|
||||
result.append(stats)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
pass
|
||||
return result
|
||||
|
||||
def queryConfigurations(
|
||||
self, intervalType: int, beginTime: int, endTime: int
|
||||
) -> List["ConfigurationStats"]:
|
||||
"""查询配置统计"""
|
||||
return []
|
||||
|
||||
def queryEvents(
|
||||
self, beginTime: int, endTime: int
|
||||
) -> "UsageEvents":
|
||||
"""查询使用事件"""
|
||||
return UsageEvents()
|
||||
|
||||
def isAppInactive(self, packageName: str) -> bool:
|
||||
"""检查应用是否处于非活跃状态"""
|
||||
return self._app_inactive.get(packageName, False)
|
||||
|
||||
def setAppInactive(self, packageName: str, inactive: bool):
|
||||
"""设置应用非活跃状态"""
|
||||
self._app_inactive[packageName] = inactive
|
||||
|
||||
def onCarrierPrivilegedAppsChanged(self):
|
||||
"""运营商特权应用变更 (桌面存根)"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UsageStatsManager@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsageStats
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsageStats(Stub):
|
||||
"""UsageStats — 应用使用统计"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._package_name = ""
|
||||
self._first_time = 0
|
||||
self._last_time = 0
|
||||
self._last_used = 0
|
||||
self._total_foreground = 0
|
||||
self._launch_count = 0
|
||||
|
||||
def getPackageName(self) -> str:
|
||||
return self._package_name
|
||||
|
||||
def getFirstTimeStamp(self) -> int:
|
||||
return self._first_time
|
||||
|
||||
def getLastTimeStamp(self) -> int:
|
||||
return self._last_time
|
||||
|
||||
def getLastUsedTime(self) -> int:
|
||||
return self._last_used
|
||||
|
||||
def getTotalTimeInForeground(self) -> int:
|
||||
return self._total_foreground
|
||||
|
||||
def getAppLaunchCount(self) -> int:
|
||||
return self._launch_count
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"UsageStats(pkg={self._package_name}, "
|
||||
f"foreground={self._total_foreground}ms, "
|
||||
f"launches={self._launch_count})")
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, UsageStats):
|
||||
return False
|
||||
return self._package_name == other._package_name
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._package_name) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsageEvents
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsageEvents(Stub):
|
||||
"""UsageEvents — 使用事件列表"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._events: List["UsageEvents.Event"] = []
|
||||
self._index = 0
|
||||
|
||||
def getNextEvent(self, eventOut: "UsageEvents.Event") -> bool:
|
||||
"""获取下一个事件"""
|
||||
if self._index < len(self._events):
|
||||
ev = self._events[self._index]
|
||||
eventOut._package_name = ev._package_name
|
||||
eventOut._class_name = ev._class_name
|
||||
eventOut._action = ev._action
|
||||
eventOut._time_stamp = ev._time_stamp
|
||||
self._index += 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def hasNextEvent(self) -> bool:
|
||||
"""检查是否还有下一个事件"""
|
||||
return self._index < len(self._events)
|
||||
|
||||
def resetToStart(self):
|
||||
"""重置到起始位置"""
|
||||
self._index = 0
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Event — 内部事件类
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Event(Stub):
|
||||
"""UsageEvents.Event — 使用事件"""
|
||||
|
||||
# --- Event action constants ---
|
||||
MOVE_TO_FOREGROUND = 1
|
||||
MOVE_TO_BACKGROUND = 2
|
||||
CONFIGURATION_CHANGE = 3
|
||||
SHORTCUT_INVOCATION = 4
|
||||
STANDBY_BUDGET_CHANGED = 5
|
||||
USER_INTERACTION = 6
|
||||
NOTIFICATION_SEEN = 7
|
||||
NOTIFICATION_INTERRUPTION = 8
|
||||
CHOOSER_ACTION = 9
|
||||
APP_COMPONENT_USED = 10
|
||||
ACTIVITY_STOPPED = 11
|
||||
SYSTEM_INTERACTION = 12
|
||||
FLAG_IS_PACKAGE_INSTALL = 13
|
||||
FLAG_NO_INTERRUPTION = 14
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._package_name = ""
|
||||
self._class_name = ""
|
||||
self._action = 0
|
||||
self._time_stamp = 0
|
||||
|
||||
def getPackageName(self) -> str:
|
||||
return self._package_name
|
||||
|
||||
def getClassName(self) -> str:
|
||||
return self._class_name
|
||||
|
||||
def getAction(self) -> int:
|
||||
return self._action
|
||||
|
||||
def getTimeStamp(self) -> int:
|
||||
return self._time_stamp
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Event(pkg={self._package_name}, "
|
||||
f"action={self._action}, time={self._time_stamp})")
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, UsageEvents.Event):
|
||||
return False
|
||||
return (self._package_name == other._package_name
|
||||
and self._action == other._action
|
||||
and self._time_stamp == other._time_stamp)
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._package_name, self._action, self._time_stamp)) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UsageEvents(count={len(self._events)})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, UsageEvents):
|
||||
return False
|
||||
return self._events == other._events
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(len(self._events)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ConfigurationStats
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ConfigurationStats(Stub):
|
||||
"""ConfigurationStats — 配置统计"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._first_time = 0
|
||||
self._last_time = 0
|
||||
self._last_active = 0
|
||||
self._total_active = 0
|
||||
self._activation_count = 0
|
||||
|
||||
def getFirstTimeStamp(self) -> int:
|
||||
return self._first_time
|
||||
|
||||
def getLastTimeStamp(self) -> int:
|
||||
return self._last_time
|
||||
|
||||
def getLastTimeActive(self) -> int:
|
||||
return self._last_active
|
||||
|
||||
def getTotalTimeActive(self) -> int:
|
||||
return self._total_active
|
||||
|
||||
def getActivationCount(self) -> int:
|
||||
return self._activation_count
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ConfigurationStats(active={self._total_active}ms, count={self._activation_count})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, ConfigurationStats):
|
||||
return False
|
||||
return (self._first_time == other._first_time
|
||||
and self._last_time == other._last_time)
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._first_time, self._last_time)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CacheUsageStats
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CacheUsageStats(Stub):
|
||||
"""CacheUsageStats — 缓存使用统计"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._cache_size = 0
|
||||
self._total_size = 0
|
||||
|
||||
def getCacheSize(self) -> int:
|
||||
return self._cache_size
|
||||
|
||||
def getTotalSize(self) -> int:
|
||||
return self._total_size
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CacheUsageStats(cache={self._cache_size}B)"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NetworkUsageStats
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NetworkUsageStats(Stub):
|
||||
"""NetworkUsageStats — 网络使用统计"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._rx_bytes = 0
|
||||
self._tx_bytes = 0
|
||||
self._rx_packets = 0
|
||||
self._tx_packets = 0
|
||||
|
||||
def getRxBytes(self) -> int:
|
||||
return self._rx_bytes
|
||||
|
||||
def getTxBytes(self) -> int:
|
||||
return self._tx_bytes
|
||||
|
||||
def getRxPackets(self) -> int:
|
||||
return self._rx_packets
|
||||
|
||||
def getTxPackets(self) -> int:
|
||||
return self._tx_packets
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"NetworkUsageStats(rx={self._rx_bytes}B, tx={self._tx_bytes}B)"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsageStatsManagerInternal
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsageStatsManagerInternal(Stub):
|
||||
"""UsageStatsManagerInternal — 内部使用统计管理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def reportEvent(self, event: Any):
|
||||
"""报告使用事件"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UsageStatsManagerInternal@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# StorageStatsManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StorageStatsManager(Stub):
|
||||
"""StorageStatsManager — 存储统计管理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def queryStatsForPackage(
|
||||
self, storageUuid: Any, packageName: str, userHandle: Any
|
||||
) -> "StorageStats":
|
||||
"""查询指定应用存储统计"""
|
||||
return self._query_stats(storageUuid, packageName)
|
||||
|
||||
def queryStatsForUid(
|
||||
self, storageUuid: Any, uid: int
|
||||
) -> "StorageStats":
|
||||
"""查询指定 UID 存储统计"""
|
||||
return self._query_stats(storageUuid, f"uid_{uid}")
|
||||
|
||||
def queryStatsForUser(
|
||||
self, storageUuid: Any, userHandle: Any
|
||||
) -> "StorageStats":
|
||||
"""查询指定用户存储统计"""
|
||||
return self._query_stats(storageUuid, f"user_{userHandle}")
|
||||
|
||||
def queryExternalStatsForUser(
|
||||
self, storageUuid: Any, userHandle: Any
|
||||
) -> "StorageStats":
|
||||
"""查询外部用户存储统计"""
|
||||
return self._query_stats(storageUuid, f"external_user_{userHandle}")
|
||||
|
||||
def getFreeBytes(self, storageUuid: Any) -> int:
|
||||
"""获取空闲字节数"""
|
||||
try:
|
||||
st = _os.statvfs(_sdcard_root)
|
||||
return st.f_bavail * st.f_frsize
|
||||
except (OSError, AttributeError):
|
||||
return 1024 * 1024 * 1024 # 默认 1GB
|
||||
|
||||
def getTotalBytes(self, storageUuid: Any) -> int:
|
||||
"""获取总字节数"""
|
||||
try:
|
||||
st = _os.statvfs(_sdcard_root)
|
||||
return st.f_blocks * st.f_frsize
|
||||
except (OSError, AttributeError):
|
||||
return 64 * 1024 * 1024 * 1024 # 默认 64GB
|
||||
|
||||
def _query_stats(self, storage_uuid: Any, tag: str) -> "StorageStats":
|
||||
"""内部查询存储统计"""
|
||||
stats = StorageStats()
|
||||
pkg_dir = _os.path.join(_app_data_dir, "files")
|
||||
if _os.path.exists(pkg_dir):
|
||||
for dirpath, dirnames, filenames in _os.walk(pkg_dir):
|
||||
for fname in filenames:
|
||||
fpath = _os.path.join(dirpath, fname)
|
||||
try:
|
||||
fsize = _os.path.getsize(fpath)
|
||||
ext = _os.path.splitext(fname)[1].lower()
|
||||
if ext in (".jpg", ".png", ".gif", ".webp", ".bmp"):
|
||||
stats._image_bytes += fsize
|
||||
elif ext in (".mp3", ".wav", ".flac", ".ogg", ".aac"):
|
||||
stats._music_bytes += fsize
|
||||
elif ext in (".mp4", ".avi", ".mkv", ".mov", ".webm"):
|
||||
stats._video_bytes += fsize
|
||||
else:
|
||||
stats._data_bytes += fsize
|
||||
except OSError:
|
||||
pass
|
||||
return stats
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"StorageStatsManager@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# StorageStats
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StorageStats(Stub):
|
||||
"""StorageStats — 存储统计"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._app_bytes = 0
|
||||
self._data_bytes = 0
|
||||
self._cache_bytes = 0
|
||||
self._music_bytes = 0
|
||||
self._video_bytes = 0
|
||||
self._image_bytes = 0
|
||||
self._game_bytes = 0
|
||||
|
||||
def getAppBytes(self) -> int:
|
||||
return self._app_bytes
|
||||
|
||||
def getDataBytes(self) -> int:
|
||||
return self._data_bytes
|
||||
|
||||
def getCacheBytes(self) -> int:
|
||||
return self._cache_bytes
|
||||
|
||||
def getMusicBytes(self) -> int:
|
||||
return self._music_bytes
|
||||
|
||||
def getVideoBytes(self) -> int:
|
||||
return self._video_bytes
|
||||
|
||||
def getImageBytes(self) -> int:
|
||||
return self._image_bytes
|
||||
|
||||
def getGameBytes(self) -> int:
|
||||
return self._game_bytes
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"StorageStats(app={self._app_bytes}B, data={self._data_bytes}B, "
|
||||
f"cache={self._cache_bytes}B, music={self._music_bytes}B, "
|
||||
f"video={self._video_bytes}B, image={self._image_bytes}B)")
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, StorageStats):
|
||||
return False
|
||||
return (self._app_bytes == other._app_bytes
|
||||
and self._data_bytes == other._data_bytes)
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._app_bytes, self._data_bytes)) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,360 @@
|
||||
"""
|
||||
Android App Widget API — AppWidgetProvider, AppWidgetManager, AppWidgetProviderInfo, AppWidgetHost, AppWidgetHostView
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"AppWidgetProvider",
|
||||
"AppWidgetManager",
|
||||
"AppWidgetProviderInfo",
|
||||
"AppWidgetHost",
|
||||
"AppWidgetHostView",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AppWidgetProvider
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AppWidgetProvider(Stub):
|
||||
"""AppWidgetProvider — 应用小部件提供者"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onUpdate(self, context: Any, appWidgetManager: "AppWidgetManager", appWidgetIds: List[int]):
|
||||
"""小部件更新回调"""
|
||||
pass
|
||||
|
||||
def onDeleted(self, context: Any, appWidgetIds: List[int]):
|
||||
"""小部件删除回调"""
|
||||
pass
|
||||
|
||||
def onEnabled(self, context: Any):
|
||||
"""首个小部件启用回调"""
|
||||
pass
|
||||
|
||||
def onDisabled(self, context: Any):
|
||||
"""最后一个小部件禁用回调"""
|
||||
pass
|
||||
|
||||
def onAppWidgetOptionsChanged(
|
||||
self, context: Any, appWidgetManager: "AppWidgetManager",
|
||||
appWidgetId: int, newOptions: Dict[str, Any]
|
||||
):
|
||||
"""小部件选项变更回调"""
|
||||
pass
|
||||
|
||||
def onReceive(self, context: Any, intent: Any):
|
||||
"""接收广播回调"""
|
||||
pass
|
||||
|
||||
def onRestored(self, context: Any, oldWidgetIds: List[int], newWidgetId: int):
|
||||
"""小部件恢复回调"""
|
||||
pass
|
||||
|
||||
def onProviderChanged(
|
||||
self, context: Any, appWidgetManager: "AppWidgetManager",
|
||||
appWidgetId: int, newInfo: "AppWidgetProviderInfo"
|
||||
):
|
||||
"""提供者变更回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AppWidgetProvider@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AppWidgetManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AppWidgetManager(Stub):
|
||||
"""AppWidgetManager — 应用小部件管理器 (桌面存根)"""
|
||||
|
||||
ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"
|
||||
ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED"
|
||||
ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED"
|
||||
ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED"
|
||||
ACTION_APPWIDGET_OPTIONS_CHANGED = "android.appwidget.action.APPWIDGET_OPTIONS_CHANGED"
|
||||
ACTION_APPWIDGET_RESTORED = "android.appwidget.action.APPWIDGET_RESTORED"
|
||||
ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED"
|
||||
EXTRA_APPWIDGET_ID = "appWidgetId"
|
||||
EXTRA_APPWIDGET_IDS = "appWidgetIds"
|
||||
EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds"
|
||||
EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions"
|
||||
EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider"
|
||||
EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile"
|
||||
WIDGET_CATEGORY_HOME_SCREEN = 1
|
||||
WIDGET_CATEGORY_KEYGUARD = 2
|
||||
WIDGET_CATEGORY_SEARCHBOX = 4
|
||||
OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth"
|
||||
OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight"
|
||||
OPTION_APPWIDGET_MAX_WIDTH = "appWidgetMaxWidth"
|
||||
OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight"
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._widget_views: Dict[int, Any] = {}
|
||||
self._widget_options: Dict[int, Dict[str, Any]] = {}
|
||||
self._providers: List["AppWidgetProviderInfo"] = []
|
||||
self._next_widget_id = 1
|
||||
|
||||
@staticmethod
|
||||
def getInstance(context: Any) -> "AppWidgetManager":
|
||||
"""获取 AppWidgetManager 实例"""
|
||||
return AppWidgetManager(context)
|
||||
|
||||
def getAppWidgetIds(self, info: "AppWidgetProviderInfo") -> List[int]:
|
||||
"""获取指定提供者的小部件 ID 列表"""
|
||||
return list(self._widget_views.keys())
|
||||
|
||||
def updateAppWidget(self, appWidgetId: int, view: Any):
|
||||
"""更新小部件视图"""
|
||||
self._widget_views[appWidgetId] = view
|
||||
|
||||
def updateAppWidgetOptions(self, appWidgetId: int, options: Dict[str, Any]):
|
||||
"""更新小部件选项"""
|
||||
if appWidgetId in self._widget_options:
|
||||
self._widget_options[appWidgetId].update(options)
|
||||
else:
|
||||
self._widget_options[appWidgetId] = dict(options)
|
||||
|
||||
def getAppWidgetOptions(self, appWidgetId: int) -> Dict[str, Any]:
|
||||
"""获取小部件选项"""
|
||||
return self._widget_options.get(appWidgetId, {})
|
||||
|
||||
def bindAppWidgetIdIfAllowed(
|
||||
self, appWidgetId: int, info: "AppWidgetProviderInfo"
|
||||
) -> bool:
|
||||
"""绑定小部件 ID(如允许)"""
|
||||
return True
|
||||
|
||||
def getAppWidgetInfo(self, appWidgetId: int) -> Optional["AppWidgetProviderInfo"]:
|
||||
"""获取小部件信息"""
|
||||
if appWidgetId in self._widget_views:
|
||||
return self._providers[0] if self._providers else None
|
||||
return None
|
||||
|
||||
def getInstalledProviders(self) -> List["AppWidgetProviderInfo"]:
|
||||
"""获取已安装的提供者列表"""
|
||||
return list(self._providers)
|
||||
|
||||
def isBindAppWidgetIdAllowed(self, appWidgetId: int, info: "AppWidgetProviderInfo") -> bool:
|
||||
"""检查是否允许绑定小部件 ID"""
|
||||
return True
|
||||
|
||||
def createAppWidgetConfigIntentSender(
|
||||
self, info: "AppWidgetProviderInfo", appWidgetId: int
|
||||
) -> Optional[Any]:
|
||||
"""创建小部件配置 IntentSender"""
|
||||
return None
|
||||
|
||||
def requestPinAppWidget(
|
||||
self, info: "AppWidgetProviderInfo", successCallback: Any = None
|
||||
) -> bool:
|
||||
"""请求固定小部件"""
|
||||
return True
|
||||
|
||||
def getAppWidgetHostViews(self, host: "AppWidgetHost") -> List["AppWidgetHostView"]:
|
||||
"""获取主机视图列表"""
|
||||
return []
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AppWidgetManager(widgets={len(self._widget_views)})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AppWidgetProviderInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AppWidgetProviderInfo(Stub):
|
||||
"""AppWidgetProviderInfo — 小部件提供者信息"""
|
||||
|
||||
# --- ResizeMode ---
|
||||
RESIZE_MODE_NONE = 0
|
||||
RESIZE_MODE_HORIZONTAL = 1
|
||||
RESIZE_MODE_VERTICAL = 2
|
||||
RESIZE_MODE_RESIZE_ALL = 3
|
||||
|
||||
# --- WidgetCategory ---
|
||||
WIDGET_CATEGORY_HOME_SCREEN = 1
|
||||
WIDGET_CATEGORY_KEYGUARD = 2
|
||||
WIDGET_CATEGORY_SEARCHBOX = 4
|
||||
|
||||
# --- WidgetFeatures ---
|
||||
WIDGET_FEATURE_HIDE_FROM_PICKER = 1
|
||||
WIDGET_FEATURE_CONFIGURATION_OPTIONAL = 2
|
||||
WIDGET_FEATURE_RECONFIGURABLE = 4
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.provider: Any = None
|
||||
self.configure: Any = None
|
||||
self.icon: int = 0
|
||||
self.label: str = ""
|
||||
self.previewImage: int = 0
|
||||
self.previewLayout: int = 0
|
||||
self.autoAdvanceViewId: int = -1
|
||||
self.resizeMode: int = self.RESIZE_MODE_NONE
|
||||
self.widgetCategory: int = self.WIDGET_CATEGORY_HOME_SCREEN
|
||||
self.widgetFeatures: int = 0
|
||||
self.description: str = ""
|
||||
self.targetCellWidth: int = 0
|
||||
self.targetCellHeight: int = 0
|
||||
self.minWidth: int = 0
|
||||
self.minHeight: int = 0
|
||||
self.maxWidth: int = 0
|
||||
self.maxHeight: int = 0
|
||||
self.minResizeWidth: int = 0
|
||||
self.minResizeHeight: int = 0
|
||||
self.updatePeriodMillis: int = 0
|
||||
self.initialLayout: int = 0
|
||||
|
||||
def getProfile(self) -> Optional[Any]:
|
||||
"""获取配置文件"""
|
||||
return None
|
||||
|
||||
def getActivityInfo(self) -> Optional[Any]:
|
||||
"""获取 Activity 信息"""
|
||||
return None
|
||||
|
||||
def getIcon(self) -> int:
|
||||
return self.icon
|
||||
|
||||
def getLabel(self) -> str:
|
||||
return self.label
|
||||
|
||||
def loadPreviewImage(self, context: Any, density: int) -> Any:
|
||||
"""加载预览图像"""
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AppWidgetProviderInfo(label={self.label}, icon={self.icon})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, AppWidgetProviderInfo):
|
||||
return False
|
||||
return self.provider == other.provider and self.label == other.label
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((str(self.provider), self.label)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AppWidgetHost
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AppWidgetHost(Stub):
|
||||
"""AppWidgetHost — 小部件主机"""
|
||||
|
||||
def __init__(self, context: Any, hostId: int):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._host_id = hostId
|
||||
self._widget_ids: List[int] = []
|
||||
self._listening = False
|
||||
|
||||
def createView(
|
||||
self, context: Any, appWidgetId: int, info: "AppWidgetProviderInfo"
|
||||
) -> "AppWidgetHostView":
|
||||
"""创建小部件视图"""
|
||||
view = AppWidgetHostView(context)
|
||||
view.setAppWidget(appWidgetId, info)
|
||||
return view
|
||||
|
||||
def startListening(self):
|
||||
"""开始监听"""
|
||||
self._listening = True
|
||||
|
||||
def stopListening(self):
|
||||
"""停止监听"""
|
||||
self._listening = False
|
||||
|
||||
def allocateAppWidgetId(self) -> int:
|
||||
"""分配小部件 ID"""
|
||||
widget_id = len(self._widget_ids) + 1000 + self._host_id
|
||||
self._widget_ids.append(widget_id)
|
||||
return widget_id
|
||||
|
||||
def deleteAppWidgetId(self, appWidgetId: int):
|
||||
"""删除小部件 ID"""
|
||||
if appWidgetId in self._widget_ids:
|
||||
self._widget_ids.remove(appWidgetId)
|
||||
|
||||
def deleteHost(self):
|
||||
"""删除主机"""
|
||||
self._widget_ids.clear()
|
||||
|
||||
def clearViews(self):
|
||||
"""清除所有视图"""
|
||||
self._widget_ids.clear()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AppWidgetHost(hostId={self._host_id}, widgets={len(self._widget_ids)})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, AppWidgetHost):
|
||||
return False
|
||||
return self._host_id == other._host_id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._host_id & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AppWidgetHostView
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AppWidgetHostView(Stub):
|
||||
"""AppWidgetHostView — 小部件主机视图"""
|
||||
|
||||
def __init__(self, context: Any):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._app_widget_id = -1
|
||||
self._info: Optional["AppWidgetProviderInfo"] = None
|
||||
|
||||
def setAppWidget(self, appWidgetId: int, info: "AppWidgetProviderInfo"):
|
||||
"""设置小部件"""
|
||||
self._app_widget_id = appWidgetId
|
||||
self._info = info
|
||||
|
||||
def updateAppWidget(self, info: "AppWidgetProviderInfo"):
|
||||
"""更新小部件"""
|
||||
self._info = info
|
||||
|
||||
def getAppWidgetInfo(self) -> Optional["AppWidgetProviderInfo"]:
|
||||
return self._info
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AppWidgetHostView(widgetId={self._app_widget_id})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, AppWidgetHostView):
|
||||
return False
|
||||
return self._app_widget_id == other._app_widget_id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._app_widget_id & 0x7FFFFFFF
|
||||
@@ -0,0 +1,415 @@
|
||||
"""
|
||||
Android Companion Device API — CompanionDeviceManager, AssociationInfo, DeviceFilters
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"CompanionDeviceManager",
|
||||
"AssociationInfo",
|
||||
"WifiDeviceFilter",
|
||||
"BluetoothDeviceFilter",
|
||||
"BluetoothLeDeviceFilter",
|
||||
"CommsDevice",
|
||||
"DeviceActivityCallback",
|
||||
"CompanionService",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CompanionDeviceManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CompanionDeviceManager(Stub):
|
||||
"""CompanionDeviceManager — 配套设备管理器 (桌面存根)"""
|
||||
|
||||
EXTRA_DEVICE = "android.companion.extra.DEVICE"
|
||||
EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION"
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._associations: Dict[str, "AssociationInfo"] = {}
|
||||
self._callbacks: List[Callable] = []
|
||||
|
||||
def associate(
|
||||
self,
|
||||
request: Any,
|
||||
callback: "CompanionDeviceManager.Callback",
|
||||
handler: Any = None,
|
||||
):
|
||||
"""发起设备关联请求"""
|
||||
raise AndroidOnlyError("CompanionDeviceManager.associate() 仅在 Android 设备上可用")
|
||||
|
||||
def associateCallback(
|
||||
self,
|
||||
request: Any,
|
||||
callback: Callable,
|
||||
executor: Any = None,
|
||||
):
|
||||
"""使用回调方式发起设备关联"""
|
||||
raise AndroidOnlyError("CompanionDeviceManager.associateCallback() 仅在 Android 设备上可用")
|
||||
|
||||
def requestNotificationAccess(self, pairingDevice: "AssociationInfo"):
|
||||
"""请求通知访问权限"""
|
||||
raise AndroidOnlyError("CompanionDeviceManager.requestNotificationAccess() 仅在 Android 设备上可用")
|
||||
|
||||
def requestSystemAppStore(self, pairingDevice: "AssociationInfo"):
|
||||
"""请求系统应用商店"""
|
||||
pass
|
||||
|
||||
def startObservingDevicePresence(self, deviceAddress: str):
|
||||
"""开始监听设备存在"""
|
||||
pass
|
||||
|
||||
def stopObservingDevicePresence(self, deviceAddress: str):
|
||||
"""停止监听设备存在"""
|
||||
pass
|
||||
|
||||
def isDeviceAssociated(self, deviceAddress: str) -> bool:
|
||||
"""检查设备是否已关联"""
|
||||
return deviceAddress in self._associations
|
||||
|
||||
def getDeviceId(self, deviceAddress: str) -> Optional[str]:
|
||||
"""获取设备 ID"""
|
||||
info = self._associations.get(deviceAddress)
|
||||
return info.getSelfManagedDeviceId() if info else None
|
||||
|
||||
def getAssociations(self) -> List["AssociationInfo"]:
|
||||
"""获取所有关联信息"""
|
||||
return list(self._associations.values())
|
||||
|
||||
def removeAssociation(self, deviceAddress: str):
|
||||
"""移除设备关联"""
|
||||
self._associations.pop(deviceAddress, None)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Callback - 回调类
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Callback(Stub):
|
||||
"""CompanionDeviceManager.Callback — 设备发现回调"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onDeviceFound(self, intent: Any):
|
||||
"""设备找到回调"""
|
||||
pass
|
||||
|
||||
def onFailure(self, error: str):
|
||||
"""设备发现失败回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Callback@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CompanionDeviceManager(associations={len(self._associations)})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AssociationInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AssociationInfo(Stub):
|
||||
"""AssociationInfo — 设备关联信息"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
userId: int = 0,
|
||||
deviceProfile: str = "",
|
||||
selfManagedDeviceId: str = "",
|
||||
associatedDevice: Any = None,
|
||||
isSelfManaged: bool = False,
|
||||
isNotAssociated: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self._user_id = userId
|
||||
self._device_profile = deviceProfile
|
||||
self._self_managed_device_id = selfManagedDeviceId
|
||||
self._associated_device = associatedDevice
|
||||
self._self_managed = isSelfManaged
|
||||
self._not_associated = isNotAssociated
|
||||
|
||||
def getUserId(self) -> int:
|
||||
return self._user_id
|
||||
|
||||
def getDeviceProfile(self) -> str:
|
||||
return self._device_profile
|
||||
|
||||
def getSelfManagedDeviceId(self) -> str:
|
||||
return self._self_managed_device_id
|
||||
|
||||
def getAssociatedDevice(self) -> Optional[Any]:
|
||||
return self._associated_device
|
||||
|
||||
def isSelfManaged(self) -> bool:
|
||||
return self._self_managed
|
||||
|
||||
def isNotAssociated(self) -> bool:
|
||||
return self._not_associated
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"AssociationInfo(deviceId={self._self_managed_device_id}, "
|
||||
f"profile={self._device_profile})")
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, AssociationInfo):
|
||||
return False
|
||||
return self._self_managed_device_id == other._self_managed_device_id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._self_managed_device_id) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WifiDeviceFilter
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WifiDeviceFilter(Stub):
|
||||
"""WifiDeviceFilter — WiFi 设备过滤器"""
|
||||
|
||||
def __init__(self, namePattern: str = ""):
|
||||
super().__init__()
|
||||
self._name_pattern = namePattern
|
||||
|
||||
def getNamePattern(self) -> str:
|
||||
return self._name_pattern
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"WifiDeviceFilter(pattern={self._name_pattern})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, WifiDeviceFilter):
|
||||
return False
|
||||
return self._name_pattern == other._name_pattern
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._name_pattern) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothDeviceFilter
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothDeviceFilter(Stub):
|
||||
"""BluetoothDeviceFilter — 蓝牙设备过滤器"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""BluetoothDeviceFilter.Builder — 蓝牙过滤器构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._name_pattern = ""
|
||||
self._address = ""
|
||||
|
||||
def setNamePattern(self, pattern: str) -> "BluetoothDeviceFilter.Builder":
|
||||
self._name_pattern = pattern
|
||||
return self
|
||||
|
||||
def setAddress(self, address: str) -> "BluetoothDeviceFilter.Builder":
|
||||
self._address = address
|
||||
return self
|
||||
|
||||
def build(self) -> "BluetoothDeviceFilter":
|
||||
return BluetoothDeviceFilter(
|
||||
namePattern=self._name_pattern,
|
||||
address=self._address,
|
||||
)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Builder(pattern={self._name_pattern})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def __init__(self, namePattern: str = "", address: str = ""):
|
||||
super().__init__()
|
||||
self._name_pattern = namePattern
|
||||
self._address = address
|
||||
|
||||
def getNamePattern(self) -> str:
|
||||
return self._name_pattern
|
||||
|
||||
def getAddress(self) -> str:
|
||||
return self._address
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BluetoothDeviceFilter(address={self._address})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, BluetoothDeviceFilter):
|
||||
return False
|
||||
return self._address == other._address
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._address) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BluetoothLeDeviceFilter
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BluetoothLeDeviceFilter(Stub):
|
||||
"""BluetoothLeDeviceFilter — BLE 设备过滤器"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""BluetoothLeDeviceFilter.Builder — BLE 过滤器构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._name_pattern = ""
|
||||
self._scan_filter = None
|
||||
|
||||
def setNamePattern(self, pattern: str) -> "BluetoothLeDeviceFilter.Builder":
|
||||
self._name_pattern = pattern
|
||||
return self
|
||||
|
||||
def setScanFilter(self, scanFilter: Any) -> "BluetoothLeDeviceFilter.Builder":
|
||||
self._scan_filter = scanFilter
|
||||
return self
|
||||
|
||||
def build(self) -> "BluetoothLeDeviceFilter":
|
||||
return BluetoothLeDeviceFilter(
|
||||
namePattern=self._name_pattern,
|
||||
scanFilter=self._scan_filter,
|
||||
)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BLEBuilder(pattern={self._name_pattern})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def __init__(self, namePattern: str = "", scanFilter: Any = None):
|
||||
super().__init__()
|
||||
self._name_pattern = namePattern
|
||||
self._scan_filter = scanFilter
|
||||
|
||||
def getNamePattern(self) -> str:
|
||||
return self._name_pattern
|
||||
|
||||
def getScanFilter(self) -> Optional[Any]:
|
||||
return self._scan_filter
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BluetoothLeDeviceFilter(pattern={self._name_pattern})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, BluetoothLeDeviceFilter):
|
||||
return False
|
||||
return self._name_pattern == other._name_pattern
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._name_pattern) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CommsDevice
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CommsDevice(Stub):
|
||||
"""CommsDevice — 通信设备信息"""
|
||||
|
||||
def __init__(self, deviceId: str = "", displayName: str = ""):
|
||||
super().__init__()
|
||||
self._device_id = deviceId
|
||||
self._display_name = displayName
|
||||
|
||||
def getDeviceId(self) -> str:
|
||||
return self._device_id
|
||||
|
||||
def getDisplayName(self) -> str:
|
||||
return self._display_name
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CommsDevice(id={self._device_id}, name={self._display_name})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, CommsDevice):
|
||||
return False
|
||||
return self._device_id == other._device_id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._device_id) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DeviceActivityCallback
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DeviceActivityCallback(Stub):
|
||||
"""DeviceActivityCallback — 设备活动回调"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onDeviceActivityChanged(self, associationInfo: "AssociationInfo", active: bool):
|
||||
"""设备活动状态变更回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"DeviceActivityCallback@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CompanionService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CompanionService(Stub):
|
||||
"""CompanionService — 配套服务"""
|
||||
|
||||
SERVICE_INTERFACE = "android.companion.CompanionDeviceService"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onConnected(self, deviceInfo: Any):
|
||||
"""已连接回调"""
|
||||
pass
|
||||
|
||||
def onDisconnected(self, deviceInfo: Any):
|
||||
"""已断开回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CompanionService@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,418 @@
|
||||
"""
|
||||
Android Credentials API — CredentialManager, CreateCredentialRequest, GetCredentialRequest 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"CredentialManager",
|
||||
"CreateCredentialRequest",
|
||||
"CreateCredentialResponse",
|
||||
"GetCredentialRequest",
|
||||
"GetCredentialResponse",
|
||||
"CredentialDescription",
|
||||
"CredentialManagerState",
|
||||
"PrepareGetCredentialResponse",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CredentialManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CredentialManager(Stub):
|
||||
"""CredentialManager — 凭据管理器 (桌面存根)"""
|
||||
|
||||
ACTION_CREATE_CREDENTIAL = "android.credentials.action.CREATE_CREDENTIAL"
|
||||
ACTION_GET_CREDENTIAL = "android.credentials.action.GET_CREDENTIAL"
|
||||
ACTION_CLEAR_CREDENTIAL = "android.credentials.action.CLEAR_CREDENTIAL"
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._descriptions: List["CredentialDescription"] = []
|
||||
|
||||
def createCredential(
|
||||
self,
|
||||
request: "CreateCredentialRequest",
|
||||
cancellationSignal: Any = None,
|
||||
executor: Any = None,
|
||||
callback: Callable = None,
|
||||
):
|
||||
"""创建凭据"""
|
||||
raise AndroidOnlyError("CredentialManager.createCredential() 仅在 Android 设备上可用")
|
||||
|
||||
def getCredential(
|
||||
self,
|
||||
request: "GetCredentialRequest",
|
||||
cancellationSignal: Any = None,
|
||||
executor: Any = None,
|
||||
callback: Callable = None,
|
||||
):
|
||||
"""获取凭据"""
|
||||
raise AndroidOnlyError("CredentialManager.getCredential() 仅在 Android 设备上可用")
|
||||
|
||||
def clearCredentialState(
|
||||
self,
|
||||
request: Any = None,
|
||||
cancellationSignal: Any = None,
|
||||
executor: Any = None,
|
||||
callback: Callable = None,
|
||||
):
|
||||
"""清除凭据状态"""
|
||||
raise AndroidOnlyError("CredentialManager.clearCredentialState() 仅在 Android 设备上可用")
|
||||
|
||||
def registerCredentialDescription(
|
||||
self,
|
||||
credentialDescription: "CredentialDescription",
|
||||
cancellationSignal: Any = None,
|
||||
executor: Any = None,
|
||||
callback: Callable = None,
|
||||
):
|
||||
"""注册凭据描述"""
|
||||
if credentialDescription not in self._descriptions:
|
||||
self._descriptions.append(credentialDescription)
|
||||
|
||||
def unregisterCredentialDescription(
|
||||
self,
|
||||
credentialDescription: "CredentialDescription",
|
||||
):
|
||||
"""注销凭据描述"""
|
||||
if credentialDescription in self._descriptions:
|
||||
self._descriptions.remove(credentialDescription)
|
||||
|
||||
def getCredentialManagerState(self) -> "CredentialManagerState":
|
||||
"""获取凭据管理器状态"""
|
||||
return CredentialManagerState()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CredentialManager@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CreateCredentialRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CreateCredentialRequest(Stub):
|
||||
"""CreateCredentialRequest — 创建凭据请求"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""CreateCredentialRequest.Builder — 创建凭据请求构建器"""
|
||||
|
||||
def __init__(self, credentialType: str = ""):
|
||||
super().__init__()
|
||||
self._credential_type = credentialType
|
||||
self._credential_data = None
|
||||
self._candidate_query_data = None
|
||||
self._always_send_app_uri = False
|
||||
self._origin = ""
|
||||
|
||||
def setCredentialData(self, data: Any) -> "CreateCredentialRequest.Builder":
|
||||
self._credential_data = data
|
||||
return self
|
||||
|
||||
def setCandidateQueryData(
|
||||
self, data: Any
|
||||
) -> "CreateCredentialRequest.Builder":
|
||||
self._candidate_query_data = data
|
||||
return self
|
||||
|
||||
def setAlwaysSendAppUri(
|
||||
self, alwaysSend: bool
|
||||
) -> "CreateCredentialRequest.Builder":
|
||||
self._always_send_app_uri = alwaysSend
|
||||
return self
|
||||
|
||||
def setOrigin(self, origin: str) -> "CreateCredentialRequest.Builder":
|
||||
self._origin = origin
|
||||
return self
|
||||
|
||||
def build(self) -> "CreateCredentialRequest":
|
||||
req = CreateCredentialRequest.__new__(CreateCredentialRequest)
|
||||
req._credential_type = self._credential_type
|
||||
req._credential_data = self._credential_data
|
||||
req._candidate_query_data = self._candidate_query_data
|
||||
req._always_send_app_uri = self._always_send_app_uri
|
||||
req._origin = self._origin
|
||||
return req
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CreateCredentialRequest.Builder(type={self._credential_type})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._credential_type = ""
|
||||
self._credential_data = None
|
||||
self._candidate_query_data = None
|
||||
self._always_send_app_uri = False
|
||||
self._origin = ""
|
||||
|
||||
def getCredentialData(self) -> Optional[Any]:
|
||||
return self._credential_data
|
||||
|
||||
def getCredentialType(self) -> str:
|
||||
return self._credential_type
|
||||
|
||||
def getCandidateQueryData(self) -> Optional[Any]:
|
||||
return self._candidate_query_data
|
||||
|
||||
def alwaysSendAppUri(self) -> bool:
|
||||
return self._always_send_app_uri
|
||||
|
||||
def getOrigin(self) -> str:
|
||||
return self._origin
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CreateCredentialRequest(type={self._credential_type})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, CreateCredentialRequest):
|
||||
return False
|
||||
return self._credential_type == other._credential_type
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._credential_type) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CreateCredentialResponse
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CreateCredentialResponse(Stub):
|
||||
"""CreateCredentialResponse — 创建凭据响应"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._credential_data = None
|
||||
self._credential_data_result = None
|
||||
|
||||
def getCredentialData(self) -> Optional[Any]:
|
||||
return self._credential_data
|
||||
|
||||
def getCredentialDataResult(self) -> Optional[Any]:
|
||||
return self._credential_data_result
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CreateCredentialResponse@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# GetCredentialRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class GetCredentialRequest(Stub):
|
||||
"""GetCredentialRequest — 获取凭据请求"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""GetCredentialRequest.Builder — 获取凭据请求构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._credential_data = None
|
||||
self._origin = ""
|
||||
self._always_send_app_uri = False
|
||||
self._candidate_query_data = None
|
||||
|
||||
def setCredentialData(
|
||||
self, data: Any
|
||||
) -> "GetCredentialRequest.Builder":
|
||||
self._credential_data = data
|
||||
return self
|
||||
|
||||
def setOrigin(self, origin: str) -> "GetCredentialRequest.Builder":
|
||||
self._origin = origin
|
||||
return self
|
||||
|
||||
def setAlwaysSendAppUri(
|
||||
self, alwaysSend: bool
|
||||
) -> "GetCredentialRequest.Builder":
|
||||
self._always_send_app_uri = alwaysSend
|
||||
return self
|
||||
|
||||
def setCandidateQueryData(
|
||||
self, data: Any
|
||||
) -> "GetCredentialRequest.Builder":
|
||||
self._candidate_query_data = data
|
||||
return self
|
||||
|
||||
def build(self) -> "GetCredentialRequest":
|
||||
req = GetCredentialRequest.__new__(GetCredentialRequest)
|
||||
req._credential_data = self._credential_data
|
||||
req._origin = self._origin
|
||||
req._always_send_app_uri = self._always_send_app_uri
|
||||
req._candidate_query_data = self._candidate_query_data
|
||||
return req
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"GetCredentialRequest.Builder@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._credential_data = None
|
||||
self._origin = ""
|
||||
self._always_send_app_uri = False
|
||||
self._candidate_query_data = None
|
||||
|
||||
def getCredentialData(self) -> Optional[Any]:
|
||||
return self._credential_data
|
||||
|
||||
def getOrigin(self) -> str:
|
||||
return self._origin
|
||||
|
||||
def alwaysSendAppUri(self) -> bool:
|
||||
return self._always_send_app_uri
|
||||
|
||||
def getCandidateQueryData(self) -> Optional[Any]:
|
||||
return self._candidate_query_data
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"GetCredentialRequest@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# GetCredentialResponse
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class GetCredentialResponse(Stub):
|
||||
"""GetCredentialResponse — 获取凭据响应"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._credential_data_results: List[Any] = []
|
||||
|
||||
def getCredentialDataResults(self) -> List[Any]:
|
||||
return list(self._credential_data_results)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"GetCredentialResponse(results={len(self._credential_data_results)})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CredentialDescription
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CredentialDescription(Stub):
|
||||
"""CredentialDescription — 凭据描述"""
|
||||
|
||||
def __init__(self, credentialType: str = "", origin: str = ""):
|
||||
super().__init__()
|
||||
self._credential_type = credentialType
|
||||
self._origin = origin
|
||||
|
||||
def getCredentialType(self) -> str:
|
||||
return self._credential_type
|
||||
|
||||
def getOrigin(self) -> str:
|
||||
return self._origin
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CredentialDescription(type={self._credential_type})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, CredentialDescription):
|
||||
return False
|
||||
return (self._credential_type == other._credential_type
|
||||
and self._origin == other._origin)
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._credential_type, self._origin)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CredentialManagerState
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CredentialManagerState(Stub):
|
||||
"""CredentialManagerState — 凭据管理器状态"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._registered_descriptions: List["CredentialDescription"] = []
|
||||
self._is_enabled = True
|
||||
|
||||
def getRegisteredCredentialDescriptions(self) -> List["CredentialDescription"]:
|
||||
return list(self._registered_descriptions)
|
||||
|
||||
def isEnabled(self) -> bool:
|
||||
return self._is_enabled
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CredentialManagerState(enabled={self._is_enabled})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PrepareGetCredentialResponse
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PrepareGetCredentialResponse(Stub):
|
||||
"""PrepareGetCredentialResponse — 预备获取凭据响应"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._has_results = False
|
||||
self._pending_credential = None
|
||||
|
||||
def hasResults(self) -> bool:
|
||||
return self._has_results
|
||||
|
||||
def getPendingGetCredentialResponse(self) -> Optional["GetCredentialResponse"]:
|
||||
return self._pending_credential
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PrepareGetCredentialResponse(hasResults={self._has_results})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,299 @@
|
||||
"""
|
||||
Android hardware.display 组件 — DisplayManager, VirtualDisplay, WifiDisplay 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DisplayManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DisplayManager(Stub):
|
||||
"""DisplayManager — 显示管理器"""
|
||||
|
||||
VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 1
|
||||
VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 4
|
||||
VIRTUAL_DISPLAY_FLAG_PUBLIC_DISPLAY = 8
|
||||
VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2
|
||||
VIRTUAL_DISPLAY_FLAG_SECURE = 16
|
||||
VIRTUAL_DISPLAY_FLAG_SUPPORTS_HDR = 32
|
||||
|
||||
MATCH_DEFAULT_DISPLAY = -1
|
||||
MATCH_ALL_DISPLAYS = 0
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# DisplayListener
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class DisplayListener(Stub):
|
||||
"""DisplayManager.DisplayListener — 显示监听器"""
|
||||
|
||||
def onDisplayAdded(self, displayId: int):
|
||||
pass
|
||||
|
||||
def onDisplayRemoved(self, displayId: int):
|
||||
pass
|
||||
|
||||
def onDisplayChanged(self, displayId: int):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._displays: Dict[int, Display] = {}
|
||||
self._listeners: Dict[int, DisplayManager.DisplayListener] = {}
|
||||
self._next_display_id = 1
|
||||
# Create default display
|
||||
self._displays[0] = Display(0, "Built-in Display")
|
||||
|
||||
def getDisplay(self, displayId: int) -> Optional["Display"]:
|
||||
return self._displays.get(displayId)
|
||||
|
||||
def getDisplays(self) -> List["Display"]:
|
||||
if DisplayManager.MATCH_ALL_DISPLAYS:
|
||||
return list(self._displays.values())
|
||||
return []
|
||||
|
||||
def registerDisplayListener(self, listener: DisplayListener, handler: Any = None):
|
||||
lid = id(listener)
|
||||
self._listeners[lid] = listener
|
||||
|
||||
def unregisterDisplayListener(self, listener: DisplayListener):
|
||||
lid = id(listener)
|
||||
self._listeners.pop(lid, None)
|
||||
|
||||
def createVirtualDisplay(self, name: str, width: int, height: int, densityDpi: int,
|
||||
surface: Any, flags: int, callback: Callable = None,
|
||||
handler: Any = None) -> "VirtualDisplay":
|
||||
display_id = self._next_display_id
|
||||
self._next_display_id += 1
|
||||
vd = VirtualDisplay(display_id, name, width, height, densityDpi, surface, flags)
|
||||
return vd
|
||||
|
||||
def getStableDisplay(self) -> Optional["Display"]:
|
||||
return self._displays.get(0)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Display
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Display(Stub):
|
||||
"""Display — 显示信息"""
|
||||
|
||||
DEFAULT_DISPLAY = 0
|
||||
|
||||
STATE_UNKNOWN = 0
|
||||
STATE_OFF = 1
|
||||
STATE_ON = 2
|
||||
STATE_DOZE = 3
|
||||
STATE_DOZE_SUSPEND = 4
|
||||
|
||||
FLAG_PRESENTATION = 2
|
||||
FLAG_PRIVATE = 4
|
||||
FLAG_ROUND = 8
|
||||
FLAG_CAN_SHOW_WITH_INSETS = 16
|
||||
FLAG_SECURE = 32
|
||||
FLAG_SUPPORTS_HDR = 64
|
||||
|
||||
def __init__(self, displayId: int = 0, name: str = "Display",
|
||||
width: int = 1920, height: int = 1080,
|
||||
densityDpi: int = 420, refreshRate: float = 60.0):
|
||||
super().__init__()
|
||||
self._display_id = displayId
|
||||
self._name = name
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._density_dpi = densityDpi
|
||||
self._refresh_rate = refreshRate
|
||||
self._state = Display.STATE_ON
|
||||
self._flags = 0
|
||||
self._app_width = width
|
||||
self._app_height = height
|
||||
|
||||
def getDisplayId(self) -> int:
|
||||
return self._display_id
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getWidth(self) -> int:
|
||||
return self._width
|
||||
|
||||
def getHeight(self) -> int:
|
||||
return self._height
|
||||
|
||||
def getRotation(self) -> int:
|
||||
return 0
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def getRefreshRate(self) -> float:
|
||||
return self._refresh_rate
|
||||
|
||||
def isHdr(self) -> bool:
|
||||
return bool(self._flags & Display.FLAG_SUPPORTS_HDR)
|
||||
|
||||
def getFlags(self) -> int:
|
||||
return self._flags
|
||||
|
||||
def getMode(self) -> Any:
|
||||
return None
|
||||
|
||||
def getSupportedModes(self) -> List[Any]:
|
||||
return []
|
||||
|
||||
def getSupportedHdrTypes(self) -> List[int]:
|
||||
return []
|
||||
|
||||
def getAppWidth(self) -> int:
|
||||
return self._app_width
|
||||
|
||||
def getAppHeight(self) -> int:
|
||||
return self._app_height
|
||||
|
||||
def getDensityDpi(self) -> int:
|
||||
return self._density_dpi
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Display(id={self._display_id}, name={self._name}, {self._width}x{self._height}," \
|
||||
f" dpi={self._density_dpi})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# VirtualDisplay
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class VirtualDisplay(Stub):
|
||||
"""VirtualDisplay — 虚拟显示"""
|
||||
|
||||
def __init__(self, displayId: int, name: str, width: int, height: int,
|
||||
densityDpi: int, surface: Any, flags: int):
|
||||
super().__init__()
|
||||
self._display_id = displayId
|
||||
self._name = name
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._density_dpi = densityDpi
|
||||
self._surface = surface
|
||||
self._flags = flags
|
||||
self._display = Display(displayId, name, width, height, densityDpi)
|
||||
self._released = False
|
||||
|
||||
def setSurface(self, surface: Any):
|
||||
self._surface = surface
|
||||
|
||||
def resize(self, width: int, height: int, densityDpi: int):
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._density_dpi = densityDpi
|
||||
|
||||
def release(self):
|
||||
self._released = True
|
||||
|
||||
def getDisplay(self) -> Display:
|
||||
return self._display
|
||||
|
||||
def getSurface(self) -> Any:
|
||||
return self._surface
|
||||
|
||||
def getFlags(self) -> int:
|
||||
return self._flags
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"VirtualDisplay(id={self._display_id}, name={self._name}," \
|
||||
f" {self._width}x{self._height}, released={self._released})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WifiDisplayStatus
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WifiDisplayStatus(Stub):
|
||||
"""WifiDisplayStatus — Wi-Fi 显示状态"""
|
||||
|
||||
FEATURE_STATE_DISABLED = 0
|
||||
FEATURE_STATE_ON = 1
|
||||
FEATURE_STATE_OFF = 2
|
||||
FEATURE_STATE_UNAVAILABLE = 3
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._active_display: Optional[WifiDisplay] = None
|
||||
self._scan_results: List[WifiDisplay] = []
|
||||
self._feature_state = WifiDisplayStatus.FEATURE_STATE_DISABLED
|
||||
|
||||
def getActiveDisplay(self) -> Optional["WifiDisplay"]:
|
||||
return self._active_display
|
||||
|
||||
def getScanResults(self) -> List["WifiDisplay"]:
|
||||
return self._scan_results
|
||||
|
||||
def getFeatureState(self) -> int:
|
||||
return self._feature_state
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"WifiDisplayStatus(featureState={self._feature_state})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WifiDisplay
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WifiDisplay(Stub):
|
||||
"""WifiDisplay — Wi-Fi 显示设备"""
|
||||
|
||||
def __init__(self, deviceAddress: str = "", deviceName: str = "",
|
||||
deviceAlias: str = "", available: bool = False,
|
||||
remembered: bool = False):
|
||||
super().__init__()
|
||||
self._device_address = deviceAddress
|
||||
self._device_name = deviceName
|
||||
self._device_alias = deviceAlias
|
||||
self._available = available
|
||||
self._remembered = remembered
|
||||
|
||||
def getDeviceAddress(self) -> str:
|
||||
return self._device_address
|
||||
|
||||
def getDeviceName(self) -> str:
|
||||
return self._device_name
|
||||
|
||||
def getDeviceAlias(self) -> str:
|
||||
return self._device_alias
|
||||
|
||||
def isAvailable(self) -> bool:
|
||||
return self._available
|
||||
|
||||
def isRemembered(self) -> bool:
|
||||
return self._remembered
|
||||
|
||||
def equals(self, other: Any) -> bool:
|
||||
if not isinstance(other, WifiDisplay):
|
||||
return False
|
||||
return self._device_address == other._device_address
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._device_address) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"WifiDisplay(address={self._device_address}, name={self._device_name}," \
|
||||
f" available={self._available})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"DisplayManager",
|
||||
"Display",
|
||||
"VirtualDisplay",
|
||||
"WifiDisplayStatus",
|
||||
"WifiDisplay",
|
||||
]
|
||||
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
Android hardware.fingerprint 组件 — FingerprintManager, Fingerprint 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FingerprintManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FingerprintManager(Stub):
|
||||
"""FingerprintManager — 指纹管理器"""
|
||||
|
||||
FINGERPRINT_ACQUIRED_GOOD = 0
|
||||
FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3
|
||||
FINGERPRINT_ACQUIRED_INSUFFICIENT = 2
|
||||
FINGERPRINT_ACQUIRED_PARTIAL = 1
|
||||
FINGERPRINT_ACQUIRED_TOO_FAST = 5
|
||||
FINGERPRINT_ACQUIRED_TOO_SLOW = 4
|
||||
|
||||
FINGERPRINT_ERROR_HW_UNAVAILABLE = 1
|
||||
FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2
|
||||
FINGERPRINT_ERROR_TIMEOUT = 3
|
||||
FINGERPRINT_ERROR_NO_SPACE = 4
|
||||
FINGERPRINT_ERROR_CANCELED = 5
|
||||
FINGERPRINT_ERROR_LOCKOUT = 7
|
||||
FINGERPRINT_ERROR_VENDOR = 8
|
||||
FINGERPRINT_ERROR_USER_CANCELED = 10
|
||||
FINGERPRINT_ERROR_BAD_CALIBRATION = 11
|
||||
FINGERPRINT_ERROR_POWER_PRESS = 12
|
||||
FINGERPRINT_ERROR_IDLE_NOT_ENROLLED = 13
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# AuthenticationCallback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class AuthenticationCallback(Stub):
|
||||
"""FingerprintManager.AuthenticationCallback — 认证回调"""
|
||||
|
||||
def onAuthenticationError(self, errorCode: int, errString: str):
|
||||
pass
|
||||
|
||||
def onAuthenticationHelp(self, helpCode: int, helpString: str):
|
||||
pass
|
||||
|
||||
def onAuthenticationSucceeded(self, result: "FingerprintManager.AuthenticationResult"):
|
||||
pass
|
||||
|
||||
def onAuthenticationFailed(self):
|
||||
pass
|
||||
|
||||
def onAuthenticationAcquired(self, acquireInfo: int):
|
||||
pass
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# AuthenticationResult
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class AuthenticationResult(Stub):
|
||||
"""FingerprintManager.AuthenticationResult — 认证结果"""
|
||||
|
||||
def __init__(self, fingerprint: "Fingerprint" = None,
|
||||
cryptoObject: "FingerprintManager.CryptoObject" = None):
|
||||
super().__init__()
|
||||
self._fingerprint = fingerprint
|
||||
self._crypto_object = cryptoObject
|
||||
|
||||
def getFingerprint(self) -> Optional["Fingerprint"]:
|
||||
return self._fingerprint
|
||||
|
||||
def getCryptoObject(self) -> Optional["FingerprintManager.CryptoObject"]:
|
||||
return self._crypto_object
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# CryptoObject
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class CryptoObject(Stub):
|
||||
"""FingerprintManager.CryptoObject — 加密对象"""
|
||||
|
||||
def __init__(self, cipher: Any = None, signature: Any = None, mac: Any = None):
|
||||
super().__init__()
|
||||
self._cipher = cipher
|
||||
self._signature = signature
|
||||
self._mac = mac
|
||||
|
||||
def getCipher(self) -> Any:
|
||||
return self._cipher
|
||||
|
||||
def getSignature(self) -> Any:
|
||||
return self._signature
|
||||
|
||||
def getMac(self) -> Any:
|
||||
return self._mac
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._enrolled_fingerprints: List[Fingerprint] = []
|
||||
self._callbacks: Dict[int, FingerprintManager.AuthenticationCallback] = {}
|
||||
|
||||
def isHardwareDetected(self) -> bool:
|
||||
return False
|
||||
|
||||
def hasEnrolledFingerprints(self) -> bool:
|
||||
return len(self._enrolled_fingerprints) > 0
|
||||
|
||||
def authenticate(self, crypto: Optional[CryptoObject] = None,
|
||||
cancel: Any = None, flags: int = 0,
|
||||
callback: Optional[AuthenticationCallback] = None,
|
||||
handler: Any = None):
|
||||
if callback:
|
||||
callback.onAuthenticationError(
|
||||
FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
|
||||
"Fingerprint hardware not available on desktop"
|
||||
)
|
||||
|
||||
def enroll(self, token: Any, userId: int = 0,
|
||||
callback: Optional[AuthenticationCallback] = None):
|
||||
raise AndroidOnlyError("FingerprintManager.enroll() is not available on desktop")
|
||||
|
||||
def remove(self, fingerprint: "Fingerprint", userId: int = 0):
|
||||
if fingerprint in self._enrolled_fingerprints:
|
||||
self._enrolled_fingerprints.remove(fingerprint)
|
||||
|
||||
def rename(self, fingerprint: "Fingerprint", newName: str):
|
||||
if fingerprint:
|
||||
fingerprint._name = newName
|
||||
|
||||
def getEnrolledFingerprints(self) -> List["Fingerprint"]:
|
||||
return list(self._enrolled_fingerprints)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Fingerprint
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Fingerprint(Stub):
|
||||
"""Fingerprint — 指纹数据"""
|
||||
|
||||
def __init__(self, name: str = "", fingerId: int = 0, groupId: int = 0,
|
||||
deviceId: int = 0):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._finger_id = fingerId
|
||||
self._group_id = groupId
|
||||
self._device_id = deviceId
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getFingerId(self) -> int:
|
||||
return self._finger_id
|
||||
|
||||
def getGroupId(self) -> int:
|
||||
return self._group_id
|
||||
|
||||
def getDeviceId(self) -> int:
|
||||
return self._device_id
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Fingerprint(name={self._name}, fingerId={self._finger_id})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"FingerprintManager",
|
||||
"Fingerprint",
|
||||
]
|
||||
@@ -0,0 +1,254 @@
|
||||
"""
|
||||
Android hardware.input 组件 — InputManager, InputDevice, MotionRange 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputManager(Stub):
|
||||
"""InputManager — 输入管理器"""
|
||||
|
||||
ACTION_QUERY_KEYBOARD_LAYOUTS = "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# InputDeviceListener
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class InputDeviceListener(Stub):
|
||||
"""InputManager.InputDeviceListener — 输入设备监听器"""
|
||||
|
||||
def onInputDeviceAdded(self, deviceId: int):
|
||||
pass
|
||||
|
||||
def onInputDeviceRemoved(self, deviceId: int):
|
||||
pass
|
||||
|
||||
def onInputDeviceChanged(self, deviceId: int):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._devices: Dict[int, InputDevice] = {}
|
||||
self._listeners: Dict[int, InputManager.InputDeviceListener] = {}
|
||||
self._next_id = 1
|
||||
self._init_default_devices()
|
||||
|
||||
def _init_default_devices(self):
|
||||
# Keyboard
|
||||
kb = InputDevice(
|
||||
deviceId=0, name="Virtual Keyboard",
|
||||
descriptor="keyboard_v1", sources=InputDevice.SOURCE_KEYBOARD,
|
||||
vendorId=0x1, productId=0x1,
|
||||
keyboardType=InputDevice.KEYBOARD_TYPE_ALPHABETIC,
|
||||
hasMicrophone=False, isVirtual=True
|
||||
)
|
||||
self._devices[0] = kb
|
||||
|
||||
# Mouse
|
||||
mouse = InputDevice(
|
||||
deviceId=1, name="Virtual Mouse",
|
||||
descriptor="mouse_v1", sources=InputDevice.SOURCE_MOUSE,
|
||||
vendorId=0x2, productId=0x2,
|
||||
keyboardType=InputDevice.KEYBOARD_TYPE_NONE,
|
||||
hasMicrophone=False, isVirtual=True
|
||||
)
|
||||
self._devices[1] = mouse
|
||||
|
||||
# Trackpad
|
||||
tp = InputDevice(
|
||||
deviceId=2, name="Virtual Trackpad",
|
||||
descriptor="trackpad_v1", sources=InputDevice.SOURCE_TOUCHPAD,
|
||||
vendorId=0x3, productId=0x3,
|
||||
keyboardType=InputDevice.KEYBOARD_TYPE_NONE,
|
||||
hasMicrophone=False, isVirtual=False
|
||||
)
|
||||
self._devices[2] = tp
|
||||
|
||||
def getDevice(self, deviceId: int) -> Optional["InputDevice"]:
|
||||
return self._devices.get(deviceId)
|
||||
|
||||
def getDeviceIds(self) -> List[int]:
|
||||
return list(self._devices.keys())
|
||||
|
||||
def registerInputDeviceListener(self, listener: InputDeviceListener, handler: Any = None):
|
||||
lid = id(listener)
|
||||
self._listeners[lid] = listener
|
||||
|
||||
def unregisterInputDeviceListener(self, listener: InputDeviceListener):
|
||||
lid = id(listener)
|
||||
self._listeners.pop(lid, None)
|
||||
|
||||
def getInputDevice(self, deviceId: int) -> Optional["InputDevice"]:
|
||||
return self.getDevice(deviceId)
|
||||
|
||||
def isInTabletMode(self) -> bool:
|
||||
return False
|
||||
|
||||
def getCurrentKeyboardLayoutForInputDevice(self, device: "InputDevice") -> Optional[str]:
|
||||
return None
|
||||
|
||||
def setKeyboardLayoutForInputDevice(self, device: "InputDevice",
|
||||
keyboardLayoutDescriptor: str):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InputDevice
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InputDevice(Stub):
|
||||
"""InputDevice — 输入设备"""
|
||||
|
||||
KEYBOARD_TYPE_ALPHABETIC = 2
|
||||
KEYBOARD_TYPE_NON_ALPHABETIC = 1
|
||||
KEYBOARD_TYPE_NONE = 0
|
||||
|
||||
SOURCE_CLASS_JOYSTICK = 16
|
||||
SOURCE_CLASS_MASK = 0x000000FF
|
||||
SOURCE_CLASS_NONE = 0x00000000
|
||||
SOURCE_CLASS_POINTER = 0x00000002
|
||||
SOURCE_CLASS_POSITION = 0x00000008
|
||||
SOURCE_CLASS_TRACKBALL = 0x00000004
|
||||
SOURCE_CLASS_BUTTON = 0x00000001
|
||||
|
||||
SOURCE_GAMEPAD = 0x00000401
|
||||
SOURCE_HDMI = 0x02000000
|
||||
SOURCE_JOYSTICK = 0x01000010
|
||||
SOURCE_KEYBOARD = 0x00000101
|
||||
SOURCE_MOUSE = 0x00002002
|
||||
SOURCE_MOUSE_RELATIVE = 0x00002000
|
||||
SOURCE_ROTARY_ENCODER = 0x00800000
|
||||
SOURCE_STYLUS = 0x00004002
|
||||
SOURCE_TOUCHPAD = 0x00100008
|
||||
SOURCE_TOUCHSCREEN = 0x00001002
|
||||
SOURCE_TOUCH_NAVIGATION = 0x00200008
|
||||
SOURCE_TRACKBALL = 0x00010004
|
||||
SOURCE_UNKNOWN = 0x00000000
|
||||
SOURCE_ANY = 0xFFFFFFFE
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# MotionRange
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class MotionRange(Stub):
|
||||
"""InputDevice.MotionRange — 运动范围"""
|
||||
|
||||
def __init__(self, axis: int = 0, source: int = 0,
|
||||
min: float = 0.0, max: float = 1.0,
|
||||
flat: float = 0.0, fuzz: float = 0.0,
|
||||
resolution: float = 0.0):
|
||||
super().__init__()
|
||||
self._axis = axis
|
||||
self._source = source
|
||||
self._min = min
|
||||
self._max = max
|
||||
self._flat = flat
|
||||
self._fuzz = fuzz
|
||||
self._resolution = resolution
|
||||
|
||||
def getAxis(self) -> int:
|
||||
return self._axis
|
||||
|
||||
def getSource(self) -> int:
|
||||
return self._source
|
||||
|
||||
def getMin(self) -> float:
|
||||
return self._min
|
||||
|
||||
def getMax(self) -> float:
|
||||
return self._max
|
||||
|
||||
def getFlat(self) -> float:
|
||||
return self._flat
|
||||
|
||||
def getFuzz(self) -> float:
|
||||
return self._fuzz
|
||||
|
||||
def getRange(self) -> float:
|
||||
return self._max - self._min
|
||||
|
||||
def getResolution(self) -> float:
|
||||
return self._resolution
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MotionRange(axis={self._axis}, source={self._source}," \
|
||||
f" range=[{self._min}, {self._max}])"
|
||||
|
||||
def __init__(self, deviceId: int = 0, name: str = "", descriptor: str = "",
|
||||
sources: int = 0, vendorId: int = 0, productId: int = 0,
|
||||
controllerNumber: int = 0, keyboardType: int = KEYBOARD_TYPE_NONE,
|
||||
hasMicrophone: bool = False, isVirtual: bool = False):
|
||||
super().__init__()
|
||||
self._device_id = deviceId
|
||||
self._name = name
|
||||
self._descriptor = descriptor
|
||||
self._sources = sources
|
||||
self._vendor_id = vendorId
|
||||
self._product_id = productId
|
||||
self._controller_number = controllerNumber
|
||||
self._keyboard_type = keyboardType
|
||||
self._has_microphone = hasMicrophone
|
||||
self._is_virtual = isVirtual
|
||||
self._motion_ranges: List[InputDevice.MotionRange] = []
|
||||
|
||||
def getId(self) -> int:
|
||||
return self._device_id
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getDescriptor(self) -> str:
|
||||
return self._descriptor
|
||||
|
||||
def getSources(self) -> int:
|
||||
return self._sources
|
||||
|
||||
def getVendorId(self) -> int:
|
||||
return self._vendor_id
|
||||
|
||||
def getProductId(self) -> int:
|
||||
return self._product_id
|
||||
|
||||
def getControllerNumber(self) -> int:
|
||||
return self._controller_number
|
||||
|
||||
def getKeyCharacterMap(self) -> Any:
|
||||
return None
|
||||
|
||||
def getMotionRange(self, axis: int = -1, source: int = -1) -> List[MotionRange]:
|
||||
results = []
|
||||
for mr in self._motion_ranges:
|
||||
if axis >= 0 and mr.getAxis() != axis:
|
||||
continue
|
||||
if source >= 0 and mr.getSource() != source:
|
||||
continue
|
||||
results.append(mr)
|
||||
return results
|
||||
|
||||
def hasMicrophone(self) -> bool:
|
||||
return self._has_microphone
|
||||
|
||||
def isVirtual(self) -> bool:
|
||||
return self._is_virtual
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"InputDevice(id={self._device_id}, name={self._name}," \
|
||||
f" sources={self._sources:#x})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"InputManager",
|
||||
"InputDevice",
|
||||
]
|
||||
@@ -0,0 +1,409 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""
|
||||
Android hardware.usb 组件 — UsbManager, UsbDevice, UsbDeviceConnection 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsbConstants
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsbConstants(Stub):
|
||||
"""UsbConstants — USB 常量"""
|
||||
|
||||
USB_DIR_IN = 0x80
|
||||
USB_DIR_OUT = 0
|
||||
|
||||
USB_TYPE_MASK = 0x60
|
||||
USB_TYPE_CLASS = 0x20
|
||||
USB_TYPE_VENDOR = 0x40
|
||||
USB_TYPE_STANDARD = 0x00
|
||||
|
||||
USB_ENDPOINT_XFER_CONTROL = 0
|
||||
USB_ENDPOINT_XFER_ISOC = 1
|
||||
USB_ENDPOINT_XFER_BULK = 2
|
||||
USB_ENDPOINT_XFER_INT = 3
|
||||
|
||||
USB_CLASS_APP_SPEC = 0xFE
|
||||
USB_CLASS_AUDIO = 0x01
|
||||
USB_CLASS_CDC_DATA = 0x0A
|
||||
USB_CLASS_COMM = 0x02
|
||||
USB_CLASS_CONTENT_SEC = 0x0D
|
||||
USB_CLASS_CSCID = 0x0B
|
||||
USB_CLASS_HID = 0x03
|
||||
USB_CLASS_HUB = 0x09
|
||||
USB_CLASS_MASS_STORAGE = 0x08
|
||||
USB_CLASS_MISC = 0xEF
|
||||
USB_CLASS_PER_INTERFACE = 0x00
|
||||
USB_CLASS_PHYSICA = 0x05
|
||||
USB_CLASS_PRINTER = 0x07
|
||||
USB_CLASS_STILL_IMAGE = 0x06
|
||||
USB_CLASS_VENDOR_SPEC = 0xFF
|
||||
USB_CLASS_VIDEO = 0x0E
|
||||
USB_CLASS_WIRELESS = 0xE0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsbManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsbManager(Stub):
|
||||
"""UsbManager — USB 管理器"""
|
||||
|
||||
ACTION_USB_DEVICE_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
ACTION_USB_DEVICE_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED"
|
||||
ACTION_USB_ACCESSORY_ATTACHED = "android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
|
||||
ACTION_USB_ACCESSORY_DETACHED = "android.hardware.usb.action.USB_ACCESSORY_DETACHED"
|
||||
|
||||
EXTRA_DEVICE = "device"
|
||||
EXTRA_ACCESSORY = "accessory"
|
||||
EXTRA_PERMISSION_GRANTED = "permission"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._devices: Dict[str, UsbDevice] = {}
|
||||
self._permissions: Set[str] = set()
|
||||
self._accessories: List[UsbAccessory] = []
|
||||
self._connections: Dict[str, UsbDeviceConnection] = {}
|
||||
# Create some mock devices
|
||||
self._devices["001"] = UsbDevice(
|
||||
deviceId=1, deviceName="/dev/bus/usb/001/001",
|
||||
vendorId=0x18D1, productId=0x4EE1,
|
||||
deviceClass=UsbConstants.USB_CLASS_MISC,
|
||||
deviceSubclass=0, deviceProtocol=0,
|
||||
manufacturerName="Google", productName="Pixel Device",
|
||||
version="3.1", serialNumber="ABC123"
|
||||
)
|
||||
|
||||
def getDeviceList(self) -> Dict[str, UsbDevice]:
|
||||
return dict(self._devices)
|
||||
|
||||
def openDevice(self, device: "UsbDevice") -> Optional["UsbDeviceConnection"]:
|
||||
if not device:
|
||||
return None
|
||||
conn = UsbDeviceConnection(device)
|
||||
self._connections[str(device.getDeviceId())] = conn
|
||||
return conn
|
||||
|
||||
def hasPermission(self, device: "UsbDevice") -> bool:
|
||||
return device.getDeviceName() in self._permissions
|
||||
|
||||
def requestPermission(self, device: "UsbDevice"):
|
||||
self._permissions.add(device.getDeviceName())
|
||||
|
||||
def grantPermission(self, device: "UsbDevice"):
|
||||
self._permissions.add(device.getDeviceName())
|
||||
|
||||
def revokePermission(self, device: "UsbDevice"):
|
||||
self._permissions.discard(device.getDeviceName())
|
||||
|
||||
def setCurrentFunction(self, function: str, usbDataUnlocked: bool = False):
|
||||
pass
|
||||
|
||||
def setUsbDataUnlocked(self, unlocked: bool):
|
||||
pass
|
||||
|
||||
def getCurrentAccessory(self) -> Optional["UsbAccessory"]:
|
||||
return self._accessories[0] if self._accessories else None
|
||||
|
||||
def openAccessory(self, accessory: "UsbAccessory") -> Optional["UsbDeviceConnection"]:
|
||||
if not accessory:
|
||||
return None
|
||||
conn = UsbDeviceConnection(accessory)
|
||||
return conn
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsbDevice
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsbDevice(Stub):
|
||||
"""UsbDevice — USB 设备"""
|
||||
|
||||
def __init__(self, deviceId: int = 0, deviceName: str = "",
|
||||
vendorId: int = 0, productId: int = 0,
|
||||
deviceClass: int = 0, deviceSubclass: int = 0,
|
||||
deviceProtocol: int = 0, configurationCount: int = 1,
|
||||
interfaceCount: int = 0,
|
||||
manufacturerName: str = "", productName: str = "",
|
||||
version: str = "", serialNumber: str = ""):
|
||||
super().__init__()
|
||||
self._device_id = deviceId
|
||||
self._device_name = deviceName
|
||||
self._vendor_id = vendorId
|
||||
self._product_id = productId
|
||||
self._device_class = deviceClass
|
||||
self._device_subclass = deviceSubclass
|
||||
self._device_protocol = deviceProtocol
|
||||
self._configuration_count = configurationCount
|
||||
self._interface_count = interfaceCount
|
||||
self._manufacturer_name = manufacturerName
|
||||
self._product_name = productName
|
||||
self._version = version
|
||||
self._serial_number = serialNumber
|
||||
self._interfaces: List[UsbInterface] = []
|
||||
|
||||
def getDeviceId(self) -> int:
|
||||
return self._device_id
|
||||
|
||||
def getDeviceName(self) -> str:
|
||||
return self._device_name
|
||||
|
||||
def getVendorId(self) -> int:
|
||||
return self._vendor_id
|
||||
|
||||
def getProductId(self) -> int:
|
||||
return self._product_id
|
||||
|
||||
def getDeviceClass(self) -> int:
|
||||
return self._device_class
|
||||
|
||||
def getDeviceSubclass(self) -> int:
|
||||
return self._device_subclass
|
||||
|
||||
def getDeviceProtocol(self) -> int:
|
||||
return self._device_protocol
|
||||
|
||||
def getConfigurationCount(self) -> int:
|
||||
return self._configuration_count
|
||||
|
||||
def getInterfaceCount(self) -> int:
|
||||
return len(self._interfaces)
|
||||
|
||||
def getInterface(self, index: int) -> Optional["UsbInterface"]:
|
||||
if 0 <= index < len(self._interfaces):
|
||||
return self._interfaces[index]
|
||||
return None
|
||||
|
||||
def getInterfaces(self) -> List["UsbInterface"]:
|
||||
return list(self._interfaces)
|
||||
|
||||
def getManufacturerName(self) -> str:
|
||||
return self._manufacturer_name
|
||||
|
||||
def getProductName(self) -> str:
|
||||
return self._product_name
|
||||
|
||||
def getVersion(self) -> str:
|
||||
return self._version
|
||||
|
||||
def getSerialNumber(self) -> str:
|
||||
return self._serial_number
|
||||
|
||||
def describeContents(self) -> int:
|
||||
return 0
|
||||
|
||||
def equals(self, other: Any) -> bool:
|
||||
if not isinstance(other, UsbDevice):
|
||||
return False
|
||||
return self._device_id == other._device_id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._device_id) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UsbDevice(name={self._device_name}, vid={self._vendor_id:x}," \
|
||||
f" pid={self._product_id:x})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsbDeviceConnection
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsbDeviceConnection(Stub):
|
||||
"""UsbDeviceConnection — USB 设备连接"""
|
||||
|
||||
def __init__(self, device: Any):
|
||||
super().__init__()
|
||||
self._device = device
|
||||
self._claimed_interfaces: Set[int] = set()
|
||||
self._fd = -1
|
||||
|
||||
def claimInterface(self, intf: "UsbInterface", force: bool = False) -> bool:
|
||||
self._claimed_interfaces.add(intf.getId())
|
||||
return True
|
||||
|
||||
def releaseInterface(self, intf: "UsbInterface") -> bool:
|
||||
self._claimed_interfaces.discard(intf.getId())
|
||||
return True
|
||||
|
||||
def bulkTransfer(self, endpoint: "UsbEndpoint", buffer: bytes,
|
||||
length: int, timeout: int) -> int:
|
||||
return -1
|
||||
|
||||
def controlTransfer(self, requestType: int, request: int,
|
||||
value: int, index: int, buffer: bytes,
|
||||
length: int, timeout: int) -> int:
|
||||
return -1
|
||||
|
||||
def getRawDescriptors(self) -> bytes:
|
||||
return b""
|
||||
|
||||
def getFileDescriptor(self) -> int:
|
||||
return self._fd
|
||||
|
||||
def serial(self) -> Optional[str]:
|
||||
if hasattr(self._device, 'getSerialNumber'):
|
||||
return self._device.getSerialNumber()
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
self._claimed_interfaces.clear()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UsbDeviceConnection(device={self._device})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsbInterface
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsbInterface(Stub):
|
||||
"""UsbInterface — USB 接口"""
|
||||
|
||||
def __init__(self, id: int = 0, alternateSetting: int = 0,
|
||||
name: str = "", interfaceClass: int = 0,
|
||||
interfaceSubclass: int = 0, interfaceProtocol: int = 0):
|
||||
super().__init__()
|
||||
self._id = id
|
||||
self._alternate_setting = alternateSetting
|
||||
self._name = name
|
||||
self._interface_class = interfaceClass
|
||||
self._interface_subclass = interfaceSubclass
|
||||
self._interface_protocol = interfaceProtocol
|
||||
self._endpoints: List[UsbEndpoint] = []
|
||||
|
||||
def getId(self) -> int:
|
||||
return self._id
|
||||
|
||||
def getAlternateSetting(self) -> int:
|
||||
return self._alternate_setting
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getInterfaceClass(self) -> int:
|
||||
return self._interface_class
|
||||
|
||||
def getInterfaceSubclass(self) -> int:
|
||||
return self._interface_subclass
|
||||
|
||||
def getInterfaceProtocol(self) -> int:
|
||||
return self._interface_protocol
|
||||
|
||||
def getEndpointCount(self) -> int:
|
||||
return len(self._endpoints)
|
||||
|
||||
def getEndpoint(self, index: int) -> Optional["UsbEndpoint"]:
|
||||
if 0 <= index < len(self._endpoints):
|
||||
return self._endpoints[index]
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UsbInterface(id={self._id}, name={self._name})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsbEndpoint
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsbEndpoint(Stub):
|
||||
"""UsbEndpoint — USB 端点"""
|
||||
|
||||
def __init__(self, address: int = 0, endpointNumber: int = 0,
|
||||
attributes: int = 0, direction: int = 0,
|
||||
maxPacketSize: int = 64, interval: int = 0,
|
||||
type_: int = 0):
|
||||
super().__init__()
|
||||
self._address = address
|
||||
self._endpoint_number = endpointNumber
|
||||
self._attributes = attributes
|
||||
self._direction = direction
|
||||
self._max_packet_size = maxPacketSize
|
||||
self._interval = interval
|
||||
self._type = type_
|
||||
|
||||
def getAddress(self) -> int:
|
||||
return self._address
|
||||
|
||||
def getEndpointNumber(self) -> int:
|
||||
return self._endpoint_number
|
||||
|
||||
def getAttributes(self) -> int:
|
||||
return self._attributes
|
||||
|
||||
def getDirection(self) -> int:
|
||||
return self._direction
|
||||
|
||||
def getMaxPacketSize(self) -> int:
|
||||
return self._max_packet_size
|
||||
|
||||
def getInterval(self) -> int:
|
||||
return self._interval
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._type
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UsbEndpoint(addr={self._address:#04x}, type={self._type})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UsbAccessory
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UsbAccessory(Stub):
|
||||
"""UsbAccessory — USB 附件"""
|
||||
|
||||
def __init__(self, manufacturer: str = "", model: str = "",
|
||||
description: str = "", version: str = "",
|
||||
uri: str = "", serial: str = ""):
|
||||
super().__init__()
|
||||
self._manufacturer = manufacturer
|
||||
self._model = model
|
||||
self._description = description
|
||||
self._version = version
|
||||
self._uri = uri
|
||||
self._serial = serial
|
||||
|
||||
def getManufacturer(self) -> str:
|
||||
return self._manufacturer
|
||||
|
||||
def getModel(self) -> str:
|
||||
return self._model
|
||||
|
||||
def getDescription(self) -> str:
|
||||
return self._description
|
||||
|
||||
def getVersion(self) -> str:
|
||||
return self._version
|
||||
|
||||
def getUri(self) -> str:
|
||||
return self._uri
|
||||
|
||||
def getSerial(self) -> str:
|
||||
return self._serial
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UsbAccessory(manufacturer={self._manufacturer}, model={self._model})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"UsbManager",
|
||||
"UsbDevice",
|
||||
"UsbDeviceConnection",
|
||||
"UsbInterface",
|
||||
"UsbEndpoint",
|
||||
"UsbConstants",
|
||||
"UsbAccessory",
|
||||
]
|
||||
@@ -0,0 +1,547 @@
|
||||
"""
|
||||
Android media.audiofx 组件 — AudioEffect, Equalizer, BassBoost, Virtualizer 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AudioEffect
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AudioEffect(Stub):
|
||||
"""AudioEffect — 音频效果基类"""
|
||||
|
||||
EFFECT_SUCCESS = 0
|
||||
EFFECT_ERROR = -1
|
||||
EFFECT_ALREADY_EXISTS = -2
|
||||
EFFECT_NO_MORE_SESSION = -3
|
||||
EFFECT_REQUEST_INIT = -4
|
||||
EFFECT_INIT_FAILED = -5
|
||||
|
||||
EFFECT_INSERT = 1
|
||||
EFFECT_AUXILIARY = 2
|
||||
EFFECT_PRE_PROCESSING = 4
|
||||
EFFECT_POST_PROCESSING = 8
|
||||
|
||||
CONTENT_TYPE_MUSIC = 2
|
||||
CONTENT_TYPE_MOVIE = 3
|
||||
CONTENT_TYPE_GAME = 4
|
||||
CONTENT_TYPE_VOICE = 1
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Descriptor
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Descriptor(Stub):
|
||||
"""AudioEffect.Descriptor — 效果描述符"""
|
||||
|
||||
def __init__(self, type: str = "", uuid: str = "",
|
||||
connectMode: str = "", name: str = "",
|
||||
implementor: str = ""):
|
||||
super().__init__()
|
||||
self.type = type
|
||||
self.uuid = uuid
|
||||
self.connectMode = connectMode
|
||||
self.name = name
|
||||
self.implementor = implementor
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Descriptor(type={self.type}, uuid={self.uuid}, name={self.name})"
|
||||
|
||||
def __init__(self, effectType: str = "", priority: int = 0,
|
||||
audioSession: int = 0):
|
||||
super().__init__()
|
||||
self._enabled = False
|
||||
self._effect_type = effectType
|
||||
self._priority = priority
|
||||
self._audio_session = audioSession
|
||||
self._id = hash(self) & 0x7FFFFFFF
|
||||
self._control_status_listener: Optional[Callable] = None
|
||||
self._enable_status_listener: Optional[Callable] = None
|
||||
|
||||
def getEnabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
def setEnabled(self, enabled: bool) -> int:
|
||||
self._enabled = enabled
|
||||
return AudioEffect.EFFECT_SUCCESS
|
||||
|
||||
def getId(self) -> int:
|
||||
return self._id
|
||||
|
||||
def getDescriptor(self) -> Descriptor:
|
||||
return AudioEffect.Descriptor(
|
||||
type=self._effect_type,
|
||||
uuid="",
|
||||
connectMode="",
|
||||
name=self.__class__.__name__,
|
||||
implementor="Android-pyApi"
|
||||
)
|
||||
|
||||
def release(self):
|
||||
self._enabled = False
|
||||
|
||||
def setControlStatusListener(self, listener: Callable):
|
||||
self._control_status_listener = listener
|
||||
|
||||
def setEnableStatusListener(self, listener: Callable):
|
||||
self._enable_status_listener = listener
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Equalizer
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Equalizer(AudioEffect):
|
||||
"""Equalizer — 均衡器"""
|
||||
|
||||
PARAM_CURRENT_PRESET = 0
|
||||
PARAM_GET_BAND = 1
|
||||
PARAM_STRING_SIZE_MAX = 2
|
||||
PARAM_BAND_LEVEL = 3
|
||||
PARAM_LEVEL_RANGE = 4
|
||||
PARAM_NUM_BANDS = 5
|
||||
PARAM_BAND_FREQ_RANGE = 6
|
||||
PARAM_CENTER_FREQ = 7
|
||||
PARAM_GET_PRESET_NAME = 8
|
||||
PARAM_NUM_PRESETS = 9
|
||||
PARAM_PROPERTIES = 10
|
||||
|
||||
def __init__(self, priority: int = 0, audioSession: int = 0):
|
||||
super().__init__("Equalizer", priority, audioSession)
|
||||
self._num_bands = 5
|
||||
self._band_levels: Dict[int, int] = {}
|
||||
self._presets = [
|
||||
"Normal", "Classical", "Dance", "Flat", "Folk",
|
||||
"Heavy Metal", "Hip Hop", "Jazz", "Pop", "Rock"
|
||||
]
|
||||
self._current_preset = 0
|
||||
self._band_freq_ranges: Dict[int, Tuple[int, int]] = {
|
||||
0: (60, 200), 1: (200, 600), 2: (600, 2400),
|
||||
3: (2400, 8000), 4: (8000, 16000)
|
||||
}
|
||||
|
||||
def getNumberOfBands(self) -> int:
|
||||
return self._num_bands
|
||||
|
||||
def getBand(self, frequency: int) -> int:
|
||||
for band, (low, high) in self._band_freq_ranges.items():
|
||||
if low <= frequency <= high:
|
||||
return band
|
||||
return 0
|
||||
|
||||
def getBandLevel(self, band: int) -> int:
|
||||
return self._band_levels.get(band, 0)
|
||||
|
||||
def setBandLevel(self, band: int, level: int):
|
||||
self._band_levels[band] = level
|
||||
|
||||
def getBandLevelRange(self) -> List[int]:
|
||||
return [-1500, 1500]
|
||||
|
||||
def getCenterFreq(self, band: int) -> int:
|
||||
if band in self._band_freq_ranges:
|
||||
low, high = self._band_freq_ranges[band]
|
||||
return (low + high) // 2
|
||||
return 0
|
||||
|
||||
def getNumberOfPresets(self) -> int:
|
||||
return len(self._presets)
|
||||
|
||||
def getPresetName(self, preset: int) -> str:
|
||||
if 0 <= preset < len(self._presets):
|
||||
return self._presets[preset]
|
||||
return "Unknown"
|
||||
|
||||
def usePreset(self, preset: int):
|
||||
if 0 <= preset < len(self._presets):
|
||||
self._current_preset = preset
|
||||
|
||||
def getCurrentPreset(self) -> int:
|
||||
return self._current_preset
|
||||
|
||||
def getProperties(self) -> Any:
|
||||
return None
|
||||
|
||||
def setProperties(self, properties: Any):
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Equalizer(preset={self._presets[self._current_preset]}," \
|
||||
f" bands={self._num_bands})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BassBoost
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BassBoost(AudioEffect):
|
||||
"""BassBoost — 低音增强"""
|
||||
|
||||
PARAM_STRENGTH = 1
|
||||
PARAM_STRENGTH_SUPPORTED = 2
|
||||
|
||||
def __init__(self, priority: int = 0, audioSession: int = 0):
|
||||
super().__init__("BassBoost", priority, audioSession)
|
||||
self._strength: int = 0
|
||||
|
||||
def setStrength(self, strength: int):
|
||||
self._strength = max(0, min(1000, strength))
|
||||
|
||||
def getRoundedStrength(self) -> int:
|
||||
return self._strength
|
||||
|
||||
def getStrengthSupported(self) -> bool:
|
||||
return True
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BassBoost(strength={self._strength})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Virtualizer
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Virtualizer(AudioEffect):
|
||||
"""Virtualizer — 虚拟器 (空间音频)"""
|
||||
|
||||
PARAM_STRENGTH = 1
|
||||
PARAM_STRENGTH_SUPPORTED = 2
|
||||
PARAM_VIRTUALIZATION_MODE = 3
|
||||
PARAM_FORCE_VIRTUALIZATION_MODE = 4
|
||||
|
||||
VIRTUALIZATION_MODE_AUTO = 0
|
||||
VIRTUALIZATION_MODE_OFF = 1
|
||||
VIRTUALIZATION_MODE_TRANSAURAL = 2
|
||||
VIRTUALIZATION_MODE_BINAURAL = 3
|
||||
|
||||
def __init__(self, priority: int = 0, audioSession: int = 0):
|
||||
super().__init__("Virtualizer", priority, audioSession)
|
||||
self._strength: int = 0
|
||||
self._virtualization_mode = Virtualizer.VIRTUALIZATION_MODE_AUTO
|
||||
|
||||
def setStrength(self, strength: int):
|
||||
self._strength = max(0, min(1000, strength))
|
||||
|
||||
def getRoundedStrength(self) -> int:
|
||||
return self._strength
|
||||
|
||||
def canVirtualize(self, channelMask: int, virtualizationMode: int) -> bool:
|
||||
return True
|
||||
|
||||
def getVirtualizationMode(self) -> int:
|
||||
return self._virtualization_mode
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Virtualizer(strength={self._strength}, mode={self._virtualization_mode})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PresetReverb
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PresetReverb(AudioEffect):
|
||||
"""PresetReverb — 预设混响"""
|
||||
|
||||
PRESET_NONE = 0
|
||||
PRESET_SMALLROOM = 1
|
||||
PRESET_MEDIUMROOM = 2
|
||||
PRESET_LARGEROOM = 3
|
||||
PRESET_MEDIUMHALL = 4
|
||||
PRESET_LARGEHALL = 5
|
||||
PRESET_PLATE = 6
|
||||
|
||||
PARAM_PRESET = 0
|
||||
|
||||
def __init__(self, priority: int = 0, audioSession: int = 0):
|
||||
super().__init__("PresetReverb", priority, audioSession)
|
||||
self._preset = PresetReverb.PRESET_NONE
|
||||
self._preset_names = {
|
||||
PresetReverb.PRESET_NONE: "None",
|
||||
PresetReverb.PRESET_SMALLROOM: "Small Room",
|
||||
PresetReverb.PRESET_MEDIUMROOM: "Medium Room",
|
||||
PresetReverb.PRESET_LARGEROOM: "Large Room",
|
||||
PresetReverb.PRESET_MEDIUMHALL: "Medium Hall",
|
||||
PresetReverb.PRESET_LARGEHALL: "Large Hall",
|
||||
PresetReverb.PRESET_PLATE: "Plate",
|
||||
}
|
||||
|
||||
def setPreset(self, preset: int):
|
||||
if preset in self._preset_names:
|
||||
self._preset = preset
|
||||
|
||||
def getPreset(self) -> int:
|
||||
return self._preset
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PresetReverb(preset={self._preset_names.get(self._preset, 'Unknown')})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# EnvironmentalReverb
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class EnvironmentalReverb(AudioEffect):
|
||||
"""EnvironmentalReverb — 环境混响"""
|
||||
|
||||
def __init__(self, priority: int = 0, audioSession: int = 0):
|
||||
super().__init__("EnvironmentalReverb", priority, audioSession)
|
||||
self._decay_time = 1500
|
||||
self._decay_hf_ratio = 500
|
||||
self._density = 500
|
||||
self._diffusion = 500
|
||||
self._reverb_level = 0
|
||||
self._room_level = 0
|
||||
self._room_hf_level = 0
|
||||
self._reflections_delay = 20
|
||||
self._reflections_level = -500
|
||||
self._reverb_delay = 40
|
||||
|
||||
def setDecayTime(self, decayTime: int):
|
||||
self._decay_time = decayTime
|
||||
|
||||
def getDecayTime(self) -> int:
|
||||
return self._decay_time
|
||||
|
||||
def setDecayHFRatio(self, decayHFRatio: int):
|
||||
self._decay_hf_ratio = decayHFRatio
|
||||
|
||||
def getDecayHFRatio(self) -> int:
|
||||
return self._decay_hf_ratio
|
||||
|
||||
def setDensity(self, density: int):
|
||||
self._density = density
|
||||
|
||||
def getDensity(self) -> int:
|
||||
return self._density
|
||||
|
||||
def setDiffusion(self, diffusion: int):
|
||||
self._diffusion = diffusion
|
||||
|
||||
def getDiffusion(self) -> int:
|
||||
return self._diffusion
|
||||
|
||||
def setReverbLevel(self, reverbLevel: int):
|
||||
self._reverb_level = reverbLevel
|
||||
|
||||
def getReverbLevel(self) -> int:
|
||||
return self._reverb_level
|
||||
|
||||
def setRoomLevel(self, roomLevel: int):
|
||||
self._room_level = roomLevel
|
||||
|
||||
def getRoomLevel(self) -> int:
|
||||
return self._room_level
|
||||
|
||||
def setRoomHFLevel(self, roomHFLevel: int):
|
||||
self._room_hf_level = roomHFLevel
|
||||
|
||||
def getRoomHFLevel(self) -> int:
|
||||
return self._room_hf_level
|
||||
|
||||
def setReflectionsDelay(self, reflectionsDelay: int):
|
||||
self._reflections_delay = reflectionsDelay
|
||||
|
||||
def getReflectionsDelay(self) -> int:
|
||||
return self._reflections_delay
|
||||
|
||||
def setReflectionsLevel(self, reflectionsLevel: int):
|
||||
self._reflections_level = reflectionsLevel
|
||||
|
||||
def getReflectionsLevel(self) -> int:
|
||||
return self._reflections_level
|
||||
|
||||
def setReverbDelay(self, reverbDelay: int):
|
||||
self._reverb_delay = reverbDelay
|
||||
|
||||
def getReverbDelay(self) -> int:
|
||||
return self._reverb_delay
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"EnvironmentalReverb(decayTime={self._decay_time}," \
|
||||
f" density={self._density})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# LoudnessEnhancer
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class LoudnessEnhancer(AudioEffect):
|
||||
"""LoudnessEnhancer — 响度增强器"""
|
||||
|
||||
PARAM_TARGET_GAIN_MB = 0
|
||||
|
||||
def __init__(self, audioSession: int = 0):
|
||||
super().__init__("LoudnessEnhancer", 0, audioSession)
|
||||
self._target_gain = 0
|
||||
|
||||
def setTargetGain(self, gainMillibels: int):
|
||||
self._target_gain = gainMillibels
|
||||
|
||||
def getTargetGain(self) -> int:
|
||||
return self._target_gain
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"LoudnessEnhancer(targetGain={self._target_gain} mB)"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DynamicsProcessing
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DynamicsProcessing(AudioEffect):
|
||||
"""DynamicsProcessing — 动态处理"""
|
||||
|
||||
class Stage(Stub):
|
||||
"""DynamicsProcessing.Stage — 处理阶段"""
|
||||
|
||||
def __init__(self, enabled: bool = True):
|
||||
super().__init__()
|
||||
self._enabled = enabled
|
||||
self._in_use = True
|
||||
|
||||
def isEnabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
def setEnabled(self, enabled: bool):
|
||||
self._enabled = enabled
|
||||
|
||||
class EQBand(Stage):
|
||||
"""DynamicsProcessing.EQBand — EQ 频段"""
|
||||
|
||||
def __init__(self, enabled: bool = True):
|
||||
super().__init__(enabled)
|
||||
self._gain = 0.0
|
||||
self._center_freq = 1000.0
|
||||
|
||||
def setGain(self, gain: float):
|
||||
self._gain = gain
|
||||
|
||||
def getGain(self) -> float:
|
||||
return self._gain
|
||||
|
||||
def setCenterFreq(self, freq: float):
|
||||
self._center_freq = freq
|
||||
|
||||
def getCenterFreq(self) -> float:
|
||||
return self._center_freq
|
||||
|
||||
class EQ(Stage):
|
||||
"""DynamicsProcessing.EQ — 均衡器"""
|
||||
|
||||
def __init__(self, enabled: bool = True, bandCount: int = 6):
|
||||
super().__init__(enabled)
|
||||
self._bands = [EQBand() for _ in range(bandCount)]
|
||||
|
||||
def getBand(self, band: int) -> "DynamicsProcessing.EQBand":
|
||||
if 0 <= band < len(self._bands):
|
||||
return self._bands[band]
|
||||
raise IndexError(f"Band {band} out of range")
|
||||
|
||||
class MBCBand(Stage):
|
||||
"""DynamicsProcessing.MBCBand — 多频段压缩频段"""
|
||||
|
||||
def __init__(self, enabled: bool = True):
|
||||
super().__init__(enabled)
|
||||
self._gain = 0.0
|
||||
|
||||
def setGain(self, gain: float):
|
||||
self._gain = gain
|
||||
|
||||
def getGain(self) -> float:
|
||||
return self._gain
|
||||
|
||||
class MBC(Stage):
|
||||
"""DynamicsProcessing.MBC — 多频段压缩"""
|
||||
|
||||
def __init__(self, enabled: bool = True, bandCount: int = 6):
|
||||
super().__init__(enabled)
|
||||
self._bands = [MBCBand() for _ in range(bandCount)]
|
||||
|
||||
def getBand(self, band: int) -> "DynamicsProcessing.MBCBand":
|
||||
if 0 <= band < len(self._bands):
|
||||
return self._bands[band]
|
||||
raise IndexError(f"Band {band} out of range")
|
||||
|
||||
class Limiter(Stage):
|
||||
"""DynamicsProcessing.Limiter — 限制器"""
|
||||
|
||||
def __init__(self, enabled: bool = True):
|
||||
super().__init__(enabled)
|
||||
|
||||
class Channel(Stub):
|
||||
"""DynamicsProcessing.Channel — 处理通道"""
|
||||
|
||||
def __init__(self, stageCount: int = 1):
|
||||
super().__init__()
|
||||
self._stage_count = stageCount
|
||||
|
||||
class Config(Stub):
|
||||
"""DynamicsProcessing.Config — 处理配置"""
|
||||
|
||||
def __init__(self, channelCount: int = 1, preEqCount: int = 1,
|
||||
mbcCount: int = 1, postEqCount: int = 1,
|
||||
limiterCount: int = 1, prefEqBandCount: int = 6,
|
||||
postEqBandCount: int = 6, mbcBandCount: int = 6):
|
||||
super().__init__()
|
||||
self._channels = [DynamicsProcessing.Channel(0)
|
||||
for _ in range(channelCount)]
|
||||
|
||||
def __init__(self, audioSession: int = 0):
|
||||
super().__init__("DynamicsProcessing", 0, audioSession)
|
||||
self._channel_count = 1
|
||||
self._post_constraints: List[Any] = []
|
||||
self._pre_eqs: List[DynamicsProcessing.EQ] = [DynamicsProcessing.EQ()]
|
||||
self._post_eqs: List[DynamicsProcessing.EQ] = [DynamicsProcessing.EQ()]
|
||||
self._mbcs: List[DynamicsProcessing.MBC] = [DynamicsProcessing.MBC()]
|
||||
self._limiters: List[DynamicsProcessing.Limiter] = [DynamicsProcessing.Limiter()]
|
||||
self._mbands: List[Any] = []
|
||||
self._channel_stage: List[Any] = []
|
||||
|
||||
def getChannelCount(self) -> int:
|
||||
return self._channel_count
|
||||
|
||||
def getPostConstraints(self) -> List[Any]:
|
||||
return self._post_constraints
|
||||
|
||||
def getPreEQs(self) -> List[EQ]:
|
||||
return self._pre_eqs
|
||||
|
||||
def getMbands(self) -> List[Any]:
|
||||
return self._mbands
|
||||
|
||||
def getPostEQs(self) -> List[EQ]:
|
||||
return self._post_eqs
|
||||
|
||||
def getLimiters(self) -> List[Limiter]:
|
||||
return self._limiters
|
||||
|
||||
def getMbc(self) -> List[MBC]:
|
||||
return self._mbcs
|
||||
|
||||
def getChannelStage(self) -> List[Any]:
|
||||
return self._channel_stage
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"DynamicsProcessing(channels={self._channel_count})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"AudioEffect",
|
||||
"Equalizer",
|
||||
"BassBoost",
|
||||
"Virtualizer",
|
||||
"PresetReverb",
|
||||
"EnvironmentalReverb",
|
||||
"LoudnessEnhancer",
|
||||
"DynamicsProcessing",
|
||||
]
|
||||
@@ -0,0 +1,310 @@
|
||||
"""
|
||||
Android media.browse 组件 — MediaBrowser, MediaItem, MediaDescription 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional, Set
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaBrowser
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaBrowser(Stub):
|
||||
"""MediaBrowser — 媒体浏览器"""
|
||||
|
||||
EXTRA_PAGE = "android.media.browse.extra.PAGE"
|
||||
EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# ConnectionCallback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class ConnectionCallback(Stub):
|
||||
"""MediaBrowser.ConnectionCallback — 连接回调"""
|
||||
|
||||
def onConnected(self):
|
||||
pass
|
||||
|
||||
def onConnectionSuspended(self):
|
||||
pass
|
||||
|
||||
def onConnectionFailed(self):
|
||||
pass
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# SubscriptionCallback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class SubscriptionCallback(Stub):
|
||||
"""MediaBrowser.SubscriptionCallback — 订阅回调"""
|
||||
|
||||
def onChildrenLoaded(self, parentId: str, children: List["MediaBrowser.MediaItem"]):
|
||||
pass
|
||||
|
||||
def onError(self, parentId: str):
|
||||
pass
|
||||
|
||||
def onChildrenLoadedWithOptions(self, parentId: str,
|
||||
children: List["MediaBrowser.MediaItem"],
|
||||
options: Any):
|
||||
pass
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# ItemCallback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class ItemCallback(Stub):
|
||||
"""MediaBrowser.ItemCallback — 媒体项回调"""
|
||||
|
||||
def onItemLoaded(self, item: "MediaBrowser.MediaItem"):
|
||||
pass
|
||||
|
||||
def onError(self, mediaId: str):
|
||||
pass
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# MediaItem
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class MediaItem(Stub):
|
||||
"""MediaBrowser.MediaItem — 媒体项"""
|
||||
|
||||
FLAG_BROWSABLE = 1
|
||||
FLAG_PLAYABLE = 2
|
||||
|
||||
def __init__(self, description: "MediaDescription" = None, flags: int = FLAG_PLAYABLE):
|
||||
super().__init__()
|
||||
self._description = description or MediaDescription()
|
||||
self._flags = flags
|
||||
|
||||
def getMediaId(self) -> Optional[str]:
|
||||
return self._description.getMediaId()
|
||||
|
||||
def getDescription(self) -> "MediaDescription":
|
||||
return self._description
|
||||
|
||||
def getFlags(self) -> int:
|
||||
return self._flags
|
||||
|
||||
def isBrowsable(self) -> bool:
|
||||
return bool(self._flags & MediaBrowser.MediaItem.FLAG_BROWSABLE)
|
||||
|
||||
def isPlayable(self) -> bool:
|
||||
return bool(self._flags & MediaBrowser.MediaItem.FLAG_PLAYABLE)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MediaItem(mediaId={self.getMediaId()}, flags={self._flags})"
|
||||
|
||||
def __init__(self, context: Any = None, serviceComponent: Any = None,
|
||||
connectionCallback: Optional[ConnectionCallback] = None,
|
||||
rootHints: Any = None):
|
||||
super().__init__()
|
||||
self._connected = False
|
||||
self._service_component = serviceComponent
|
||||
self._root: str = ""
|
||||
self._session_token: Any = None
|
||||
self._connection_callback = connectionCallback
|
||||
self._subscriptions: Dict[str, List[MediaBrowser.SubscriptionCallback]] = {}
|
||||
self._media_tree: Dict[str, List[MediaBrowser.MediaItem]] = {}
|
||||
self._item_cache: Dict[str, MediaBrowser.MediaItem] = {}
|
||||
|
||||
def connect(self):
|
||||
self._connected = True
|
||||
self._root = "__ROOT__"
|
||||
# Build a simple in-memory media tree
|
||||
self._media_tree["__ROOT__"] = [
|
||||
MediaBrowser.MediaItem(
|
||||
MediaDescription.Builder()
|
||||
.setMediaId("__RECENT__")
|
||||
.setTitle("Recently Played")
|
||||
.setSubtitle("Your recently played media")
|
||||
.build(),
|
||||
flags=MediaBrowser.MediaItem.FLAG_BROWSABLE
|
||||
),
|
||||
MediaBrowser.MediaItem(
|
||||
MediaDescription.Builder()
|
||||
.setMediaId("__ALL__")
|
||||
.setTitle("All Music")
|
||||
.setSubtitle("All your music library")
|
||||
.build(),
|
||||
flags=MediaBrowser.MediaItem.FLAG_BROWSABLE
|
||||
),
|
||||
]
|
||||
self._media_tree["__RECENT__"] = [
|
||||
MediaBrowser.MediaItem(
|
||||
MediaDescription.Builder()
|
||||
.setMediaId("song_1")
|
||||
.setTitle("Sample Song 1")
|
||||
.setSubtitle("Artist 1")
|
||||
.build(),
|
||||
flags=MediaBrowser.MediaItem.FLAG_PLAYABLE
|
||||
),
|
||||
]
|
||||
self._item_cache["song_1"] = self._media_tree["__RECENT__"][0]
|
||||
|
||||
if self._connection_callback:
|
||||
self._connection_callback.onConnected()
|
||||
|
||||
def disconnect(self):
|
||||
self._connected = False
|
||||
self._subscriptions.clear()
|
||||
if self._connection_callback:
|
||||
self._connection_callback.onConnectionSuspended()
|
||||
|
||||
def getConnected(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
def getServiceComponent(self) -> Any:
|
||||
return self._service_component
|
||||
|
||||
def getRoot(self) -> str:
|
||||
return self._root
|
||||
|
||||
def isConnected(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
def getSessionToken(self) -> Any:
|
||||
return self._session_token
|
||||
|
||||
def getItem(self, mediaId: str) -> Optional[MediaItem]:
|
||||
return self._item_cache.get(mediaId)
|
||||
|
||||
def subscribe(self, parentId: str, callback: SubscriptionCallback):
|
||||
if parentId not in self._subscriptions:
|
||||
self._subscriptions[parentId] = []
|
||||
self._subscriptions[parentId].append(callback)
|
||||
|
||||
children = self._media_tree.get(parentId, [])
|
||||
if children:
|
||||
callback.onChildrenLoaded(parentId, children)
|
||||
else:
|
||||
callback.onError(parentId)
|
||||
|
||||
def unsubscribe(self, parentId: str, callback: Optional[SubscriptionCallback] = None):
|
||||
if parentId in self._subscriptions:
|
||||
if callback:
|
||||
self._subscriptions[parentId] = [
|
||||
cb for cb in self._subscriptions[parentId]
|
||||
if cb is not callback
|
||||
]
|
||||
if not self._subscriptions[parentId]:
|
||||
del self._subscriptions[parentId]
|
||||
else:
|
||||
del self._subscriptions[parentId]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaDescription
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaDescription(Stub):
|
||||
"""MediaDescription — 媒体描述"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""MediaDescription.Builder — 媒体描述构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._media_id: Optional[str] = None
|
||||
self._title: Optional[str] = None
|
||||
self._subtitle: Optional[str] = None
|
||||
self._description: Optional[str] = None
|
||||
self._icon_bitmap: Any = None
|
||||
self._icon_uri: Optional[str] = None
|
||||
self._extras: Any = None
|
||||
self._media_uri: Optional[str] = None
|
||||
|
||||
def setMediaId(self, mediaId: str) -> "MediaDescription.Builder":
|
||||
self._media_id = mediaId
|
||||
return self
|
||||
|
||||
def setTitle(self, title: str) -> "MediaDescription.Builder":
|
||||
self._title = title
|
||||
return self
|
||||
|
||||
def setSubtitle(self, subtitle: str) -> "MediaDescription.Builder":
|
||||
self._subtitle = subtitle
|
||||
return self
|
||||
|
||||
def setDescription(self, description: str) -> "MediaDescription.Builder":
|
||||
self._description = description
|
||||
return self
|
||||
|
||||
def setIconBitmap(self, icon: Any) -> "MediaDescription.Builder":
|
||||
self._icon_bitmap = icon
|
||||
return self
|
||||
|
||||
def setIconUri(self, iconUri: str) -> "MediaDescription.Builder":
|
||||
self._icon_uri = iconUri
|
||||
return self
|
||||
|
||||
def setExtras(self, extras: Any) -> "MediaDescription.Builder":
|
||||
self._extras = extras
|
||||
return self
|
||||
|
||||
def setMediaUri(self, mediaUri: str) -> "MediaDescription.Builder":
|
||||
self._media_uri = mediaUri
|
||||
return self
|
||||
|
||||
def build(self) -> "MediaDescription":
|
||||
md = MediaDescription()
|
||||
md._media_id = self._media_id
|
||||
md._title = self._title
|
||||
md._subtitle = self._subtitle
|
||||
md._description = self._description
|
||||
md._icon_bitmap = self._icon_bitmap
|
||||
md._icon_uri = self._icon_uri
|
||||
md._extras = self._extras
|
||||
md._media_uri = self._media_uri
|
||||
return md
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._media_id: Optional[str] = None
|
||||
self._title: Optional[str] = None
|
||||
self._subtitle: Optional[str] = None
|
||||
self._description: Optional[str] = None
|
||||
self._icon_bitmap: Any = None
|
||||
self._icon_uri: Optional[str] = None
|
||||
self._extras: Any = None
|
||||
self._media_uri: Optional[str] = None
|
||||
|
||||
def getMediaId(self) -> Optional[str]:
|
||||
return self._media_id
|
||||
|
||||
def getTitle(self) -> Optional[str]:
|
||||
return self._title
|
||||
|
||||
def getSubtitle(self) -> Optional[str]:
|
||||
return self._subtitle
|
||||
|
||||
def getDescription(self) -> Optional[str]:
|
||||
return self._description
|
||||
|
||||
def getIconBitmap(self) -> Any:
|
||||
return self._icon_bitmap
|
||||
|
||||
def getIconUri(self) -> Optional[str]:
|
||||
return self._icon_uri
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
def getMediaUri(self) -> Optional[str]:
|
||||
return self._media_uri
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MediaDescription(id={self._media_id}, title={self._title})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"MediaBrowser",
|
||||
"MediaDescription",
|
||||
]
|
||||
@@ -0,0 +1,315 @@
|
||||
"""
|
||||
Android media.midi 组件 — MidiManager, MidiDevice, MidiReceiver, MidiSender 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional, Set
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiManager(Stub):
|
||||
"""MidiManager — MIDI 管理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._devices: Dict[int, MidiDevice] = {}
|
||||
self._callbacks: Dict[int, Callable] = {}
|
||||
self._next_device_id = 1
|
||||
self._init_default_devices()
|
||||
|
||||
def _init_default_devices(self):
|
||||
info = MidiDeviceInfo(
|
||||
id=0, inputPortCount=1, outputPortCount=1,
|
||||
type=MidiDeviceInfo.TYPE_VIRTUAL,
|
||||
properties={MidiDeviceInfo.PROPERTY_NAME: "Virtual MIDI"}
|
||||
)
|
||||
device = MidiDevice(info)
|
||||
self._devices[0] = device
|
||||
|
||||
def getDevices(self) -> List["MidiDeviceInfo"]:
|
||||
return [d.getInfo() for d in self._devices.values()]
|
||||
|
||||
def registerDeviceCallback(self, callback: Callable, handler: Any = None):
|
||||
cid = id(callback)
|
||||
self._callbacks[cid] = callback
|
||||
|
||||
def unregisterDeviceCallback(self, callback: Callable):
|
||||
cid = id(callback)
|
||||
self._callbacks.pop(cid, None)
|
||||
|
||||
def openDevice(self, deviceInfo: "MidiDeviceInfo",
|
||||
callback: Callable, handler: Any = None):
|
||||
device = self._devices.get(deviceInfo.getId())
|
||||
if device:
|
||||
callback(device)
|
||||
|
||||
def openBluetoothDevice(self, bluetoothDevice: Any) -> Optional["MidiDevice"]:
|
||||
return None
|
||||
|
||||
def getDefaultDevice(self) -> Optional["MidiDevice"]:
|
||||
for d in self._devices.values():
|
||||
return d
|
||||
return None
|
||||
|
||||
def deviceConnected(self, info: "MidiDeviceInfo"):
|
||||
pass
|
||||
|
||||
def deviceDisconnected(self, info: "MidiDeviceInfo"):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiDevice
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiDevice(Stub):
|
||||
"""MidiDevice — MIDI 设备"""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# MidiConnection
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class MidiConnection(Stub):
|
||||
"""MidiDevice.MidiConnection — MIDI 连接"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return "MidiConnection"
|
||||
|
||||
def __init__(self, info: "MidiDeviceInfo" = None):
|
||||
super().__init__()
|
||||
self._info = info or MidiDeviceInfo()
|
||||
self._input_ports: List[MidiInputPort] = []
|
||||
self._output_ports: List[MidiOutputPort] = []
|
||||
self._closed = False
|
||||
|
||||
def getInfo(self) -> "MidiDeviceInfo":
|
||||
return self._info
|
||||
|
||||
def openInputPort(self, portNumber: int) -> Optional["MidiInputPort"]:
|
||||
if self._closed:
|
||||
return None
|
||||
port = MidiInputPort(portNumber)
|
||||
self._input_ports.append(port)
|
||||
return port
|
||||
|
||||
def openOutputPort(self, portNumber: int) -> Optional["MidiOutputPort"]:
|
||||
if self._closed:
|
||||
return None
|
||||
port = MidiOutputPort(portNumber)
|
||||
self._output_ports.append(port)
|
||||
return port
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MidiDevice(info={self._info})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiDeviceInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiDeviceInfo(Stub):
|
||||
"""MidiDeviceInfo — MIDI 设备信息"""
|
||||
|
||||
TYPE_USB = 1
|
||||
TYPE_BLUETOOTH = 2
|
||||
TYPE_VIRTUAL = 3
|
||||
|
||||
PROPERTY_ALSA_CARD = "alsa_card"
|
||||
PROPERTY_ALSA_DEVICE = "alsa_device"
|
||||
PROPERTY_MANUFACTURER = "manufacturer"
|
||||
PROPERTY_NAME = "name"
|
||||
PROPERTY_PRODUCT = "product"
|
||||
PROPERTY_SERIAL_NUMBER = "serial_number"
|
||||
PROPERTY_USB_DEVICE = "usb_device"
|
||||
|
||||
def __init__(self, id: int = 0, inputPortCount: int = 0,
|
||||
outputPortCount: int = 0, type_: int = TYPE_VIRTUAL,
|
||||
properties: Optional[Dict[str, str]] = None):
|
||||
super().__init__()
|
||||
self._id = id
|
||||
self._input_port_count = inputPortCount
|
||||
self._output_port_count = outputPortCount
|
||||
self._type = type_
|
||||
self._properties = properties or {}
|
||||
|
||||
def getId(self) -> int:
|
||||
return self._id
|
||||
|
||||
def getProperties(self) -> Dict[str, str]:
|
||||
return dict(self._properties)
|
||||
|
||||
def getInputPortCount(self) -> int:
|
||||
return self._input_port_count
|
||||
|
||||
def getOutputPortCount(self) -> int:
|
||||
return self._output_port_count
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._type
|
||||
|
||||
def equals(self, other: Any) -> bool:
|
||||
if not isinstance(other, MidiDeviceInfo):
|
||||
return False
|
||||
return self._id == other._id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._id) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MidiDeviceInfo(id={self._id}, type={self._type}," \
|
||||
f" name={self._properties.get(MidiDeviceInfo.PROPERTY_NAME, '')})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiReceiver
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiReceiver(Stub):
|
||||
"""MidiReceiver — MIDI 接收器"""
|
||||
|
||||
def __init__(self, maxMessageSize: int = 1024):
|
||||
super().__init__()
|
||||
self._max_message_size = maxMessageSize
|
||||
|
||||
def send(self, msg: bytes, offset: int, count: int):
|
||||
self.onSend(msg, offset, count)
|
||||
|
||||
def onSend(self, msg: bytes, offset: int, count: int):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def getMaxMessageSize(self) -> int:
|
||||
return self._max_message_size
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiSender
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiSender(Stub):
|
||||
"""MidiSender — MIDI 发送器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._receivers: List[MidiReceiver] = []
|
||||
|
||||
def send(self, msg: bytes, offset: int, count: int):
|
||||
for r in self._receivers:
|
||||
r.send(msg, offset, count)
|
||||
|
||||
def onConnect(self, receiver: MidiReceiver):
|
||||
pass
|
||||
|
||||
def onDisconnect(self, receiver: MidiReceiver):
|
||||
pass
|
||||
|
||||
def connect(self, receiver: MidiReceiver):
|
||||
self._receivers.append(receiver)
|
||||
self.onConnect(receiver)
|
||||
|
||||
def disconnect(self, receiver: MidiReceiver):
|
||||
if receiver in self._receivers:
|
||||
self._receivers.remove(receiver)
|
||||
self.onDisconnect(receiver)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiInputPort
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiInputPort(MidiReceiver):
|
||||
"""MidiInputPort — MIDI 输入端口"""
|
||||
|
||||
def __init__(self, portNumber: int = 0):
|
||||
super().__init__()
|
||||
self._port_number = portNumber
|
||||
self._closed = False
|
||||
|
||||
def send(self, msg: bytes, offset: int, count: int):
|
||||
if not self._closed:
|
||||
self.onSend(msg, offset, count)
|
||||
|
||||
def onSend(self, msg: bytes, offset: int, count: int):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiOutputPort
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiOutputPort(MidiSender):
|
||||
"""MidiOutputPort — MIDI 输出端口"""
|
||||
|
||||
def __init__(self, portNumber: int = 0):
|
||||
super().__init__()
|
||||
self._port_number = portNumber
|
||||
|
||||
def send(self, msg: bytes, offset: int, count: int):
|
||||
super().send(msg, offset, count)
|
||||
|
||||
def onSend(self, msg: bytes, offset: int, count: int):
|
||||
pass
|
||||
|
||||
def getPortNumber(self) -> int:
|
||||
return self._port_number
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiEndpoint (stub)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiEndpoint(Stub):
|
||||
"""MidiEndpoint — MIDI 端点"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MidiDeviceService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MidiDeviceService(Stub):
|
||||
"""MidiDeviceService — MIDI 设备服务"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"MidiManager",
|
||||
"MidiDevice",
|
||||
"MidiDeviceInfo",
|
||||
"MidiReceiver",
|
||||
"MidiSender",
|
||||
"MidiInputPort",
|
||||
"MidiOutputPort",
|
||||
"MidiEndpoint",
|
||||
"MidiDeviceService",
|
||||
]
|
||||
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
Android media.projection 组件 — MediaProjectionManager, MediaProjection, MediaProjectionInfo 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaProjectionManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaProjectionManager(Stub):
|
||||
"""MediaProjectionManager — 媒体投影管理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._active_projections: Dict[int, "MediaProjection"] = {}
|
||||
self._callbacks: Dict[int, Callable] = {}
|
||||
|
||||
def createScreenCaptureIntent(self) -> Any:
|
||||
return "SCREEN_CAPTURE_INTENT"
|
||||
|
||||
def getMediaProjection(self, resultCode: int, data: Any) -> Optional["MediaProjection"]:
|
||||
if resultCode == -1: # RESULT_OK
|
||||
proj = MediaProjection()
|
||||
self._active_projections[id(proj)] = proj
|
||||
return proj
|
||||
return None
|
||||
|
||||
def getActiveProjectionInfo(self) -> Optional["MediaProjectionInfo"]:
|
||||
for proj in self._active_projections.values():
|
||||
info = proj.getProjectionInfo()
|
||||
if info:
|
||||
return info
|
||||
return None
|
||||
|
||||
def getDisplayProjection(self, displayId: int) -> Optional["MediaProjection"]:
|
||||
return None
|
||||
|
||||
def addCallback(self, callback: Callable, handler: Any = None):
|
||||
cid = id(callback)
|
||||
self._callbacks[cid] = callback
|
||||
|
||||
def removeCallback(self, callback: Callable):
|
||||
cid = id(callback)
|
||||
self._callbacks.pop(cid, None)
|
||||
|
||||
def isMediaProjectionAllowed(self, packageName: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaProjection
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaProjection(Stub):
|
||||
"""MediaProjection — 媒体投影"""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Callback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Callback(Stub):
|
||||
"""MediaProjection.Callback — 投影回调"""
|
||||
|
||||
def onStop(self):
|
||||
pass
|
||||
|
||||
def onCapturedContentResize(self, width: int, height: int):
|
||||
pass
|
||||
|
||||
def onCapturedContentVisibilityChanged(self, isVisible: bool):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._stopped = False
|
||||
self._callbacks: List[MediaProjection.Callback] = []
|
||||
self._projection_info = MediaProjectionInfo(
|
||||
packageName="com.android.systemui",
|
||||
uid=1000,
|
||||
isVirtualDisplay=False
|
||||
)
|
||||
|
||||
def createVirtualDisplay(self, name: str, width: int, height: int,
|
||||
densityDpi: int, flags: int, surface: Any,
|
||||
callback: Callable = None, handler: Any = None) -> Any:
|
||||
from .hardware_display import VirtualDisplay
|
||||
vd = VirtualDisplay(99, name, width, height, densityDpi, surface, flags)
|
||||
return vd
|
||||
|
||||
def registerCallback(self, callback: Callback):
|
||||
self._callbacks.append(callback)
|
||||
|
||||
def unregisterCallback(self, callback: Callback):
|
||||
if callback in self._callbacks:
|
||||
self._callbacks.remove(callback)
|
||||
|
||||
def stop(self):
|
||||
self._stopped = True
|
||||
for cb in self._callbacks:
|
||||
cb.onStop()
|
||||
|
||||
def getProjectionInfo(self) -> "MediaProjectionInfo":
|
||||
return self._projection_info
|
||||
|
||||
def getDisplay(self) -> Optional[Any]:
|
||||
from .hardware_display import Display
|
||||
return Display(displayId=0, name="Projection Display")
|
||||
|
||||
def applyTransparentRegionOverlay(self, region: Any):
|
||||
pass
|
||||
|
||||
def removeTransparentRegionOverlay(self):
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MediaProjection(stopped={self._stopped})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaProjectionInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaProjectionInfo(Stub):
|
||||
"""MediaProjectionInfo — 媒体投影信息"""
|
||||
|
||||
def __init__(self, packageName: str = "", uid: int = 0,
|
||||
isVirtualDisplay: bool = False):
|
||||
super().__init__()
|
||||
self._package_name = packageName
|
||||
self._uid = uid
|
||||
self._is_virtual_display = isVirtualDisplay
|
||||
|
||||
def getProjectionType(self) -> int:
|
||||
return 0
|
||||
|
||||
def getLaunchedFromPackage(self) -> str:
|
||||
return self._package_name
|
||||
|
||||
def getLaunchedFromUid(self) -> int:
|
||||
return self._uid
|
||||
|
||||
def isVirtualDisplay(self) -> bool:
|
||||
return self._is_virtual_display
|
||||
|
||||
def equals(self, other: Any) -> bool:
|
||||
if not isinstance(other, MediaProjectionInfo):
|
||||
return False
|
||||
return (self._package_name == other._package_name
|
||||
and self._uid == other._uid)
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._package_name, self._uid)) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MediaProjectionInfo(package={self._package_name}, uid={self._uid})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"MediaProjectionManager",
|
||||
"MediaProjection",
|
||||
"MediaProjectionInfo",
|
||||
]
|
||||
@@ -0,0 +1,854 @@
|
||||
"""
|
||||
Android media.session 组件 — MediaSession, MediaController, PlaybackState, MediaMetadata 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaSession
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaSession(Stub):
|
||||
"""MediaSession — 媒体会话"""
|
||||
|
||||
FLAG_HANDLES_MEDIA_BUTTONS = 1
|
||||
FLAG_HANDLES_TRANSPORT_CONTROLS = 2
|
||||
FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 4
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Callback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Callback(Stub):
|
||||
"""MediaSession.Callback — 会话回调"""
|
||||
|
||||
def onPlay(self):
|
||||
pass
|
||||
|
||||
def onPause(self):
|
||||
pass
|
||||
|
||||
def onSkipToNext(self):
|
||||
pass
|
||||
|
||||
def onSkipToPrevious(self):
|
||||
pass
|
||||
|
||||
def onFastForward(self):
|
||||
pass
|
||||
|
||||
def onRewind(self):
|
||||
pass
|
||||
|
||||
def onStop(self):
|
||||
pass
|
||||
|
||||
def onSeekTo(self, pos: int):
|
||||
pass
|
||||
|
||||
def onSetRating(self, rating: "Rating"):
|
||||
pass
|
||||
|
||||
def onSetRepeatMode(self, repeatMode: int):
|
||||
pass
|
||||
|
||||
def onSetShuffleMode(self, shuffleMode: int):
|
||||
pass
|
||||
|
||||
def onCommand(self, command: str, extras: Any, cb: Any):
|
||||
pass
|
||||
|
||||
def onCustomAction(self, action: str, extras: Any):
|
||||
pass
|
||||
|
||||
def onMediaButtonEvent(self, mediaButtonIntent: Any) -> bool:
|
||||
return False
|
||||
|
||||
def onPlayFromMediaId(self, mediaId: str, extras: Any):
|
||||
pass
|
||||
|
||||
def onPlayFromSearch(self, query: str, extras: Any):
|
||||
pass
|
||||
|
||||
def onPlayFromUri(self, uri: str, extras: Any):
|
||||
pass
|
||||
|
||||
def onPrepare(self):
|
||||
pass
|
||||
|
||||
def onPrepareFromMediaId(self, mediaId: str, extras: Any):
|
||||
pass
|
||||
|
||||
def onPrepareFromSearch(self, query: str, extras: Any):
|
||||
pass
|
||||
|
||||
def onPrepareFromUri(self, uri: str, extras: Any):
|
||||
pass
|
||||
|
||||
def __init__(self, context: Any = None, tag: str = "",
|
||||
token: "SessionToken" = None):
|
||||
super().__init__()
|
||||
self._tag = tag
|
||||
self._token = token or SessionToken()
|
||||
self._active = False
|
||||
self._flags = 0
|
||||
self._session_activity: Any = None
|
||||
self._callback: Optional[MediaSession.Callback] = None
|
||||
self._metadata: Optional["MediaMetadata"] = None
|
||||
self._playback_state: Optional["PlaybackState"] = None
|
||||
self._queue: List["QueueItem"] = []
|
||||
self._queue_title: Optional[str] = None
|
||||
self._extras: Any = None
|
||||
self._rating_type = Rating.RATING_NONE
|
||||
self._repeat_mode = PlaybackState.REPEAT_MODE_NONE
|
||||
self._shuffle_mode = PlaybackState.SHUFFLE_MODE_NONE
|
||||
self._captioning_enabled = False
|
||||
self._controllers: List[MediaController] = []
|
||||
|
||||
def setSessionActivity(self, sessionActivity: Any):
|
||||
self._session_activity = sessionActivity
|
||||
|
||||
def sendSessionEvent(self, event: str, extras: Any):
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onSessionEvent(event, extras)
|
||||
|
||||
def release(self):
|
||||
self._active = False
|
||||
self._controllers.clear()
|
||||
|
||||
def getSessionToken(self) -> "SessionToken":
|
||||
return self._token
|
||||
|
||||
def setFlags(self, flags: int):
|
||||
self._flags = flags
|
||||
|
||||
def setPlaybackToLocal(self, volumeProvider: Any = None):
|
||||
pass
|
||||
|
||||
def setPlaybackToRemote(self, volumeProvider: Any):
|
||||
pass
|
||||
|
||||
def setActive(self, active: bool):
|
||||
self._active = active
|
||||
|
||||
def isActive(self) -> bool:
|
||||
return self._active
|
||||
|
||||
def setMediaMetadata(self, metadata: "MediaMetadata"):
|
||||
self._metadata = metadata
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onMetadataChanged(metadata)
|
||||
|
||||
def setPlaybackState(self, state: "PlaybackState"):
|
||||
self._playback_state = state
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onPlaybackStateChanged(state)
|
||||
|
||||
def setQueue(self, queue: List["QueueItem"]):
|
||||
self._queue = queue
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onQueueChanged(queue)
|
||||
|
||||
def setQueueTitle(self, title: str):
|
||||
self._queue_title = title
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onQueueTitleChanged(title)
|
||||
|
||||
def setExtras(self, extras: Any):
|
||||
self._extras = extras
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onExtrasChanged(extras)
|
||||
|
||||
def setMetadata(self, metadata: "MediaMetadata"):
|
||||
self.setMediaMetadata(metadata)
|
||||
|
||||
def setRatingType(self, ratingType: int):
|
||||
self._rating_type = ratingType
|
||||
|
||||
def setRepeatMode(self, repeatMode: int):
|
||||
self._repeat_mode = repeatMode
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onRepeatModeChanged(repeatMode)
|
||||
|
||||
def setShuffleMode(self, shuffleMode: int):
|
||||
self._shuffle_mode = shuffleMode
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onShuffleModeChanged(shuffleMode)
|
||||
|
||||
def setCaptioningEnabled(self, enabled: bool):
|
||||
self._captioning_enabled = enabled
|
||||
for ctrl in self._controllers:
|
||||
ctrl._callback.onCaptioningEnabledChanged(enabled)
|
||||
|
||||
def getController(self) -> "MediaController":
|
||||
ctrl = MediaController(self)
|
||||
self._controllers.append(ctrl)
|
||||
return ctrl
|
||||
|
||||
def getCallerPackageName(self) -> str:
|
||||
return "com.android.systemui"
|
||||
|
||||
def setCallback(self, callback: Callback, handler: Any = None):
|
||||
self._callback = callback
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaController
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaController(Stub):
|
||||
"""MediaController — 媒体控制器"""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Callback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Callback(Stub):
|
||||
"""MediaController.Callback — 控制器回调"""
|
||||
|
||||
def onSessionReady(self):
|
||||
pass
|
||||
|
||||
def onSessionDestroyed(self):
|
||||
pass
|
||||
|
||||
def onSessionEvent(self, event: str, extras: Any):
|
||||
pass
|
||||
|
||||
def onPlaybackStateChanged(self, state: "PlaybackState"):
|
||||
pass
|
||||
|
||||
def onMetadataChanged(self, metadata: "MediaMetadata"):
|
||||
pass
|
||||
|
||||
def onQueueChanged(self, queue: List["QueueItem"]):
|
||||
pass
|
||||
|
||||
def onQueueTitleChanged(self, title: str):
|
||||
pass
|
||||
|
||||
def onExtrasChanged(self, extras: Any):
|
||||
pass
|
||||
|
||||
def onAudioInfoChanged(self, info: Any):
|
||||
pass
|
||||
|
||||
def onCaptioningEnabledChanged(self, enabled: bool):
|
||||
pass
|
||||
|
||||
def onRepeatModeChanged(self, repeatMode: int):
|
||||
pass
|
||||
|
||||
def onShuffleModeChanged(self, shuffleMode: int):
|
||||
pass
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# TransportControls
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class TransportControls(Stub):
|
||||
"""MediaController.TransportControls — 传输控制"""
|
||||
|
||||
def __init__(self, session: "MediaSession"):
|
||||
super().__init__()
|
||||
self._session = session
|
||||
|
||||
def play(self):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPlay()
|
||||
|
||||
def pause(self):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPause()
|
||||
|
||||
def stop(self):
|
||||
if self._session._callback:
|
||||
self._session._callback.onStop()
|
||||
|
||||
def fastForward(self):
|
||||
if self._session._callback:
|
||||
self._session._callback.onFastForward()
|
||||
|
||||
def rewind(self):
|
||||
if self._session._callback:
|
||||
self._session._callback.onRewind()
|
||||
|
||||
def skipToNext(self):
|
||||
if self._session._callback:
|
||||
self._session._callback.onSkipToNext()
|
||||
|
||||
def skipToPrevious(self):
|
||||
if self._session._callback:
|
||||
self._session._callback.onSkipToPrevious()
|
||||
|
||||
def seekTo(self, pos: int):
|
||||
if self._session._callback:
|
||||
self._session._callback.onSeekTo(pos)
|
||||
|
||||
def setRating(self, rating: "Rating"):
|
||||
if self._session._callback:
|
||||
self._session._callback.onSetRating(rating)
|
||||
|
||||
def setRepeatMode(self, repeatMode: int):
|
||||
if self._session._callback:
|
||||
self._session._callback.onSetRepeatMode(repeatMode)
|
||||
|
||||
def setShuffleMode(self, shuffleMode: int):
|
||||
if self._session._callback:
|
||||
self._session._callback.onSetShuffleMode(shuffleMode)
|
||||
|
||||
def sendCustomAction(self, action: str, extras: Any):
|
||||
if self._session._callback:
|
||||
self._session._callback.onCustomAction(action, extras)
|
||||
|
||||
def playFromMediaId(self, mediaId: str, extras: Any):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPlayFromMediaId(mediaId, extras)
|
||||
|
||||
def playFromSearch(self, query: str, extras: Any):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPlayFromSearch(query, extras)
|
||||
|
||||
def playFromUri(self, uri: str, extras: Any):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPlayFromUri(uri, extras)
|
||||
|
||||
def prepare(self):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPrepare()
|
||||
|
||||
def prepareFromMediaId(self, mediaId: str, extras: Any):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPrepareFromMediaId(mediaId, extras)
|
||||
|
||||
def prepareFromSearch(self, query: str, extras: Any):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPrepareFromSearch(query, extras)
|
||||
|
||||
def prepareFromUri(self, uri: str, extras: Any):
|
||||
if self._session._callback:
|
||||
self._session._callback.onPrepareFromUri(uri, extras)
|
||||
|
||||
def __init__(self, session: "MediaSession" = None):
|
||||
super().__init__()
|
||||
self._session = session
|
||||
self._callback = MediaController.Callback()
|
||||
self._transport_controls = MediaController.TransportControls(session)
|
||||
|
||||
def getSessionToken(self) -> "SessionToken":
|
||||
return self._session.getSessionToken() if self._session else SessionToken()
|
||||
|
||||
def getTransportControls(self) -> TransportControls:
|
||||
return self._transport_controls
|
||||
|
||||
def getPlaybackState(self) -> Optional["PlaybackState"]:
|
||||
return self._session._playback_state if self._session else None
|
||||
|
||||
def getMetadata(self) -> Optional["MediaMetadata"]:
|
||||
return self._session._metadata if self._session else None
|
||||
|
||||
def getQueueTitle(self) -> Optional[str]:
|
||||
return self._session._queue_title if self._session else None
|
||||
|
||||
def getQueue(self) -> List["QueueItem"]:
|
||||
return self._session._queue if self._session else []
|
||||
|
||||
def getRepeatMode(self) -> int:
|
||||
return self._session._repeat_mode if self._session else PlaybackState.REPEAT_MODE_NONE
|
||||
|
||||
def getShuffleMode(self) -> int:
|
||||
return self._session._shuffle_mode if self._session else PlaybackState.SHUFFLE_MODE_NONE
|
||||
|
||||
def getRatingType(self) -> int:
|
||||
return self._session._rating_type if self._session else Rating.RATING_NONE
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._session._extras if self._session else None
|
||||
|
||||
def getFlags(self) -> int:
|
||||
return self._session._flags if self._session else 0
|
||||
|
||||
def getPackageName(self) -> str:
|
||||
return self._session.getCallerPackageName() if self._session else ""
|
||||
|
||||
def getSessionActivity(self) -> Any:
|
||||
return self._session._session_activity if self._session else None
|
||||
|
||||
def registerCallback(self, callback: Callback, handler: Any = None):
|
||||
self._callback = callback
|
||||
|
||||
def unregisterCallback(self, callback: Callback):
|
||||
self._callback = MediaController.Callback()
|
||||
|
||||
def sendCommand(self, command: str, extras: Any, cb: Any):
|
||||
if self._session and self._session._callback:
|
||||
self._session._callback.onCommand(command, extras, cb)
|
||||
|
||||
def adjustVolume(self, direction: int, flags: int):
|
||||
pass
|
||||
|
||||
def setVolumeTo(self, value: int, flags: int):
|
||||
pass
|
||||
|
||||
def isSessionReady(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PlaybackState
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PlaybackState(Stub):
|
||||
"""PlaybackState — 播放状态"""
|
||||
|
||||
STATE_NONE = 0
|
||||
STATE_STOPPED = 1
|
||||
STATE_PAUSED = 2
|
||||
STATE_PLAYING = 3
|
||||
STATE_FAST_FORWARDING = 4
|
||||
STATE_REWINDING = 5
|
||||
STATE_BUFFERING = 6
|
||||
STATE_ERROR = 7
|
||||
STATE_CONNECTING = 8
|
||||
STATE_SKIPPING_TO_PREVIOUS = 9
|
||||
STATE_SKIPPING_TO_NEXT = 10
|
||||
STATE_SKIPPING_TO_QUEUE_ITEM = 11
|
||||
|
||||
ACTION_PLAY = 1 << 0
|
||||
ACTION_PAUSE = 1 << 1
|
||||
ACTION_PLAY_PAUSE = 1 << 2
|
||||
ACTION_STOP = 1 << 3
|
||||
ACTION_SEEK_TO = 1 << 4
|
||||
ACTION_FAST_FORWARD = 1 << 5
|
||||
ACTION_REWIND = 1 << 6
|
||||
ACTION_SKIP_TO_NEXT = 1 << 7
|
||||
ACTION_SKIP_TO_PREVIOUS = 1 << 8
|
||||
ACTION_SET_RATING = 1 << 9
|
||||
ACTION_SET_REPEAT_MODE = 1 << 10
|
||||
ACTION_SET_SHUFFLE_MODE = 1 << 11
|
||||
ACTION_SET_CAPTIONING_ENABLED = 1 << 12
|
||||
ACTION_SKIP_TO_QUEUE_ITEM = 1 << 13
|
||||
ACTION_PLAY_FROM_MEDIA_ID = 1 << 14
|
||||
ACTION_PLAY_FROM_SEARCH = 1 << 15
|
||||
ACTION_PREPARE = 1 << 16
|
||||
ACTION_PREPARE_FROM_MEDIA_ID = 1 << 17
|
||||
ACTION_PREPARE_FROM_SEARCH = 1 << 18
|
||||
ACTION_PREPARE_FROM_URI = 1 << 19
|
||||
ACTION_PLAY_FROM_URI = 1 << 20
|
||||
|
||||
REPEAT_MODE_NONE = 0
|
||||
REPEAT_MODE_ONE = 1
|
||||
REPEAT_MODE_ALL = 2
|
||||
REPEAT_MODE_GROUP = 3
|
||||
|
||||
SHUFFLE_MODE_NONE = 0
|
||||
SHUFFLE_MODE_ALL = 1
|
||||
SHUFFLE_MODE_GROUP = 2
|
||||
|
||||
class Builder(Stub):
|
||||
"""PlaybackState.Builder — 播放状态构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._state = PlaybackState.STATE_NONE
|
||||
self._position = 0
|
||||
self._speed = 1.0
|
||||
self._actions = 0
|
||||
self._custom_actions: List[Any] = []
|
||||
self._active_queue_item_id = -1
|
||||
self._error_message: Optional[str] = None
|
||||
self._extras: Any = None
|
||||
self._buffered_position = 0
|
||||
|
||||
def setState(self, state: int, position: int, speed: float) -> "PlaybackState.Builder":
|
||||
self._state = state
|
||||
self._position = position
|
||||
self._speed = speed
|
||||
return self
|
||||
|
||||
def setActions(self, actions: int) -> "PlaybackState.Builder":
|
||||
self._actions = actions
|
||||
return self
|
||||
|
||||
def setCustomAction(self, action: str, name: str, icon: Any = None) -> "PlaybackState.Builder":
|
||||
self._custom_actions.append((action, name, icon))
|
||||
return self
|
||||
|
||||
def setActiveQueueItemId(self, id: int) -> "PlaybackState.Builder":
|
||||
self._active_queue_item_id = id
|
||||
return self
|
||||
|
||||
def setErrorMessage(self, error: str) -> "PlaybackState.Builder":
|
||||
self._error_message = error
|
||||
return self
|
||||
|
||||
def setExtras(self, extras: Any) -> "PlaybackState.Builder":
|
||||
self._extras = extras
|
||||
return self
|
||||
|
||||
def setBufferedPosition(self, bufferedPosition: int) -> "PlaybackState.Builder":
|
||||
self._buffered_position = bufferedPosition
|
||||
return self
|
||||
|
||||
def setPosition(self, position: int) -> "PlaybackState.Builder":
|
||||
self._position = position
|
||||
return self
|
||||
|
||||
def setSpeed(self, speed: float) -> "PlaybackState.Builder":
|
||||
self._speed = speed
|
||||
return self
|
||||
|
||||
def addCustomAction(self, action: "PlaybackState.CustomAction") -> "PlaybackState.Builder":
|
||||
self._custom_actions.append(action)
|
||||
return self
|
||||
|
||||
def build(self) -> "PlaybackState":
|
||||
ps = PlaybackState()
|
||||
ps._state = self._state
|
||||
ps._position = self._position
|
||||
ps._speed = self._speed
|
||||
ps._actions = self._actions
|
||||
ps._custom_actions = self._custom_actions
|
||||
ps._active_queue_item_id = self._active_queue_item_id
|
||||
ps._error_message = self._error_message
|
||||
ps._extras = self._extras
|
||||
ps._buffered_position = self._buffered_position
|
||||
return ps
|
||||
|
||||
class CustomAction(Stub):
|
||||
"""PlaybackState.CustomAction — 自定义操作"""
|
||||
|
||||
def __init__(self, action: str, name: str, icon: Any = None):
|
||||
super().__init__()
|
||||
self._action = action
|
||||
self._name = name
|
||||
self._icon = icon
|
||||
|
||||
def getAction(self) -> str:
|
||||
return self._action
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getIcon(self) -> Any:
|
||||
return self._icon
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._state = PlaybackState.STATE_NONE
|
||||
self._position = 0
|
||||
self._speed = 1.0
|
||||
self._actions = 0
|
||||
self._custom_actions: List[Any] = []
|
||||
self._active_queue_item_id = -1
|
||||
self._error_message: Optional[str] = None
|
||||
self._extras: Any = None
|
||||
self._buffered_position = 0
|
||||
self._last_position_update_time = int(time.time() * 1000)
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def getPosition(self) -> int:
|
||||
return self._position
|
||||
|
||||
def getPlaybackSpeed(self) -> float:
|
||||
return self._speed
|
||||
|
||||
def getActions(self) -> int:
|
||||
return self._actions
|
||||
|
||||
def getCustomActions(self) -> List[Any]:
|
||||
return self._custom_actions
|
||||
|
||||
def getActiveQueueItemId(self) -> int:
|
||||
return self._active_queue_item_id
|
||||
|
||||
def getErrorMessage(self) -> Optional[str]:
|
||||
return self._error_message
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
def getBufferedPosition(self) -> int:
|
||||
return self._buffered_position
|
||||
|
||||
def getLastPositionUpdateTime(self) -> int:
|
||||
return self._last_position_update_time
|
||||
|
||||
def toString(self) -> str:
|
||||
state_names = {
|
||||
PlaybackState.STATE_NONE: "NONE",
|
||||
PlaybackState.STATE_STOPPED: "STOPPED",
|
||||
PlaybackState.STATE_PAUSED: "PAUSED",
|
||||
PlaybackState.STATE_PLAYING: "PLAYING",
|
||||
}
|
||||
return f"PlaybackState(state={state_names.get(self._state, str(self._state))}," \
|
||||
f" pos={self._position})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MediaMetadata
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MediaMetadata(Stub):
|
||||
"""MediaMetadata — 媒体元数据"""
|
||||
|
||||
METADATA_KEY_ALBUM = "android.media.metadata.ALBUM"
|
||||
METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART"
|
||||
METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST"
|
||||
METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI"
|
||||
METADATA_KEY_ART = "android.media.metadata.ART"
|
||||
METADATA_KEY_ARTIST = "android.media.metadata.ARTIST"
|
||||
METADATA_KEY_ART_URI = "android.media.metadata.ART_URI"
|
||||
METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR"
|
||||
METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION"
|
||||
METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER"
|
||||
METADATA_KEY_DATE = "android.media.metadata.DATE"
|
||||
METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER"
|
||||
METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION"
|
||||
METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON"
|
||||
METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI"
|
||||
METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE"
|
||||
METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE"
|
||||
METADATA_KEY_DURATION = "android.media.metadata.DURATION"
|
||||
METADATA_KEY_GENRE = "android.media.metadata.GENRE"
|
||||
METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID"
|
||||
METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS"
|
||||
METADATA_KEY_RATING = "android.media.metadata.RATING"
|
||||
METADATA_KEY_TITLE = "android.media.metadata.TITLE"
|
||||
METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER"
|
||||
METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING"
|
||||
METADATA_KEY_WRITER = "android.media.metadata.WRITER"
|
||||
METADATA_KEY_YEAR = "android.media.metadata.YEAR"
|
||||
|
||||
class Builder(Stub):
|
||||
"""MediaMetadata.Builder — 元数据构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._data: Dict[str, Any] = {}
|
||||
|
||||
def putString(self, key: str, value: str) -> "MediaMetadata.Builder":
|
||||
self._data[key] = value
|
||||
return self
|
||||
|
||||
def putLong(self, key: str, value: int) -> "MediaMetadata.Builder":
|
||||
self._data[key] = value
|
||||
return self
|
||||
|
||||
def putBitmap(self, key: str, value: Any) -> "MediaMetadata.Builder":
|
||||
self._data[key] = value
|
||||
return self
|
||||
|
||||
def putText(self, key: str, value: str) -> "MediaMetadata.Builder":
|
||||
self._data[key] = value
|
||||
return self
|
||||
|
||||
def putRating(self, key: str, value: "Rating") -> "MediaMetadata.Builder":
|
||||
self._data[key] = value
|
||||
return self
|
||||
|
||||
def build(self) -> "MediaMetadata":
|
||||
mm = MediaMetadata()
|
||||
mm._data = dict(self._data)
|
||||
return mm
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._data: Dict[str, Any] = {}
|
||||
|
||||
def getString(self, key: str) -> Optional[str]:
|
||||
v = self._data.get(key)
|
||||
return str(v) if v is not None else None
|
||||
|
||||
def getLong(self, key: str) -> int:
|
||||
v = self._data.get(key, 0)
|
||||
try:
|
||||
return int(v)
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
def getBitmap(self, key: str) -> Any:
|
||||
return self._data.get(key)
|
||||
|
||||
def getText(self, key: str) -> Optional[str]:
|
||||
return self.getString(key)
|
||||
|
||||
def getDescription(self) -> "MediaDescription":
|
||||
from .media_browse import MediaDescription
|
||||
builder = MediaDescription.Builder()
|
||||
title = self.getString(MediaMetadata.METADATA_KEY_TITLE)
|
||||
if title:
|
||||
builder.setTitle(title)
|
||||
artist = self.getString(MediaMetadata.METADATA_KEY_ARTIST)
|
||||
if artist:
|
||||
builder.setSubtitle(artist)
|
||||
media_id = self.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)
|
||||
if media_id:
|
||||
builder.setMediaId(media_id)
|
||||
return builder.build()
|
||||
|
||||
def getRating(self, key: str) -> Optional["Rating"]:
|
||||
v = self._data.get(key)
|
||||
return v if isinstance(v, Rating) else None
|
||||
|
||||
def getBitmapDescription(self) -> Any:
|
||||
return None
|
||||
|
||||
def containsKey(self, key: str) -> bool:
|
||||
return key in self._data
|
||||
|
||||
def keySet(self) -> Set[str]:
|
||||
return set(self._data.keys())
|
||||
|
||||
def getMediaId(self) -> Optional[str]:
|
||||
return self.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)
|
||||
|
||||
def toString(self) -> str:
|
||||
title = self.getString(MediaMetadata.METADATA_KEY_TITLE)
|
||||
return f"MediaMetadata(title={title})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# QueueItem
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class QueueItem(Stub):
|
||||
"""QueueItem — 队列项"""
|
||||
|
||||
def __init__(self, description: "MediaDescription" = None, queueId: int = 0):
|
||||
super().__init__()
|
||||
self._description = description
|
||||
self._queue_id = queueId
|
||||
|
||||
def getDescription(self) -> "MediaDescription":
|
||||
return self._description
|
||||
|
||||
def getQueueId(self) -> int:
|
||||
return self._queue_id
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"QueueItem(id={self._queue_id}, desc={self._description})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Rating
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Rating(Stub):
|
||||
"""Rating — 评分"""
|
||||
|
||||
RATING_NONE = 0
|
||||
RATING_HEART = 1
|
||||
RATING_THUMB_UP_DOWN = 2
|
||||
RATING_3_STARS = 3
|
||||
RATING_4_STARS = 4
|
||||
RATING_5_STARS = 5
|
||||
RATING_PERCENTAGE = 6
|
||||
|
||||
def __init__(self, ratingStyle: int = RATING_NONE):
|
||||
super().__init__()
|
||||
self._rating_style = ratingStyle
|
||||
self._rated = False
|
||||
self._heart = False
|
||||
self._thumb_up = False
|
||||
self._star_rating = 0.0
|
||||
self._percent_rating = 0.0
|
||||
|
||||
@staticmethod
|
||||
def newHeartRating(hasHeart: bool) -> "Rating":
|
||||
r = Rating(Rating.RATING_HEART)
|
||||
r._rated = True
|
||||
r._heart = hasHeart
|
||||
return r
|
||||
|
||||
@staticmethod
|
||||
def newThumbRating(thumbUp: bool) -> "Rating":
|
||||
r = Rating(Rating.RATING_THUMB_UP_DOWN)
|
||||
r._rated = True
|
||||
r._thumb_up = thumbUp
|
||||
return r
|
||||
|
||||
@staticmethod
|
||||
def newStarRating(starRatingStyle: int, starRating: float) -> "Rating":
|
||||
r = Rating(starRatingStyle)
|
||||
r._rated = True
|
||||
r._star_rating = starRating
|
||||
return r
|
||||
|
||||
@staticmethod
|
||||
def newPercentageRating(percent: float) -> "Rating":
|
||||
r = Rating(Rating.RATING_PERCENTAGE)
|
||||
r._rated = True
|
||||
r._percent_rating = percent
|
||||
return r
|
||||
|
||||
@staticmethod
|
||||
def newUnratedRating(ratingStyle: int) -> "Rating":
|
||||
return Rating(ratingStyle)
|
||||
|
||||
def isRated(self) -> bool:
|
||||
return self._rated
|
||||
|
||||
def getRatingStyle(self) -> int:
|
||||
return self._rating_style
|
||||
|
||||
def hasHeart(self) -> bool:
|
||||
return self._heart
|
||||
|
||||
def isThumbUp(self) -> bool:
|
||||
return self._thumb_up
|
||||
|
||||
def getStarRating(self) -> float:
|
||||
return self._star_rating
|
||||
|
||||
def getPercentRating(self) -> float:
|
||||
return self._percent_rating
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Rating(style={self._rating_style}, rated={self._rated})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SessionToken
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SessionToken(Stub):
|
||||
"""SessionToken — 会话令牌"""
|
||||
|
||||
TYPE_SESSION = 0
|
||||
|
||||
def __init__(self, type_: int = TYPE_SESSION):
|
||||
super().__init__()
|
||||
self._type = type_
|
||||
self._binder = None
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._type
|
||||
|
||||
def getSessionBinder(self) -> Any:
|
||||
return self._binder
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SessionToken(type={self._type})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"MediaSession",
|
||||
"MediaController",
|
||||
"PlaybackState",
|
||||
"MediaMetadata",
|
||||
"QueueItem",
|
||||
"Rating",
|
||||
"SessionToken",
|
||||
]
|
||||
@@ -0,0 +1,359 @@
|
||||
"""
|
||||
Android media.tv 组件 — TvInputManager, TvInputInfo, TvInputService 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TvInputManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TvInputManager(Stub):
|
||||
"""TvInputManager — 电视输入管理器"""
|
||||
|
||||
ACTION_VIEW_RECORDING_SCHEDULES = "android.tv.action.VIEW_RECORDING_SCHEDULES"
|
||||
ACTION_WATCH_NEXT = "android.tv.action.WATCH_NEXT"
|
||||
|
||||
TIME_SHIFT_STATUS_AVAILABLE = 0
|
||||
TIME_SHIFT_STATUS_UNAVAILABLE = 1
|
||||
TIME_SHIFT_STATUS_UNKNOWN = 2
|
||||
TIME_SHIFT_STATUS_UNSUPPORTED = 3
|
||||
|
||||
VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 8
|
||||
VIDEO_UNAVAILABLE_REASON_BUFFERING = 3
|
||||
VIDEO_UNAVAILABLE_REASON_CAS = 7
|
||||
VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 5
|
||||
VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 6
|
||||
VIDEO_UNAVAILABLE_REASON_PREEMPTED = 4
|
||||
VIDEO_UNAVAILABLE_REASON_TUNING = 1
|
||||
VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0
|
||||
VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# TvInputCallback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class TvInputCallback(Stub):
|
||||
"""TvInputManager.TvInputCallback — 电视输入回调"""
|
||||
|
||||
def onInputAdded(self, inputId: str):
|
||||
pass
|
||||
|
||||
def onInputRemoved(self, inputId: str):
|
||||
pass
|
||||
|
||||
def onInputChanged(self, inputId: str):
|
||||
pass
|
||||
|
||||
def onInputStateChanged(self, inputId: str, state: int):
|
||||
pass
|
||||
|
||||
def onTvInputInfoUpdated(self, inputInfo: "TvInputInfo"):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._inputs: Dict[str, TvInputInfo] = {}
|
||||
self._callbacks: Dict[int, TvInputManager.TvInputCallback] = {}
|
||||
|
||||
def getTvInputList(self) -> List["TvInputInfo"]:
|
||||
return list(self._inputs.values())
|
||||
|
||||
def getTvInputInfo(self, inputId: str) -> Optional["TvInputInfo"]:
|
||||
return self._inputs.get(inputId)
|
||||
|
||||
def getCurrentTvInputInfo(self) -> Optional["TvInputInfo"]:
|
||||
for inp in self._inputs.values():
|
||||
return inp
|
||||
return None
|
||||
|
||||
def updateTvInputInfo(self, inputInfo: "TvInputInfo"):
|
||||
if inputInfo:
|
||||
self._inputs[inputInfo.getId()] = inputInfo
|
||||
|
||||
def registerCallback(self, callback: TvInputCallback, handler: Any = None):
|
||||
lid = id(callback)
|
||||
self._callbacks[lid] = callback
|
||||
|
||||
def unregisterCallback(self, callback: TvInputCallback):
|
||||
lid = id(callback)
|
||||
self._callbacks.pop(lid, None)
|
||||
|
||||
def createRecordingSession(self, recordingService: str, inputId: str):
|
||||
raise AndroidOnlyError("TvInputManager.createRecordingSession() is not available on desktop")
|
||||
|
||||
def sendTvInputNotifyIntent(self, intent: Any, inputId: str):
|
||||
pass
|
||||
|
||||
def isParentalControlsEnabled(self) -> bool:
|
||||
return False
|
||||
|
||||
def isRatingBlocked(self, rating: str) -> bool:
|
||||
return False
|
||||
|
||||
def getBlockedRatings(self) -> List[str]:
|
||||
return []
|
||||
|
||||
def getAvailableTvInputs(self) -> List[str]:
|
||||
return list(self._inputs.keys())
|
||||
|
||||
def getTvContentRatingSystems(self) -> List[str]:
|
||||
return ["US_TV", "US_MOVIE"]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TvInputInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TvInputInfo(Stub):
|
||||
"""TvInputInfo — 电视输入信息"""
|
||||
|
||||
TYPE_COMPONENT = 1005
|
||||
TYPE_COMPOSITE = 1004
|
||||
TYPE_HDMI = 1007
|
||||
TYPE_OTHER = 1001
|
||||
TYPE_SCART = 1003
|
||||
TYPE_SVIDEO = 1006
|
||||
TYPE_TUNER = 0
|
||||
TYPE_DISPLAY_LINK = 1008
|
||||
TYPE_VGA = 1002
|
||||
|
||||
def __init__(self, id: str = "", serviceInfo: Any = None,
|
||||
parentId: str = "", type_: int = TYPE_TUNER,
|
||||
hdmiDeviceInfo: Any = None,
|
||||
isHidden: bool = False, canRecord: bool = False,
|
||||
canPauseRecording: bool = False):
|
||||
super().__init__()
|
||||
self._id = id
|
||||
self._service_info = serviceInfo
|
||||
self._parent_id = parentId
|
||||
self._type = type_
|
||||
self._hdmi_device_info = hdmiDeviceInfo
|
||||
self._is_hidden = isHidden
|
||||
self._can_record = canRecord
|
||||
self._can_pause_recording = canPauseRecording
|
||||
|
||||
def getId(self) -> str:
|
||||
return self._id
|
||||
|
||||
def getServiceInfo(self) -> Any:
|
||||
return self._service_info
|
||||
|
||||
def getParentId(self) -> str:
|
||||
return self._parent_id
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._type
|
||||
|
||||
def getHdmiDeviceInfo(self) -> Any:
|
||||
return self._hdmi_device_info
|
||||
|
||||
def loadIcon(self, context: Any) -> Any:
|
||||
return None
|
||||
|
||||
def loadLogo(self, context: Any) -> Any:
|
||||
return None
|
||||
|
||||
def createIntentForSetupScreen(self) -> Any:
|
||||
return None
|
||||
|
||||
def isHidden(self) -> bool:
|
||||
return self._is_hidden
|
||||
|
||||
def canRecord(self) -> bool:
|
||||
return self._can_record
|
||||
|
||||
def canPauseRecording(self) -> bool:
|
||||
return self._can_pause_recording
|
||||
|
||||
def toString(self) -> str:
|
||||
type_names = {
|
||||
TvInputInfo.TYPE_TUNER: "TUNER",
|
||||
TvInputInfo.TYPE_HDMI: "HDMI",
|
||||
TvInputInfo.TYPE_COMPOSITE: "COMPOSITE",
|
||||
TvInputInfo.TYPE_COMPONENT: "COMPONENT",
|
||||
}
|
||||
return f"TvInputInfo(id={self._id}, type={type_names.get(self._type, str(self._type))})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TvInputService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TvInputService(Stub):
|
||||
"""TvInputService — 电视输入服务"""
|
||||
|
||||
RECORDING_ERROR_RECORDING_LOST = 0
|
||||
RECORDING_ERROR_TIMEOUT = 1
|
||||
RECORDING_ERROR_OTHER = 2
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Session
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Session(Stub):
|
||||
"""TvInputService.Session — 电视会话"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._main = False
|
||||
self._stream_volume = 1.0
|
||||
self._caption_enabled = False
|
||||
|
||||
def onSetSurface(self, surface: Any) -> bool:
|
||||
return True
|
||||
|
||||
def onSurfaceChanged(self, format: int, width: int, height: int):
|
||||
pass
|
||||
|
||||
def onSurfaceReleased(self):
|
||||
pass
|
||||
|
||||
def onAppPrivateCommand(self, action: str, data: Any):
|
||||
pass
|
||||
|
||||
def onCreateOverlayView(self) -> Any:
|
||||
return None
|
||||
|
||||
def onRelayoutContent(self):
|
||||
pass
|
||||
|
||||
def onSetCaptionEnabled(self, enabled: bool):
|
||||
self._caption_enabled = enabled
|
||||
|
||||
def onSetStreamVolume(self, volume: float):
|
||||
self._stream_volume = volume
|
||||
|
||||
def onTimeShiftSetCurrentPositionRequest(self, positionMs: int):
|
||||
pass
|
||||
|
||||
def notifyTimeShiftStatusChanged(self, status: int):
|
||||
pass
|
||||
|
||||
def scheduleOverlayView(self, view: Any):
|
||||
pass
|
||||
|
||||
def timeShiftPlay(self, uri: str):
|
||||
pass
|
||||
|
||||
def timeShiftPause(self):
|
||||
pass
|
||||
|
||||
def timeShiftResume(self):
|
||||
pass
|
||||
|
||||
def timeShiftSeekTo(self, positionMs: int):
|
||||
pass
|
||||
|
||||
def timeShiftSetPlaybackParams(self, params: Any):
|
||||
pass
|
||||
|
||||
def timeShiftEnablePositionTracking(self, enable: bool):
|
||||
pass
|
||||
|
||||
def notifyVideoAvailable(self):
|
||||
pass
|
||||
|
||||
def notifyVideoUnavailable(self, reason: int):
|
||||
pass
|
||||
|
||||
def notifySessionEvent(self, eventType: str, eventArgs: Any):
|
||||
pass
|
||||
|
||||
def notifyTracksChanged(self, tracks: List[Any]):
|
||||
pass
|
||||
|
||||
def notifyTrackSelected(self, type_: str, trackId: str):
|
||||
pass
|
||||
|
||||
def notifyContentAllowed(self):
|
||||
pass
|
||||
|
||||
def notifyContentBlocked(self, rating: str):
|
||||
pass
|
||||
|
||||
def setMain(self, main: bool):
|
||||
self._main = main
|
||||
|
||||
def setStreamVolume(self, volume: float):
|
||||
self._stream_volume = volume
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._sessions: List[TvInputService.Session] = []
|
||||
|
||||
def onCreateSession(self, inputId: str) -> Session:
|
||||
session = TvInputService.Session()
|
||||
self._sessions.append(session)
|
||||
return session
|
||||
|
||||
def onSetupFinished(self, inputId: str):
|
||||
pass
|
||||
|
||||
def sessionCreated(self, session: Session):
|
||||
pass
|
||||
|
||||
def notifyHardwareFailed(self, deviceId: int):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TvRecordingClient / TvRecordingService (stubs)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TvRecordingClient(Stub):
|
||||
"""TvRecordingClient — 电视录制客户端"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def startRecording(self, uri: str):
|
||||
pass
|
||||
|
||||
def stopRecording(self):
|
||||
pass
|
||||
|
||||
def pauseRecording(self):
|
||||
pass
|
||||
|
||||
def resumeRecording(self):
|
||||
pass
|
||||
|
||||
def release(self):
|
||||
pass
|
||||
|
||||
|
||||
class TvRecordingService(Stub):
|
||||
"""TvRecordingService — 电视录制服务"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onStartRecording(self, inputId: str, sessionId: str):
|
||||
pass
|
||||
|
||||
def onStopRecording(self, sessionId: str):
|
||||
pass
|
||||
|
||||
def onPauseRecording(self, sessionId: str):
|
||||
pass
|
||||
|
||||
def onResumeRecording(self, sessionId: str):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"TvInputManager",
|
||||
"TvInputInfo",
|
||||
"TvInputService",
|
||||
"TvRecordingClient",
|
||||
"TvRecordingService",
|
||||
]
|
||||
@@ -0,0 +1,476 @@
|
||||
"""
|
||||
Android Media Transfer Protocol API — MtpDevice, MtpStorageInfo, MtpObjectInfo, MtpConstants
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os as _os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable, BinaryIO
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"MtpDevice",
|
||||
"MtpStorageInfo",
|
||||
"MtpObjectInfo",
|
||||
"MtpConstants",
|
||||
"MtpPropertyGroup",
|
||||
"MtpDeviceInfo",
|
||||
"MtpStorage",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MtpConstants — 50+ 格式常量
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MtpConstants(Stub):
|
||||
"""MTP 协议常量"""
|
||||
|
||||
# --- 格式常量 ---
|
||||
FORMAT_UNDEFINED = 0x3000
|
||||
FORMAT_ASSOCIATION = 0x3001
|
||||
FORMAT_SCRIPT = 0x3002
|
||||
FORMAT_EXECUTABLE = 0x3003
|
||||
FORMAT_TEXT = 0x3004
|
||||
FORMAT_HTML = 0x3005
|
||||
FORMAT_DPOF = 0x3006
|
||||
FORMAT_AIFF = 0x3007
|
||||
FORMAT_WAV = 0x3008
|
||||
FORMAT_MP3 = 0x3009
|
||||
FORMAT_AVI = 0x300A
|
||||
FORMAT_MPEG = 0x300B
|
||||
FORMAT_ASF = 0x300C
|
||||
FORMAT_DEF = 0x300D
|
||||
FORMAT_EXIF_JPEG = 0x3801
|
||||
FORMAT_TIFF_EP = 0x3802
|
||||
FORMAT_FLASHPIX = 0x3803
|
||||
FORMAT_BMP = 0x3804
|
||||
FORMAT_CIFF = 0x3805
|
||||
FORMAT_GIF = 0x3807
|
||||
FORMAT_JFIF = 0x3808
|
||||
FORMAT_PCD = 0x3809
|
||||
FORMAT_PICT = 0x380A
|
||||
FORMAT_PNG = 0x380B
|
||||
FORMAT_TIFF = 0x380D
|
||||
FORMAT_TIFF_IT = 0x380E
|
||||
FORMAT_JP2 = 0x380F
|
||||
FORMAT_JPX = 0x3810
|
||||
FORMAT_XML = 0x3811
|
||||
FORMAT_FIRMWARE = 0x3802
|
||||
FORMAT_WMA = 0xB901
|
||||
FORMAT_WMV = 0xB980
|
||||
FORMAT_WPL = 0xBA05
|
||||
FORMAT_FLAC = 0xB906
|
||||
FORMAT_OGG = 0xB902
|
||||
FORMAT_AAC = 0xB903
|
||||
FORMAT_MP4_CONTAINER = 0xB904
|
||||
FORMAT_MP2 = 0xB905
|
||||
FORMAT_3GP = 0xB907
|
||||
FORMAT_3GP_CONTAINER = 0xB908
|
||||
FORMAT_AVC = 0xB909
|
||||
FORMAT_HEVC = 0xB90A
|
||||
FORMAT_DNG = 0xB90B
|
||||
FORMAT_ARW = 0xB90C
|
||||
FORMAT_CR2 = 0xB90D
|
||||
FORMAT_RAF = 0xB90E
|
||||
FORMAT_NEF = 0xB90F
|
||||
FORMAT_ORF = 0xB910
|
||||
FORMAT_RW2 = 0xB911
|
||||
FORMAT_SRW = 0xB912
|
||||
|
||||
# --- 保护状态 ---
|
||||
PROTECTION_STATUS_NONE = 0
|
||||
PROTECTION_STATUS_READ_ONLY = 0x8001
|
||||
PROTECTION_STATUS_PROTECTED = 0x8002
|
||||
|
||||
# --- 关联类型 ---
|
||||
ASSOCIATION_TYPE_UNDEFINED = 0x0000
|
||||
ASSOCIATION_TYPE_GENERIC_FOLDER = 0x0001
|
||||
|
||||
# --- 属性格式常量 ---
|
||||
PROP_FORMAT_UNDEFINED = 0x0000
|
||||
PROP_FORMAT_STRING = 0xFFFF
|
||||
PROP_FORMAT_UINT8 = 0x0001
|
||||
PROP_FORMAT_UINT16 = 0x0002
|
||||
PROP_FORMAT_UINT32 = 0x0004
|
||||
PROP_FORMAT_UINT64 = 0x0008
|
||||
PROP_FORMAT_INT8 = 0x0009
|
||||
PROP_FORMAT_INT16 = 0x000A
|
||||
PROP_FORMAT_INT32 = 0x000B
|
||||
PROP_FORMAT_INT64 = 0x000C
|
||||
PROP_FORMAT_BOOL = 0x4001
|
||||
PROP_FORMAT_DATE = 0x4002
|
||||
|
||||
# --- 操作码 ---
|
||||
OPERATION_GET_DEVICE_INFO = 0x1001
|
||||
OPERATION_OPEN_SESSION = 0x1002
|
||||
OPERATION_CLOSE_SESSION = 0x1003
|
||||
OPERATION_GET_STORAGE_IDS = 0x1004
|
||||
OPERATION_GET_STORAGE_INFO = 0x1005
|
||||
OPERATION_GET_OBJECT_PROP_VALUE = 0x9802
|
||||
OPERATION_SET_OBJECT_PROP_VALUE = 0x9803
|
||||
OPERATION_GET_OBJECT_INFO = 0x1007
|
||||
OPERATION_GET_OBJECT = 0x1009
|
||||
OPERATION_GET_THUMB = 0x100A
|
||||
OPERATION_DELETE_OBJECT = 0x100B
|
||||
OPERATION_SEND_OBJECT_INFO = 0x100C
|
||||
OPERATION_SEND_OBJECT = 0x100D
|
||||
OPERATION_GET_OBJECT_REFERENCES = 0x1010
|
||||
OPERATION_SET_OBJECT_REFERENCES = 0x1011
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MtpDevice
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MtpDevice(Stub):
|
||||
"""MtpDevice — MTP 设备 (桌面存根)"""
|
||||
|
||||
def __init__(self, device: Any = None):
|
||||
super().__init__()
|
||||
self._device = device
|
||||
self._opened = False
|
||||
|
||||
def open(self) -> bool:
|
||||
"""打开 MTP 设备"""
|
||||
raise AndroidOnlyError("MtpDevice.open() 仅在 Android 设备上可用")
|
||||
|
||||
def close(self):
|
||||
"""关闭 MTP 设备"""
|
||||
self._opened = False
|
||||
|
||||
def getDeviceInfo(self) -> "MtpDeviceInfo":
|
||||
"""获取设备信息"""
|
||||
return MtpDeviceInfo()
|
||||
|
||||
def getDeviceName(self) -> str:
|
||||
"""获取设备名称"""
|
||||
return "MTP Device"
|
||||
|
||||
def getObjectHandles(
|
||||
self, storageId: int, format: int, objectHandle: int
|
||||
) -> List[int]:
|
||||
"""获取对象句柄列表"""
|
||||
return []
|
||||
|
||||
def getObjectInfo(self, objectHandle: int) -> Optional["MtpObjectInfo"]:
|
||||
"""获取对象信息"""
|
||||
return None
|
||||
|
||||
def getObject(self, objectHandle: int, dataSize: int) -> Optional[bytes]:
|
||||
"""获取对象数据"""
|
||||
return None
|
||||
|
||||
def getThumbnail(self, objectHandle: int) -> Optional[bytes]:
|
||||
"""获取缩略图"""
|
||||
return None
|
||||
|
||||
def getStorageIds(self) -> List[int]:
|
||||
"""获取存储 ID 列表"""
|
||||
return [0x00010001]
|
||||
|
||||
def getStorageInfo(self, storageId: int) -> "MtpStorageInfo":
|
||||
"""获取存储信息"""
|
||||
return MtpStorageInfo()
|
||||
|
||||
def importFile(self, objectHandle: int, destPath: str) -> bool:
|
||||
"""导入文件"""
|
||||
raise AndroidOnlyError("MtpDevice.importFile() 仅在 Android 设备上可用")
|
||||
|
||||
def deleteObject(self, objectHandle: int) -> bool:
|
||||
"""删除对象"""
|
||||
raise AndroidOnlyError("MtpDevice.deleteObject() 仅在 Android 设备上可用")
|
||||
|
||||
def getParent(self, objectHandle: int) -> int:
|
||||
"""获取父对象句柄"""
|
||||
return 0xFFFFFFFF
|
||||
|
||||
def getObjectReferences(self, objectHandle: int) -> List[int]:
|
||||
"""获取对象引用列表"""
|
||||
return []
|
||||
|
||||
def setObjectReferences(self, objectHandle: int, references: List[int]) -> bool:
|
||||
"""设置对象引用"""
|
||||
raise AndroidOnlyError("MtpDevice.setObjectReferences() 仅在 Android 设备上可用")
|
||||
|
||||
def sendObject(self, objectHandle: int, data: bytes) -> bool:
|
||||
"""发送对象"""
|
||||
raise AndroidOnlyError("MtpDevice.sendObject() 仅在 Android 设备上可用")
|
||||
|
||||
def sendObjectInfo(
|
||||
self, storageId: int, parent: int, info: "MtpObjectInfo"
|
||||
) -> int:
|
||||
"""发送对象信息"""
|
||||
raise AndroidOnlyError("MtpDevice.sendObjectInfo() 仅在 Android 设备上可用")
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MtpDevice(name={self.getDeviceName()})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MtpStorageInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MtpStorageInfo(Stub):
|
||||
"""MtpStorageInfo — MTP 存储信息"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._storage_id = 0x00010001
|
||||
self._max_capacity = 32 * 1024 * 1024 * 1024 # 32GB
|
||||
self._free_space = 16 * 1024 * 1024 * 1024 # 16GB
|
||||
self._description = "Internal Storage"
|
||||
self._volume_identifier = ""
|
||||
|
||||
def getStorageId(self) -> int:
|
||||
return self._storage_id
|
||||
|
||||
def getMaxCapacity(self) -> int:
|
||||
return self._max_capacity
|
||||
|
||||
def getFreeSpace(self) -> int:
|
||||
return self._free_space
|
||||
|
||||
def getDescription(self) -> str:
|
||||
return self._description
|
||||
|
||||
def getVolumeIdentifier(self) -> str:
|
||||
return self._volume_identifier
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"MtpStorageInfo(id={self._storage_id}, "
|
||||
f"free={self._free_space}B)")
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, MtpStorageInfo):
|
||||
return False
|
||||
return self._storage_id == other._storage_id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._storage_id & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MtpObjectInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MtpObjectInfo(Stub):
|
||||
"""MtpObjectInfo — MTP 对象信息"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._object_handle = 0
|
||||
self._storage_id = 0
|
||||
self._format = MtpConstants.FORMAT_UNDEFINED
|
||||
self._protection_status = MtpConstants.PROTECTION_STATUS_NONE
|
||||
self._compressed_size = 0
|
||||
self._thumb_format = MtpConstants.FORMAT_UNDEFINED
|
||||
self._thumb_compressed_size = 0
|
||||
self._thumb_pix_width = 0
|
||||
self._thumb_pix_height = 0
|
||||
self._image_pix_width = 0
|
||||
self._image_pix_height = 0
|
||||
self._image_pix_depth = 0
|
||||
self._parent = 0
|
||||
self._association_desc = 0
|
||||
self._association_type = 0
|
||||
self._sequence_number = 0
|
||||
self._name = ""
|
||||
self._date_created = 0
|
||||
self._date_modified = 0
|
||||
self._keywords = ""
|
||||
|
||||
def getObjectHandle(self) -> int:
|
||||
return self._object_handle
|
||||
|
||||
def getStorageId(self) -> int:
|
||||
return self._storage_id
|
||||
|
||||
def getFormat(self) -> int:
|
||||
return self._format
|
||||
|
||||
def getProtectionStatus(self) -> int:
|
||||
return self._protection_status
|
||||
|
||||
def getCompressedSize(self) -> int:
|
||||
return self._compressed_size
|
||||
|
||||
def getThumbFormat(self) -> int:
|
||||
return self._thumb_format
|
||||
|
||||
def getThumbCompressedSize(self) -> int:
|
||||
return self._thumb_compressed_size
|
||||
|
||||
def getThumbPixWidth(self) -> int:
|
||||
return self._thumb_pix_width
|
||||
|
||||
def getThumbPixHeight(self) -> int:
|
||||
return self._thumb_pix_height
|
||||
|
||||
def getImagePixWidth(self) -> int:
|
||||
return self._image_pix_width
|
||||
|
||||
def getImagePixHeight(self) -> int:
|
||||
return self._image_pix_height
|
||||
|
||||
def getImagePixDepth(self) -> int:
|
||||
return self._image_pix_depth
|
||||
|
||||
def getParent(self) -> int:
|
||||
return self._parent
|
||||
|
||||
def getAssociationDesc(self) -> int:
|
||||
return self._association_desc
|
||||
|
||||
def getAssociationType(self) -> int:
|
||||
return self._association_type
|
||||
|
||||
def getSequenceNumber(self) -> int:
|
||||
return self._sequence_number
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getDateCreated(self) -> int:
|
||||
return self._date_created
|
||||
|
||||
def getDateModified(self) -> int:
|
||||
return self._date_modified
|
||||
|
||||
def getKeywords(self) -> str:
|
||||
return self._keywords
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MtpObjectInfo(name={self._name}, handle={self._object_handle})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, MtpObjectInfo):
|
||||
return False
|
||||
return self._object_handle == other._object_handle
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._object_handle & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MtpPropertyGroup
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MtpPropertyGroup(Stub):
|
||||
"""MtpPropertyGroup — MTP 属性组"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._properties: Dict[int, Any] = {}
|
||||
|
||||
def getPropertyValue(self, propertyCode: int) -> Optional[Any]:
|
||||
return self._properties.get(propertyCode)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MtpPropertyGroup(properties={len(self._properties)})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MtpDeviceInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MtpDeviceInfo(Stub):
|
||||
"""MtpDeviceInfo — MTP 设备信息"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._manufacturer = "Unknown"
|
||||
self._model = "MTP Device"
|
||||
self._device_version = "1.0"
|
||||
self._serial_number = "00000000"
|
||||
self._operations_supported: List[int] = []
|
||||
self._events_supported: List[int] = []
|
||||
self._device_properties_supported: List[int] = []
|
||||
|
||||
def getManufacturer(self) -> str:
|
||||
return self._manufacturer
|
||||
|
||||
def getModel(self) -> str:
|
||||
return self._model
|
||||
|
||||
def getDeviceVersion(self) -> str:
|
||||
return self._device_version
|
||||
|
||||
def getSerialNumber(self) -> str:
|
||||
return self._serial_number
|
||||
|
||||
def getOperationsSupported(self) -> List[int]:
|
||||
return list(self._operations_supported)
|
||||
|
||||
def getEventsSupported(self) -> List[int]:
|
||||
return list(self._events_supported)
|
||||
|
||||
def getDevicePropertySupported(self) -> List[int]:
|
||||
return list(self._device_properties_supported)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MtpDeviceInfo(manufacturer={self._manufacturer}, model={self._model})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, MtpDeviceInfo):
|
||||
return False
|
||||
return self._serial_number == other._serial_number
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._serial_number) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MtpStorage
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class MtpStorage(Stub):
|
||||
"""MtpStorage — MTP 存储"""
|
||||
|
||||
def __init__(self, storageId: int = 0x00010001, path: str = ""):
|
||||
super().__init__()
|
||||
self._storage_id = storageId
|
||||
self._path = path
|
||||
self._description = "Storage"
|
||||
|
||||
def getStorageId(self) -> int:
|
||||
return self._storage_id
|
||||
|
||||
def getPath(self) -> str:
|
||||
return self._path
|
||||
|
||||
def getDescription(self) -> str:
|
||||
return self._description
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MtpStorage(id={self._storage_id}, path={self._path})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, MtpStorage):
|
||||
return False
|
||||
return self._storage_id == other._storage_id
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._storage_id & 0x7FFFFFFF
|
||||
@@ -0,0 +1,464 @@
|
||||
"""
|
||||
Android OpenGL 组件 — GLSurfaceView, GLU, GLUtils, EGL14 等
|
||||
方法签名完全对齐 Android SDK (android.opengl 包)
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Any, Callable, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# EGL 常量
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class EGL14(Stub):
|
||||
"""EGL14 — EGL 1.4 API 常量 (桌面仅提供常量)"""
|
||||
|
||||
# EGL 布尔值
|
||||
EGL_TRUE = 1
|
||||
EGL_FALSE = 0
|
||||
|
||||
# EGL 错误码
|
||||
EGL_SUCCESS = 0x3000
|
||||
EGL_NOT_INITIALIZED = 0x3001
|
||||
EGL_BAD_ACCESS = 0x3002
|
||||
EGL_BAD_ALLOC = 0x3003
|
||||
EGL_BAD_ATTRIBUTE = 0x3004
|
||||
EGL_BAD_CONFIG = 0x3005
|
||||
EGL_BAD_CONTEXT = 0x3006
|
||||
EGL_BAD_CURRENT_SURFACE = 0x3007
|
||||
EGL_BAD_DISPLAY = 0x3008
|
||||
EGL_BAD_MATCH = 0x3009
|
||||
EGL_BAD_NATIVE_PIXMAP = 0x300A
|
||||
EGL_BAD_NATIVE_WINDOW = 0x300B
|
||||
EGL_BAD_PARAMETER = 0x300C
|
||||
EGL_BAD_SURFACE = 0x300D
|
||||
EGL_CONTEXT_LOST = 0x300E
|
||||
|
||||
# EGL 配置属性
|
||||
EGL_BUFFER_SIZE = 0x3020
|
||||
EGL_ALPHA_SIZE = 0x3021
|
||||
EGL_BLUE_SIZE = 0x3022
|
||||
EGL_GREEN_SIZE = 0x3023
|
||||
EGL_RED_SIZE = 0x3024
|
||||
EGL_DEPTH_SIZE = 0x3025
|
||||
EGL_STENCIL_SIZE = 0x3026
|
||||
EGL_CONFIG_CAVEAT = 0x3027
|
||||
EGL_CONFIG_ID = 0x3028
|
||||
EGL_LEVEL = 0x3029
|
||||
EGL_MAX_PBUFFER_HEIGHT = 0x302A
|
||||
EGL_MAX_PBUFFER_PIXELS = 0x302B
|
||||
EGL_MAX_PBUFFER_WIDTH = 0x302C
|
||||
EGL_NATIVE_RENDERABLE = 0x302D
|
||||
EGL_NATIVE_VISUAL_ID = 0x302E
|
||||
EGL_NATIVE_VISUAL_TYPE = 0x302F
|
||||
EGL_PRESERVED_RESOURCES = 0x3030
|
||||
EGL_SAMPLES = 0x3031
|
||||
EGL_SAMPLE_BUFFERS = 0x3032
|
||||
EGL_SURFACE_TYPE = 0x3033
|
||||
EGL_TRANSPARENT_TYPE = 0x3034
|
||||
EGL_TRANSPARENT_BLUE_VALUE = 0x3035
|
||||
EGL_TRANSPARENT_GREEN_VALUE = 0x3036
|
||||
EGL_TRANSPARENT_RED_VALUE = 0x3037
|
||||
EGL_NONE = 0x3038
|
||||
EGL_BIND_TO_TEXTURE_RGB = 0x3039
|
||||
EGL_BIND_TO_TEXTURE_RGBA = 0x303A
|
||||
EGL_MIN_SWAP_INTERVAL = 0x303B
|
||||
EGL_MAX_SWAP_INTERVAL = 0x303C
|
||||
|
||||
# EGL 表面属性
|
||||
EGL_WIDTH = 0x3057
|
||||
EGL_HEIGHT = 0x3056
|
||||
EGL_LARGEST_PBUFFER = 0x3058
|
||||
EGL_TEXTURE_FORMAT = 0x3080
|
||||
EGL_TEXTURE_TARGET = 0x3081
|
||||
EGL_MIPMAP_TEXTURE = 0x3082
|
||||
EGL_MIPMAP_LEVEL = 0x3083
|
||||
|
||||
# EGL 渲染上下文属性
|
||||
EGL_CONTEXT_CLIENT_TYPE = 0x3097
|
||||
EGL_CONTEXT_CLIENT_VERSION = 0x3098
|
||||
|
||||
# EGL 显示
|
||||
EGL_DEFAULT_DISPLAY = 0
|
||||
|
||||
# EGL 版本
|
||||
EGL_VERSION_1_4 = 1
|
||||
|
||||
# OpenGL ES 位标记
|
||||
EGL_OPENGL_ES_BIT = 0x0001
|
||||
EGL_OPENGL_ES2_BIT = 0x0004
|
||||
EGL_OPENGL_ES3_BIT = 0x0040
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def eglGetDisplay(display_id: Any) -> "EGLDisplay":
|
||||
"""获取 EGL 显示 (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglGetDisplay() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglInitialize(display: "EGLDisplay", major_minor: Optional[List[int]] = None) -> bool:
|
||||
"""初始化 EGL (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglInitialize() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglChooseConfig(display: "EGLDisplay", attrib_list: List[int],
|
||||
configs: List[Any], config_size: int,
|
||||
num_config: List[int]) -> bool:
|
||||
"""选择 EGL 配置 (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglChooseConfig() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglCreateContext(display: "EGLDisplay", config: "EGLConfig",
|
||||
share_context: "EGLContext",
|
||||
attrib_list: List[int]) -> "EGLContext":
|
||||
"""创建 EGL 上下文 (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglCreateContext() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglCreateWindowSurface(display: "EGLDisplay", config: "EGLConfig",
|
||||
win: Any, attrib_list: List[int]) -> "EGLSurface":
|
||||
"""创建窗口表面 (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglCreateWindowSurface() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglMakeCurrent(display: "EGLDisplay", draw: "EGLSurface",
|
||||
read: "EGLSurface", context: "EGLContext") -> bool:
|
||||
"""绑定 EGL 上下文 (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglMakeCurrent() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglSwapBuffers(display: "EGLDisplay", surface: "EGLSurface") -> bool:
|
||||
"""交换缓冲区 (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglSwapBuffers() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglDestroyContext(display: "EGLDisplay", context: "EGLContext") -> bool:
|
||||
"""销毁 EGL 上下文 (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglDestroyContext() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglDestroySurface(display: "EGLDisplay", surface: "EGLSurface") -> bool:
|
||||
"""销毁表面 (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglDestroySurface() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglTerminate(display: "EGLDisplay") -> bool:
|
||||
"""终止 EGL (桌面仅返回存根)"""
|
||||
raise AndroidOnlyError("EGL14.eglTerminate() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def eglGetError() -> int:
|
||||
"""获取 EGL 错误码"""
|
||||
return EGL14.EGL_SUCCESS
|
||||
|
||||
@staticmethod
|
||||
def eglQueryString(display: "EGLDisplay", name: int) -> str:
|
||||
"""查询 EGL 字符串"""
|
||||
if name == 0x3029: # EGL_VERSION
|
||||
return "1.4 (desktop stub)"
|
||||
return ""
|
||||
|
||||
def toString(self) -> str:
|
||||
return "EGL14(desktop-stub)"
|
||||
|
||||
|
||||
class EGLDisplay(Stub):
|
||||
"""EGLDisplay — EGL 显示连接"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"EGLDisplay@{hex(id(self))}"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(id(self)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
class EGLConfig(Stub):
|
||||
"""EGLConfig — EGL 帧缓冲配置"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"EGLConfig@{hex(id(self))}"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(id(self)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
class EGLContext(Stub):
|
||||
"""EGLContext — EGL 渲染上下文"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"EGLContext@{hex(id(self))}"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(id(self)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
class EGLSurface(Stub):
|
||||
"""EGLSurface — EGL 渲染表面"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"EGLSurface@{hex(id(self))}"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(id(self)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# GLSurfaceView
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class GLSurfaceView(Stub):
|
||||
"""GLSurfaceView — OpenGL 渲染视图 (桌面存根)"""
|
||||
|
||||
# 渲染模式
|
||||
RENDERMODE_CONTINUOUSLY = 1
|
||||
RENDERMODE_WHEN_DIRTY = 0
|
||||
|
||||
# 调试标志
|
||||
DEBUG_CHECK_GL_ERROR = 1
|
||||
DEBUG_LOG_GL_CALLS = 2
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._renderer: Optional[Any] = None
|
||||
self._render_mode: int = GLSurfaceView.RENDERMODE_CONTINUOUSLY
|
||||
self._egl_context_client_version: int = 2
|
||||
self._preserve_egl_context_on_pause: bool = False
|
||||
self._debug_flags: int = 0
|
||||
self._paused: bool = False
|
||||
self._queue: List[Callable] = []
|
||||
|
||||
def setRenderer(self, renderer: Any):
|
||||
"""设置渲染器"""
|
||||
self._renderer = renderer
|
||||
if renderer:
|
||||
renderer.onSurfaceCreated(None, None)
|
||||
renderer.onSurfaceChanged(None, 0, 0)
|
||||
|
||||
def setRenderMode(self, render_mode: int):
|
||||
"""设置渲染模式"""
|
||||
self._render_mode = render_mode
|
||||
|
||||
def getRenderMode(self) -> int:
|
||||
"""获取渲染模式"""
|
||||
return self._render_mode
|
||||
|
||||
def onPause(self):
|
||||
"""暂停渲染"""
|
||||
self._paused = True
|
||||
|
||||
def onResume(self):
|
||||
"""恢复渲染"""
|
||||
self._paused = False
|
||||
|
||||
def queueEvent(self, r: Callable):
|
||||
"""将事件排入 GL 线程队列"""
|
||||
self._queue.append(r)
|
||||
# 立即执行 (桌面模拟)
|
||||
try:
|
||||
r()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def requestRender(self):
|
||||
"""请求渲染"""
|
||||
if self._renderer and not self._paused:
|
||||
self._renderer.onDrawFrame(None)
|
||||
|
||||
def setEGLContextClientVersion(self, version: int):
|
||||
"""设置 EGL 客户端版本"""
|
||||
self._egl_context_client_version = version
|
||||
|
||||
def setPreserveEGLContextOnPause(self, preserve: bool):
|
||||
"""设置暂停时是否保留 EGL 上下文"""
|
||||
self._preserve_egl_context_on_pause = preserve
|
||||
|
||||
def setDebugFlags(self, debug_flags: int):
|
||||
"""设置调试标志"""
|
||||
self._debug_flags = debug_flags
|
||||
|
||||
def getPreserveEGLContextOnPause(self) -> bool:
|
||||
return self._preserve_egl_context_on_pause
|
||||
|
||||
def surfaceCreated(self, holder: Any):
|
||||
"""表面创建回调"""
|
||||
pass
|
||||
|
||||
def surfaceDestroyed(self, holder: Any):
|
||||
"""表面销毁回调"""
|
||||
pass
|
||||
|
||||
def surfaceChanged(self, holder: Any, format: int, w: int, h: int):
|
||||
"""表面变化回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"GLSurfaceView(renderMode={self._render_mode}, paused={self._paused})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# GLU
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class GLU(Stub):
|
||||
"""GLU — OpenGL 实用工具函数"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def gluLookAt(eye_x: float, eye_y: float, eye_z: float,
|
||||
center_x: float, center_y: float, center_z: float,
|
||||
up_x: float, up_y: float, up_z: float):
|
||||
"""定义视图变换 (桌面仅抛异常)"""
|
||||
raise AndroidOnlyError("GLU.gluLookAt() 仅在 Android (OpenGL) 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def gluPerspective(fov_y: float, aspect: float, z_near: float, z_far: float):
|
||||
"""设置透视投影 (桌面仅抛异常)"""
|
||||
raise AndroidOnlyError("GLU.gluPerspective() 仅在 Android (OpenGL) 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def gluOrtho2D(left: float, right: float, bottom: float, top: float):
|
||||
"""设置正交投影 (桌面仅抛异常)"""
|
||||
raise AndroidOnlyError("GLU.gluOrtho2D() 仅在 Android (OpenGL) 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def gluProject(obj_x: float, obj_y: float, obj_z: float,
|
||||
model: List[float], proj: List[float],
|
||||
viewport: List[int]) -> Tuple[bool, float, float, float]:
|
||||
"""将对象坐标映射到窗口坐标"""
|
||||
win_x = (obj_x * proj[0] + 1.0) * viewport[2] / 2.0 + viewport[0]
|
||||
win_y = (obj_y * proj[5] + 1.0) * viewport[3] / 2.0 + viewport[1]
|
||||
win_z = obj_z * proj[10] + proj[11]
|
||||
return True, win_x, win_y, win_z
|
||||
|
||||
@staticmethod
|
||||
def gluUnProject(win_x: float, win_y: float, win_z: float,
|
||||
model: List[float], proj: List[float],
|
||||
viewport: List[int]) -> Tuple[bool, float, float, float]:
|
||||
"""将窗口坐标映射到对象坐标"""
|
||||
obj_x = 2.0 * (win_x - viewport[0]) / viewport[2] - 1.0
|
||||
obj_y = 2.0 * (win_y - viewport[1]) / viewport[3] - 1.0
|
||||
obj_z = win_z * 2.0 - 1.0
|
||||
return True, obj_x, obj_y, obj_z
|
||||
|
||||
@staticmethod
|
||||
def gluErrorString(error: int) -> str:
|
||||
"""返回 OpenGL 错误描述字符串"""
|
||||
errors = {
|
||||
0x0500: "GL_INVALID_ENUM",
|
||||
0x0501: "GL_INVALID_VALUE",
|
||||
0x0502: "GL_INVALID_OPERATION",
|
||||
0x0503: "GL_STACK_OVERFLOW",
|
||||
0x0504: "GL_STACK_UNDERFLOW",
|
||||
0x0505: "GL_OUT_OF_MEMORY",
|
||||
0x0506: "GL_INVALID_FRAMEBUFFER_OPERATION",
|
||||
}
|
||||
return errors.get(error, f"Unknown error 0x{error:04X}")
|
||||
|
||||
def toString(self) -> str:
|
||||
return "GLU(desktop-stub)"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash("android.opengl.GLU") & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return isinstance(other, GLU)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# GLUtils
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class GLUtils(Stub):
|
||||
"""GLUtils — OpenGL 工具函数"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def getInternalFormat(bitmap: Any) -> int:
|
||||
"""获取位图的内部格式"""
|
||||
return 0x1908 # GL_RGBA
|
||||
|
||||
@staticmethod
|
||||
def getType(bitmap: Any) -> int:
|
||||
"""获取位图数据类型"""
|
||||
return 0x1401 # GL_UNSIGNED_BYTE
|
||||
|
||||
@staticmethod
|
||||
def texImage2D(target: int, level: int, internal_format: int,
|
||||
bitmap: Any, border: int = 0):
|
||||
"""上传 2D 纹理图像"""
|
||||
raise AndroidOnlyError("GLUtils.texImage2D() 仅在 Android (OpenGL) 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def texSubImage2D(target: int, level: int, xoffset: int, yoffset: int,
|
||||
bitmap: Any, format: int = -1, type_: int = -1):
|
||||
"""更新 2D 纹理子图像"""
|
||||
raise AndroidOnlyError("GLUtils.texSubImage2D() 仅在 Android (OpenGL) 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def toString(bitmap_or_buffer: Any) -> str:
|
||||
return str(bitmap_or_buffer)
|
||||
|
||||
def toString(self) -> str:
|
||||
return "GLUtils(desktop-stub)"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash("android.opengl.GLUtils") & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return isinstance(other, GLUtils)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"GLSurfaceView",
|
||||
"GLU",
|
||||
"GLUtils",
|
||||
"EGL14",
|
||||
"EGLConfig",
|
||||
"EGLContext",
|
||||
"EGLDisplay",
|
||||
"EGLSurface",
|
||||
]
|
||||
@@ -0,0 +1,581 @@
|
||||
"""
|
||||
Android Print 组件 — PrintManager, PrintJob, PrintDocumentAdapter, PdfPrint 等
|
||||
方法签名完全对齐 Android SDK (android.print 包)
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PrintAttributes
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PrintAttributes(Stub):
|
||||
"""PrintAttributes — 打印属性"""
|
||||
|
||||
# 颜色模式
|
||||
COLOR_MODE_MONOCHROME = 1
|
||||
COLOR_MODE_COLOR = 2
|
||||
|
||||
# 方向
|
||||
ORIENTATION_PORTRAIT = 1
|
||||
ORIENTATION_LANDSCAPE = 2
|
||||
|
||||
# 媒质尺寸 ID
|
||||
MEDIA_SIZE_UNKNOWN = "unknown"
|
||||
MEDIA_SIZE_A4 = "A4"
|
||||
MEDIA_SIZE_A3 = "A3"
|
||||
MEDIA_SIZE_LETTER = "letter"
|
||||
MEDIA_SIZE_LEGAL = "legal"
|
||||
MEDIA_SIZE_ISO_A4 = "iso_a4"
|
||||
MEDIA_SIZE_ISO_A3 = "iso_a3"
|
||||
MEDIA_SIZE_NA_LETTER = "na_letter"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._media_size: Optional["PrintAttributes.MediaSize"] = None
|
||||
self._resolution: Optional["PrintAttributes.Resolution"] = None
|
||||
self._min_margins: Optional["PrintAttributes.Margins"] = None
|
||||
self._color_mode: int = PrintAttributes.COLOR_MODE_COLOR
|
||||
self._orientation: int = PrintAttributes.ORIENTATION_PORTRAIT
|
||||
|
||||
def getMediaSize(self) -> Optional["MediaSize"]:
|
||||
return self._media_size
|
||||
|
||||
def getResolution(self) -> Optional["Resolution"]:
|
||||
return self._resolution
|
||||
|
||||
def getMinMargins(self) -> Optional["Margins"]:
|
||||
return self._min_margins
|
||||
|
||||
def getColorMode(self) -> int:
|
||||
return self._color_mode
|
||||
|
||||
def getOrientation(self) -> int:
|
||||
return self._orientation
|
||||
|
||||
def isPortrait(self) -> bool:
|
||||
return self._orientation == PrintAttributes.ORIENTATION_PORTRAIT
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"PrintAttributes(colorMode={self._color_mode}, "
|
||||
f"orientation={self._orientation})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._color_mode, self._orientation)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, PrintAttributes):
|
||||
return False
|
||||
return (self._color_mode == other._color_mode
|
||||
and self._orientation == other._orientation)
|
||||
|
||||
# ── 内部嵌套类 ──
|
||||
|
||||
class MediaSize(Stub):
|
||||
"""MediaSize — 媒质尺寸"""
|
||||
|
||||
def __init__(self, id: str, label: str, width_mils: int, height_mils: int):
|
||||
super().__init__()
|
||||
self._id = id
|
||||
self._label = label
|
||||
self._width_mils = width_mils
|
||||
self._height_mils = height_mils
|
||||
|
||||
def getId(self) -> str:
|
||||
return self._id
|
||||
|
||||
def getLabel(self, package_manager: Any = None) -> str:
|
||||
return self._label
|
||||
|
||||
def getWidthMils(self) -> int:
|
||||
return self._width_mils
|
||||
|
||||
def getHeightMils(self) -> int:
|
||||
return self._height_mils
|
||||
|
||||
def asPortrait(self) -> "PrintAttributes.MediaSize":
|
||||
if self._width_mils > self._height_mils:
|
||||
return PrintAttributes.MediaSize(
|
||||
self._id, self._label, self._height_mils, self._width_mils)
|
||||
return self
|
||||
|
||||
def asLandscape(self) -> "PrintAttributes.MediaSize":
|
||||
if self._width_mils < self._height_mils:
|
||||
return PrintAttributes.MediaSize(
|
||||
self._id, self._label, self._height_mils, self._width_mils)
|
||||
return self
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"MediaSize({self._id}: {self._width_mils}x{self._height_mils}mils)"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._id) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, PrintAttributes.MediaSize):
|
||||
return False
|
||||
return self._id == other._id
|
||||
|
||||
class Resolution(Stub):
|
||||
"""Resolution — 打印分辨率"""
|
||||
|
||||
def __init__(self, id: str, label: str, horizontal_dpi: int, vertical_dpi: int):
|
||||
super().__init__()
|
||||
self._id = id
|
||||
self._label = label
|
||||
self._horizontal_dpi = horizontal_dpi
|
||||
self._vertical_dpi = vertical_dpi
|
||||
|
||||
def getId(self) -> str:
|
||||
return self._id
|
||||
|
||||
def getLabel(self) -> str:
|
||||
return self._label
|
||||
|
||||
def getHorizontalDpi(self) -> int:
|
||||
return self._horizontal_dpi
|
||||
|
||||
def getVerticalDpi(self) -> int:
|
||||
return self._vertical_dpi
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Resolution({self._id}: {self._horizontal_dpi}x{self._vertical_dpi}dpi)"
|
||||
|
||||
class Margins(Stub):
|
||||
"""Margins — 页边距"""
|
||||
|
||||
NO_MARGINS: "PrintAttributes.Margins" = None
|
||||
|
||||
def __init__(self, left_mils: int, top_mils: int,
|
||||
right_mils: int, bottom_mils: int):
|
||||
super().__init__()
|
||||
self._left_mils = left_mils
|
||||
self._top_mils = top_mils
|
||||
self._right_mils = right_mils
|
||||
self._bottom_mils = bottom_mils
|
||||
|
||||
def getLeftMils(self) -> int:
|
||||
return self._left_mils
|
||||
|
||||
def getTopMils(self) -> int:
|
||||
return self._top_mils
|
||||
|
||||
def getRightMils(self) -> int:
|
||||
return self._right_mils
|
||||
|
||||
def getBottomMils(self) -> int:
|
||||
return self._bottom_mils
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Margins({self._left_mils},{self._top_mils},{self._right_mils},{self._bottom_mils})"
|
||||
|
||||
class Builder(Stub):
|
||||
"""Builder — PrintAttributes 构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._attrs = PrintAttributes()
|
||||
|
||||
def setMediaSize(self, media_size: "PrintAttributes.MediaSize") -> "PrintAttributes.Builder":
|
||||
self._attrs._media_size = media_size
|
||||
return self
|
||||
|
||||
def setResolution(self, resolution: "PrintAttributes.Resolution") -> "PrintAttributes.Builder":
|
||||
self._attrs._resolution = resolution
|
||||
return self
|
||||
|
||||
def setMinMargins(self, margins: "PrintAttributes.Margins") -> "PrintAttributes.Builder":
|
||||
self._attrs._min_margins = margins
|
||||
return self
|
||||
|
||||
def setColorMode(self, color_mode: int) -> "PrintAttributes.Builder":
|
||||
self._attrs._color_mode = color_mode
|
||||
return self
|
||||
|
||||
def setOrientation(self, orientation: int) -> "PrintAttributes.Builder":
|
||||
self._attrs._orientation = orientation
|
||||
return self
|
||||
|
||||
def build(self) -> "PrintAttributes":
|
||||
return self._attrs
|
||||
|
||||
|
||||
PrintAttributes.Margins.NO_MARGINS = PrintAttributes.Margins(0, 0, 0, 0)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PrintJobInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PrintJobInfo(Stub):
|
||||
"""PrintJobInfo — 打印任务信息"""
|
||||
|
||||
# 状态常量
|
||||
STATE_CREATED = 1
|
||||
STATE_QUEUED = 2
|
||||
STATE_STARTED = 3
|
||||
STATE_BLOCKED = 4
|
||||
STATE_COMPLETED = 5
|
||||
STATE_FAILED = 6
|
||||
STATE_CANCELED = 7
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._label: str = ""
|
||||
self._id: Optional[Any] = None
|
||||
self._state: int = PrintJobInfo.STATE_CREATED
|
||||
self._attributes: Optional[PrintAttributes] = None
|
||||
self._copies: int = 1
|
||||
self._creation_time: int = int(time.time() * 1000)
|
||||
self._canceling: bool = False
|
||||
self._pages: Optional[List[int]] = None
|
||||
self._has_advanced_options: bool = False
|
||||
|
||||
def getLabel(self) -> str:
|
||||
return self._label
|
||||
|
||||
def setLabel(self, label: str):
|
||||
self._label = label
|
||||
|
||||
def getId(self) -> Optional[Any]:
|
||||
return self._id
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def setState(self, state: int):
|
||||
self._state = state
|
||||
|
||||
def getAttributes(self) -> Optional[PrintAttributes]:
|
||||
return self._attributes
|
||||
|
||||
def setAttributes(self, attributes: PrintAttributes):
|
||||
self._attributes = attributes
|
||||
|
||||
def getCopies(self) -> int:
|
||||
return self._copies
|
||||
|
||||
def setCopies(self, copies: int):
|
||||
self._copies = copies
|
||||
|
||||
def getCreationTime(self) -> int:
|
||||
return self._creation_time
|
||||
|
||||
def isCanceling(self) -> bool:
|
||||
return self._canceling
|
||||
|
||||
def getPages(self) -> Optional[List[int]]:
|
||||
return self._pages
|
||||
|
||||
def setPages(self, pages: Optional[List[int]]):
|
||||
self._pages = pages
|
||||
|
||||
def hasAdvancedOptions(self) -> bool:
|
||||
return self._has_advanced_options
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PrintJobInfo(label={self._label}, state={self._state}, copies={self._copies})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._id) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, PrintJobInfo):
|
||||
return False
|
||||
return self._id == other._id
|
||||
|
||||
# ── Builder ──
|
||||
|
||||
class Builder(Stub):
|
||||
"""Builder — PrintJobInfo 构建器"""
|
||||
|
||||
def __init__(self, info: Optional["PrintJobInfo"] = None):
|
||||
super().__init__()
|
||||
self._info = PrintJobInfo()
|
||||
if info:
|
||||
self._info._label = info._label
|
||||
self._info._id = info._id
|
||||
self._info._attributes = info._attributes
|
||||
self._info._copies = info._copies
|
||||
|
||||
def setCopies(self, copies: int) -> "PrintJobInfo.Builder":
|
||||
self._info._copies = copies
|
||||
return self
|
||||
|
||||
def setAttributes(self, attributes: PrintAttributes) -> "PrintJobInfo.Builder":
|
||||
self._info._attributes = attributes
|
||||
return self
|
||||
|
||||
def setLabel(self, label: str) -> "PrintJobInfo.Builder":
|
||||
self._info._label = label
|
||||
return self
|
||||
|
||||
def setPages(self, pages: Optional[List[int]]) -> "PrintJobInfo.Builder":
|
||||
self._info._pages = pages
|
||||
return self
|
||||
|
||||
def build(self) -> "PrintJobInfo":
|
||||
return self._info
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PrintJob
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PrintJob(Stub):
|
||||
"""PrintJob — 打印任务"""
|
||||
|
||||
def __init__(self, info: PrintJobInfo):
|
||||
super().__init__()
|
||||
self._info = info
|
||||
self._cancelled = False
|
||||
self._result: Optional[Any] = None
|
||||
|
||||
def getId(self) -> Optional[Any]:
|
||||
return self._info.getId()
|
||||
|
||||
def getInfo(self) -> PrintJobInfo:
|
||||
return self._info
|
||||
|
||||
def isStarted(self) -> bool:
|
||||
return self._info.getState() >= PrintJobInfo.STATE_STARTED
|
||||
|
||||
def isBlocked(self) -> bool:
|
||||
return self._info.getState() == PrintJobInfo.STATE_BLOCKED
|
||||
|
||||
def isCompleted(self) -> bool:
|
||||
return self._info.getState() == PrintJobInfo.STATE_COMPLETED
|
||||
|
||||
def isFailed(self) -> bool:
|
||||
return self._info.getState() == PrintJobInfo.STATE_FAILED
|
||||
|
||||
def isCancelled(self) -> bool:
|
||||
return self._cancelled
|
||||
|
||||
def cancel(self):
|
||||
"""取消打印任务"""
|
||||
self._cancelled = True
|
||||
self._info.setState(PrintJobInfo.STATE_CANCELED)
|
||||
|
||||
def restart(self):
|
||||
"""重新启动打印任务"""
|
||||
self._cancelled = False
|
||||
self._info.setState(PrintJobInfo.STATE_QUEUED)
|
||||
|
||||
def setResult(self, result: Any):
|
||||
self._result = result
|
||||
|
||||
def getResult(self) -> Optional[Any]:
|
||||
return self._result
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PrintJob(id={self.getId()}, state={self._info.getState()})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self.getId()) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, PrintJob):
|
||||
return False
|
||||
return self.getId() == other.getId()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PrintDocumentAdapter
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PrintDocumentAdapter(Stub):
|
||||
"""PrintDocumentAdapter — 打印文档适配器"""
|
||||
|
||||
EXTRA_PRINT_PREVIEW = "EXTRA_PRINT_PREVIEW"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onStart(self):
|
||||
"""打印开始回调"""
|
||||
pass
|
||||
|
||||
def onFinish(self):
|
||||
"""打印结束回调"""
|
||||
pass
|
||||
|
||||
def onLayout(self, old_attributes: PrintAttributes, new_attributes: PrintAttributes,
|
||||
cancellation_signal: Any, callback: "PrintDocumentAdapterLayoutCallback",
|
||||
metadata: Any):
|
||||
"""布局回调 - 桌面实现直接完成"""
|
||||
if callback:
|
||||
callback.onLayoutFinished(self, new_attributes)
|
||||
|
||||
def onWrite(self, pages: List[int], destination: Any,
|
||||
cancellation_signal: Any,
|
||||
callback: "PrintDocumentAdapterWriteCallback"):
|
||||
"""写入回调 - 桌面实现直接完成"""
|
||||
if callback:
|
||||
callback.onWriteFinished(pages)
|
||||
|
||||
|
||||
class PrintDocumentAdapterLayoutCallback(Stub):
|
||||
"""PrintDocumentAdapterLayoutCallback — 布局回调"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onLayoutFinished(self, adapter: PrintDocumentAdapter, attributes: PrintAttributes):
|
||||
"""布局完成"""
|
||||
pass
|
||||
|
||||
def onLayoutFailed(self, error: Optional[str] = None):
|
||||
"""布局失败"""
|
||||
pass
|
||||
|
||||
def onLayoutCancelled(self):
|
||||
"""布局取消"""
|
||||
pass
|
||||
|
||||
|
||||
class PrintDocumentAdapterWriteCallback(Stub):
|
||||
"""PrintDocumentAdapterWriteCallback — 写入回调"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onWriteFinished(self, pages: List[int]):
|
||||
"""写入完成"""
|
||||
pass
|
||||
|
||||
def onWriteFailed(self, error: Optional[str] = None):
|
||||
"""写入失败"""
|
||||
pass
|
||||
|
||||
def onWriteCancelled(self):
|
||||
"""写入取消"""
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PdfPrint
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PdfPrint(Stub):
|
||||
"""PdfPrint — PDF 打印辅助类"""
|
||||
|
||||
def __init__(self, attributes: Optional[PrintAttributes] = None):
|
||||
super().__init__()
|
||||
self._attributes = attributes or PrintAttributes()
|
||||
|
||||
def getAttributes(self) -> PrintAttributes:
|
||||
return self._attributes
|
||||
|
||||
def setAttributes(self, attributes: PrintAttributes):
|
||||
self._attributes = attributes
|
||||
|
||||
def print(self, print_adapter: PrintDocumentAdapter, output_path: str) -> bool:
|
||||
"""执行 PDF 打印 (桌面实现: 写入文件)"""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
|
||||
# 在桌面环境, PDF 打印由适配器的 onWrite 实现处理
|
||||
callback = PrintDocumentAdapterWriteCallback()
|
||||
print_adapter.onWrite([1], output_path, None, callback)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PdfPrint(attributes={self._attributes})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(str(self._attributes)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, PdfPrint):
|
||||
return False
|
||||
return str(self._attributes) == str(other._attributes)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PrintManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PrintManager(Stub):
|
||||
"""PrintManager — 打印管理器"""
|
||||
|
||||
# 打印任务 ID 生成
|
||||
_next_job_id = 1
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._print_jobs: Dict[str, PrintJob] = {}
|
||||
|
||||
def getPrintJobs(self) -> List[PrintJob]:
|
||||
"""获取打印任务列表"""
|
||||
return list(self._print_jobs.values())
|
||||
|
||||
def print(self, job_name: str, document_adapter: PrintDocumentAdapter,
|
||||
attributes: Optional[PrintAttributes] = None) -> PrintJob:
|
||||
"""提交打印任务"""
|
||||
info = PrintJobInfo()
|
||||
info.setLabel(job_name)
|
||||
info.setAttributes(attributes or PrintAttributes())
|
||||
info.setState(PrintJobInfo.STATE_QUEUED)
|
||||
|
||||
job_id = f"print_job_{PrintManager._next_job_id}"
|
||||
PrintManager._next_job_id += 1
|
||||
|
||||
job = PrintJob(info)
|
||||
self._print_jobs[job_id] = job
|
||||
|
||||
# 自动触发布局和写入
|
||||
cancellation_signal = type("CancellationSignal", (), {"isCanceled": lambda: False})()
|
||||
document_adapter.onStart()
|
||||
layout_cb = PrintDocumentAdapterLayoutCallback()
|
||||
document_adapter.onLayout(None, info.getAttributes(),
|
||||
cancellation_signal, layout_cb, None)
|
||||
|
||||
info.setState(PrintJobInfo.STATE_COMPLETED)
|
||||
document_adapter.onFinish()
|
||||
|
||||
return job
|
||||
|
||||
def getPrintJobById(self, job_id: str) -> Optional[PrintJob]:
|
||||
"""根据 ID 获取打印任务"""
|
||||
return self._print_jobs.get(job_id)
|
||||
|
||||
def cancelPrintJob(self, job_id: str) -> bool:
|
||||
"""取消打印任务"""
|
||||
job = self._print_jobs.get(job_id)
|
||||
if job:
|
||||
job.cancel()
|
||||
return True
|
||||
return False
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PrintManager(jobs={len(self._print_jobs)})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"PrintManager",
|
||||
"PrintJob",
|
||||
"PrintJobInfo",
|
||||
"PrintDocumentAdapter",
|
||||
"PrintAttributes",
|
||||
"PrintDocumentAdapterLayoutCallback",
|
||||
"PrintDocumentAdapterWriteCallback",
|
||||
"PdfPrint",
|
||||
]
|
||||
@@ -0,0 +1,903 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""
|
||||
Android RenderScript 组件 — RenderScript, Allocation, Element, Type, Script 等
|
||||
方法签名完全对齐 Android SDK (android.renderscript 包)
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RenderScript
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RenderScript(Stub):
|
||||
"""RenderScript — RenderScript 运行时上下文 (桌面仅存根)"""
|
||||
|
||||
# 优先级
|
||||
Priority_LOW = 0
|
||||
Priority_NORMAL = 1
|
||||
Priority_HIGH = 2
|
||||
|
||||
# 上下文类型
|
||||
ContextType_NORMAL = 0
|
||||
ContextType_DEBUG = 1
|
||||
ContextType_PROFILE = 2
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._context_type = RenderScript.ContextType_NORMAL
|
||||
self._error_handler: Optional[Callable] = None
|
||||
self._message_handler: Optional[Callable] = None
|
||||
self._priority = RenderScript.Priority_NORMAL
|
||||
self._initialized = False
|
||||
|
||||
@staticmethod
|
||||
def create(context: Any = None) -> "RenderScript":
|
||||
"""创建 RenderScript 上下文"""
|
||||
rs = RenderScript()
|
||||
rs._initialized = True
|
||||
return rs
|
||||
|
||||
@staticmethod
|
||||
def createMultiContext(context: Any = None,
|
||||
context_type: int = ContextType_NORMAL,
|
||||
flags: int = 0,
|
||||
api_version: int = 0) -> "RenderScript":
|
||||
"""创建多上下文 RenderScript"""
|
||||
rs = RenderScript()
|
||||
rs._context_type = context_type
|
||||
rs._initialized = True
|
||||
return rs
|
||||
|
||||
def setPriority(self, priority: int):
|
||||
"""设置优先级"""
|
||||
self._priority = priority
|
||||
|
||||
def getPriority(self) -> int:
|
||||
return self._priority
|
||||
|
||||
def setErrorHandler(self, handler: Callable):
|
||||
"""设置错误处理器"""
|
||||
self._error_handler = handler
|
||||
|
||||
def setMessageHandler(self, handler: Callable):
|
||||
"""设置消息处理器"""
|
||||
self._message_handler = handler
|
||||
|
||||
def finish(self):
|
||||
"""等待所有挂起渲染完成"""
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
"""销毁 RenderScript 上下文"""
|
||||
self._initialized = False
|
||||
|
||||
def getApplicationContext(self) -> Any:
|
||||
return None
|
||||
|
||||
def getErrorHandler(self) -> Optional[Callable]:
|
||||
return self._error_handler
|
||||
|
||||
def getMessageHandler(self) -> Optional[Callable]:
|
||||
return self._message_handler
|
||||
|
||||
def isDestroyed(self) -> bool:
|
||||
return not self._initialized
|
||||
|
||||
def nvLoadAll(self):
|
||||
pass
|
||||
|
||||
def nvLoadFont(self, name: str) -> int:
|
||||
return 0
|
||||
|
||||
def nvLoadProp(self, name: str) -> int:
|
||||
return 0
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"RenderScript(priority={self._priority}, initialized={self._initialized})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Element
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Element(Stub):
|
||||
"""Element — RenderScript 数据类型元素"""
|
||||
|
||||
# 预定义数据类型
|
||||
F32: "Element" = None
|
||||
F64: "Element" = None
|
||||
I8: "Element" = None
|
||||
I16: "Element" = None
|
||||
I32: "Element" = None
|
||||
I64: "Element" = None
|
||||
U8: "Element" = None
|
||||
U16: "Element" = None
|
||||
U32: "Element" = None
|
||||
U64: "Element" = None
|
||||
BOOLEAN: "Element" = None
|
||||
ALLOCATION: "Element" = None
|
||||
A_8: "Element" = None
|
||||
RGB_565: "Element" = None
|
||||
RGBA_4444: "Element" = None
|
||||
RGBA_8888: "Element" = None
|
||||
F32_2: "Element" = None
|
||||
F32_3: "Element" = None
|
||||
F32_4: "Element" = None
|
||||
F64_2: "Element" = None
|
||||
F64_3: "Element" = None
|
||||
F64_4: "Element" = None
|
||||
I8_2: "Element" = None
|
||||
I8_3: "Element" = None
|
||||
I8_4: "Element" = None
|
||||
I16_2: "Element" = None
|
||||
I16_3: "Element" = None
|
||||
I16_4: "Element" = None
|
||||
I32_2: "Element" = None
|
||||
I32_3: "Element" = None
|
||||
I32_4: "Element" = None
|
||||
I64_2: "Element" = None
|
||||
I64_3: "Element" = None
|
||||
I64_4: "Element" = None
|
||||
U8_2: "Element" = None
|
||||
U8_3: "Element" = None
|
||||
U8_4: "Element" = None
|
||||
U16_2: "Element" = None
|
||||
U16_3: "Element" = None
|
||||
U16_4: "Element" = None
|
||||
U32_2: "Element" = None
|
||||
U32_3: "Element" = None
|
||||
U32_4: "Element" = None
|
||||
U64_2: "Element" = None
|
||||
U64_3: "Element" = None
|
||||
U64_4: "Element" = None
|
||||
|
||||
# 数据类型
|
||||
DataType = type("DataType", (), {
|
||||
"NONE": 0, "FLOAT_32": 1, "FLOAT_64": 2,
|
||||
"SIGNED_8": 3, "SIGNED_16": 4, "SIGNED_32": 5, "SIGNED_64": 6,
|
||||
"UNSIGNED_8": 7, "UNSIGNED_16": 8, "UNSIGNED_32": 9, "UNSIGNED_64": 10,
|
||||
"BOOLEAN": 11, "UNSIGNED_5_6_5": 12, "UNSIGNED_5_5_5_1": 13,
|
||||
"UNSIGNED_4_4_4_4": 14, "MATRIX_4X4": 15, "MATRIX_3X3": 16,
|
||||
"UNSIGNED_2_10_10_10": 17, "RS_ELEMENT": 18,
|
||||
})
|
||||
|
||||
# 数据类型大小
|
||||
DataType = DataType()
|
||||
|
||||
def __init__(self, rs: Optional[RenderScript] = None, data_type: int = 0,
|
||||
vector_size: int = 1):
|
||||
super().__init__()
|
||||
self._rs = rs
|
||||
self._data_type = data_type
|
||||
self._vector_size = vector_size
|
||||
self._element_size = self._compute_size()
|
||||
|
||||
def _compute_size(self) -> int:
|
||||
base_sizes = {
|
||||
Element.DataType.FLOAT_32: 4, Element.DataType.FLOAT_64: 8,
|
||||
Element.DataType.SIGNED_8: 1, Element.DataType.SIGNED_16: 2,
|
||||
Element.DataType.SIGNED_32: 4, Element.DataType.SIGNED_64: 8,
|
||||
Element.DataType.UNSIGNED_8: 1, Element.DataType.UNSIGNED_16: 2,
|
||||
Element.DataType.UNSIGNED_32: 4, Element.DataType.UNSIGNED_64: 8,
|
||||
Element.DataType.BOOLEAN: 1,
|
||||
Element.DataType.UNSIGNED_5_6_5: 2,
|
||||
Element.DataType.UNSIGNED_5_5_5_1: 2,
|
||||
Element.DataType.UNSIGNED_4_4_4_4: 2,
|
||||
Element.DataType.MATRIX_4X4: 64,
|
||||
Element.DataType.MATRIX_3X3: 36,
|
||||
Element.DataType.RS_ELEMENT: 4,
|
||||
}
|
||||
return base_sizes.get(self._data_type, 4) * self._vector_size
|
||||
|
||||
def getBytesSize(self) -> int:
|
||||
return self._element_size
|
||||
|
||||
def getVectorSize(self) -> int:
|
||||
return self._vector_size
|
||||
|
||||
def getDataType(self) -> int:
|
||||
return self._data_type
|
||||
|
||||
def isCompatible(self, e: "Element") -> bool:
|
||||
return self._data_type == e._data_type and self._vector_size == e._vector_size
|
||||
|
||||
def getSubElement(self, index: int) -> Optional["Element"]:
|
||||
return None
|
||||
|
||||
def getSubElementCount(self) -> int:
|
||||
return 0
|
||||
|
||||
def getSubElementArraySize(self, index: int) -> int:
|
||||
return 0
|
||||
|
||||
def getSubElementOffsetBytes(self, index: int) -> int:
|
||||
return 0
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Element(dataType={self._data_type}, vectorSize={self._vector_size})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._data_type, self._vector_size)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Element):
|
||||
return False
|
||||
return self._data_type == other._data_type and self._vector_size == other._vector_size
|
||||
|
||||
@staticmethod
|
||||
def BOOL(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.BOOLEAN, 1)
|
||||
|
||||
@staticmethod
|
||||
def U8(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.UNSIGNED_8, 1)
|
||||
|
||||
@staticmethod
|
||||
def I8(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.SIGNED_8, 1)
|
||||
|
||||
@staticmethod
|
||||
def U16(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.UNSIGNED_16, 1)
|
||||
|
||||
@staticmethod
|
||||
def I16(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.SIGNED_16, 1)
|
||||
|
||||
@staticmethod
|
||||
def U32(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.UNSIGNED_32, 1)
|
||||
|
||||
@staticmethod
|
||||
def I32(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.SIGNED_32, 1)
|
||||
|
||||
@staticmethod
|
||||
def U64(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.UNSIGNED_64, 1)
|
||||
|
||||
@staticmethod
|
||||
def I64(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.SIGNED_64, 1)
|
||||
|
||||
@staticmethod
|
||||
def F32(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.FLOAT_32, 1)
|
||||
|
||||
@staticmethod
|
||||
def F64(rs: RenderScript) -> "Element":
|
||||
return Element(rs, Element.DataType.FLOAT_64, 1)
|
||||
|
||||
@staticmethod
|
||||
def createVector(rs: RenderScript, data_type: int, size: int) -> "Element":
|
||||
return Element(rs, data_type, size)
|
||||
|
||||
@staticmethod
|
||||
def createPixel(rs: RenderScript, data_type: int, pixel_type: int) -> "Element":
|
||||
return Element(rs, data_type, 4)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Type
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Type(Stub):
|
||||
"""Type — RenderScript 内存分配类型"""
|
||||
|
||||
CubemapFace = type("CubemapFace", (), {
|
||||
"POSITIVE_X": 0, "NEGATIVE_X": 1,
|
||||
"POSITIVE_Y": 2, "NEGATIVE_Y": 3,
|
||||
"POSITIVE_Z": 4, "NEGATIVE_Z": 5,
|
||||
})
|
||||
|
||||
def __init__(self, rs: Optional[RenderScript] = None,
|
||||
element: Optional[Element] = None,
|
||||
dim_x: int = 0, dim_y: int = 0, dim_z: int = 0,
|
||||
mipmaps: bool = False, faces: bool = False):
|
||||
super().__init__()
|
||||
self._rs = rs
|
||||
self._element = element
|
||||
self._dim_x = dim_x
|
||||
self._dim_y = dim_y
|
||||
self._dim_z = dim_z
|
||||
self._mipmaps = mipmaps
|
||||
self._faces = faces
|
||||
self._yuv_format = 0
|
||||
|
||||
def getElement(self) -> Optional[Element]:
|
||||
return self._element
|
||||
|
||||
def getX(self) -> int:
|
||||
return self._dim_x
|
||||
|
||||
def getY(self) -> int:
|
||||
return self._dim_y
|
||||
|
||||
def getZ(self) -> int:
|
||||
return self._dim_z
|
||||
|
||||
def getCount(self) -> int:
|
||||
return self._dim_x * max(self._dim_y, 1) * max(self._dim_z, 1)
|
||||
|
||||
def hasMipmaps(self) -> bool:
|
||||
return self._mipmaps
|
||||
|
||||
def hasFaces(self) -> bool:
|
||||
return self._faces
|
||||
|
||||
def getYuvFormat(self) -> int:
|
||||
return self._yuv_format
|
||||
|
||||
def setYuvFormat(self, yuv_format: int):
|
||||
self._yuv_format = yuv_format
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Type(element={self._element}, dims={self._dim_x}x{self._dim_y}"
|
||||
f"x{self._dim_z}, mipmaps={self._mipmaps})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._dim_x, self._dim_y, self._dim_z)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Type):
|
||||
return False
|
||||
return (self._dim_x == other._dim_x and self._dim_y == other._dim_y
|
||||
and self._dim_z == other._dim_z)
|
||||
|
||||
# ── Builder ──
|
||||
|
||||
class Builder(Stub):
|
||||
"""Builder — Type 构建器"""
|
||||
|
||||
def __init__(self, rs: RenderScript, element: Element):
|
||||
super().__init__()
|
||||
self._rs = rs
|
||||
self._element = element
|
||||
self._dim_x = 0
|
||||
self._dim_y = 0
|
||||
self._dim_z = 0
|
||||
self._mipmaps = False
|
||||
self._faces = False
|
||||
self._yuv_format = 0
|
||||
|
||||
def setX(self, value: int) -> "Type.Builder":
|
||||
self._dim_x = value
|
||||
return self
|
||||
|
||||
def setY(self, value: int) -> "Type.Builder":
|
||||
self._dim_y = value
|
||||
return self
|
||||
|
||||
def setZ(self, value: int) -> "Type.Builder":
|
||||
self._dim_z = value
|
||||
return self
|
||||
|
||||
def setMipmaps(self, enable: bool) -> "Type.Builder":
|
||||
self._mipmaps = enable
|
||||
return self
|
||||
|
||||
def setFaces(self, enable: bool) -> "Type.Builder":
|
||||
self._faces = enable
|
||||
return self
|
||||
|
||||
def setYuvFormat(self, yuv_format: int) -> "Type.Builder":
|
||||
self._yuv_format = yuv_format
|
||||
return self
|
||||
|
||||
def create(self) -> Type:
|
||||
t = Type(self._rs, self._element, self._dim_x, self._dim_y,
|
||||
self._dim_z, self._mipmaps, self._faces)
|
||||
t._yuv_format = self._yuv_format
|
||||
return t
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Allocation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Allocation(Stub):
|
||||
"""Allocation — RenderScript 内存分配"""
|
||||
|
||||
# 使用类型
|
||||
USAGE_SCRIPT = 0x0001
|
||||
USAGE_GRAPHICS_TEXTURE = 0x0002
|
||||
USAGE_GRAPHICS_VERTEX = 0x0004
|
||||
USAGE_GRAPHICS_CONSTANTS = 0x0008
|
||||
USAGE_GRAPHICS_RENDER_TARGET = 0x0010
|
||||
USAGE_IO_INPUT = 0x0020
|
||||
USAGE_IO_OUTPUT = 0x0040
|
||||
USAGE_SHARED = 0x0080
|
||||
|
||||
# Mipmap 控制
|
||||
MIPMAP_FULL = 0
|
||||
MIPMAP_ON_SYNC_TO_TEXTURE = 1
|
||||
|
||||
def __init__(self, rs: Optional[RenderScript] = None,
|
||||
type_: Optional[Type] = None,
|
||||
usage: int = USAGE_SCRIPT):
|
||||
super().__init__()
|
||||
self._rs = rs
|
||||
self._type = type_
|
||||
self._usage = usage
|
||||
self._data: Any = None
|
||||
self._element_size = type_.getElement().getBytesSize() if type_ and type_.getElement() else 0
|
||||
|
||||
@staticmethod
|
||||
def createTyped(rs: RenderScript, type_: Type,
|
||||
usage: int = USAGE_SCRIPT) -> "Allocation":
|
||||
"""创建分配"""
|
||||
alloc = Allocation(rs, type_, usage)
|
||||
total_size = type_.getCount() * type_.getElement().getBytesSize() if type_.getElement() else 0
|
||||
alloc._data = bytearray(total_size)
|
||||
return alloc
|
||||
|
||||
@staticmethod
|
||||
def createFromBitmap(rs: RenderScript, bitmap: Any,
|
||||
usage: int = USAGE_SCRIPT) -> "Allocation":
|
||||
"""从位图创建分配"""
|
||||
# 尝试获取位图尺寸
|
||||
width, height = 0, 0
|
||||
try:
|
||||
width = bitmap.getWidth() if hasattr(bitmap, 'getWidth') else 0
|
||||
height = bitmap.getHeight() if hasattr(bitmap, 'getHeight') else 0
|
||||
except Exception:
|
||||
pass
|
||||
element = Element(rs, Element.DataType.UNSIGNED_8, 4)
|
||||
type_ = Type(rs, element, width, height)
|
||||
alloc = Allocation(rs, type_, usage)
|
||||
alloc._data = bytearray(width * height * 4)
|
||||
return alloc
|
||||
|
||||
@staticmethod
|
||||
def createFromBitmapBoxed(rs: RenderScript, bitmap: Any,
|
||||
usage: int = USAGE_SCRIPT) -> "Allocation":
|
||||
return Allocation.createFromBitmap(rs, bitmap, usage)
|
||||
|
||||
def copyFrom(self, data: Any):
|
||||
"""从数组或位图复制数据"""
|
||||
if isinstance(data, (bytes, bytearray)):
|
||||
self._data = bytearray(data)
|
||||
elif hasattr(data, '__iter__'):
|
||||
import struct
|
||||
try:
|
||||
self._data = bytearray(struct.pack(f'<{len(list(data))}f',
|
||||
*[float(v) for v in data]))
|
||||
except Exception:
|
||||
self._data = bytearray(len(list(data)) * 4)
|
||||
else:
|
||||
self._data = bytearray(1024)
|
||||
|
||||
def copyTo(self, data: Any):
|
||||
"""将数据复制到目标"""
|
||||
if isinstance(data, bytearray) and self._data:
|
||||
data[:] = self._data
|
||||
elif isinstance(data, list):
|
||||
data.clear()
|
||||
if self._data:
|
||||
for i in range(0, len(self._data), 4):
|
||||
data.append(int.from_bytes(self._data[i:i+4], 'little'))
|
||||
|
||||
def resize(self, dim_x: int):
|
||||
"""重新调整分配大小"""
|
||||
if self._type:
|
||||
self._type._dim_x = dim_x
|
||||
self._data = bytearray(self._type.getCount() * self._element_size)
|
||||
|
||||
def getType(self) -> Optional[Type]:
|
||||
return self._type
|
||||
|
||||
def getUsage(self) -> int:
|
||||
return self._usage
|
||||
|
||||
def getElement(self) -> Optional[Element]:
|
||||
return self._type.getElement() if self._type else None
|
||||
|
||||
def syncAll(self, src_location: int):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
self._data = None
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Allocation(type={self._type}, usage={self._usage}, "
|
||||
f"data_size={len(self._data) if self._data else 0})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Matrix4f
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Matrix4f(Stub):
|
||||
"""Matrix4f — 4x4 浮点矩阵"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._matrix = [0.0] * 16
|
||||
self._matrix[0] = 1.0 # 单位矩阵
|
||||
self._matrix[5] = 1.0
|
||||
self._matrix[10] = 1.0
|
||||
self._matrix[15] = 1.0
|
||||
|
||||
def getArray(self) -> List[float]:
|
||||
return list(self._matrix)
|
||||
|
||||
def setArray(self, array: List[float]):
|
||||
if len(array) >= 16:
|
||||
self._matrix = list(array[:16])
|
||||
|
||||
def loadIdentity(self):
|
||||
self._matrix = [0.0] * 16
|
||||
self._matrix[0] = 1.0
|
||||
self._matrix[5] = 1.0
|
||||
self._matrix[10] = 1.0
|
||||
self._matrix[15] = 1.0
|
||||
|
||||
def load(self, src: "Matrix4f"):
|
||||
self._matrix = list(src._matrix)
|
||||
|
||||
def loadRotate(self, rot: float, x: float, y: float, z: float):
|
||||
angle_rad = math.radians(rot)
|
||||
c = math.cos(angle_rad)
|
||||
s = math.sin(angle_rad)
|
||||
self.loadIdentity()
|
||||
if x == 1 and y == 0 and z == 0:
|
||||
self._matrix[5] = c
|
||||
self._matrix[6] = -s
|
||||
self._matrix[9] = s
|
||||
self._matrix[10] = c
|
||||
elif x == 0 and y == 1 and z == 0:
|
||||
self._matrix[0] = c
|
||||
self._matrix[2] = s
|
||||
self._matrix[8] = -s
|
||||
self._matrix[10] = c
|
||||
elif x == 0 and y == 0 and z == 1:
|
||||
self._matrix[0] = c
|
||||
self._matrix[1] = -s
|
||||
self._matrix[4] = s
|
||||
self._matrix[5] = c
|
||||
|
||||
def loadScale(self, x: float, y: float, z: float):
|
||||
self.loadIdentity()
|
||||
self._matrix[0] = x
|
||||
self._matrix[5] = y
|
||||
self._matrix[10] = z
|
||||
|
||||
def loadTranslate(self, x: float, y: float, z: float):
|
||||
self.loadIdentity()
|
||||
self._matrix[12] = x
|
||||
self._matrix[13] = y
|
||||
self._matrix[14] = z
|
||||
|
||||
def loadMultiply(self, lhs: "Matrix4f", rhs: "Matrix4f"):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
self._matrix[i * 4 + j] = sum(
|
||||
lhs._matrix[i * 4 + k] * rhs._matrix[k * 4 + j]
|
||||
for k in range(4)
|
||||
)
|
||||
|
||||
def multiply(self, rhs: "Matrix4f"):
|
||||
result = [0.0] * 16
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
result[i * 4 + j] = sum(
|
||||
self._matrix[i * 4 + k] * rhs._matrix[k * 4 + j]
|
||||
for k in range(4)
|
||||
)
|
||||
self._matrix = result
|
||||
|
||||
def transpose(self):
|
||||
for i in range(4):
|
||||
for j in range(i + 1, 4):
|
||||
self._matrix[i * 4 + j], self._matrix[j * 4 + i] = \
|
||||
self._matrix[j * 4 + i], self._matrix[i * 4 + j]
|
||||
|
||||
def toString(self) -> str:
|
||||
rows = []
|
||||
for i in range(4):
|
||||
row = self._matrix[i*4:(i+1)*4]
|
||||
rows.append(f"[{', '.join(f'{v:.4f}' for v in row)}]")
|
||||
return f"Matrix4f({', '.join(rows)})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(tuple(self._matrix)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Matrix4f):
|
||||
return False
|
||||
return self._matrix == other._matrix
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Matrix3f
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Matrix3f(Stub):
|
||||
"""Matrix3f — 3x3 浮点矩阵"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._matrix = [0.0] * 9
|
||||
self._matrix[0] = 1.0
|
||||
self._matrix[4] = 1.0
|
||||
self._matrix[8] = 1.0
|
||||
|
||||
def getArray(self) -> List[float]:
|
||||
return list(self._matrix)
|
||||
|
||||
def setArray(self, array: List[float]):
|
||||
if len(array) >= 9:
|
||||
self._matrix = list(array[:9])
|
||||
|
||||
def loadIdentity(self):
|
||||
self._matrix = [0.0] * 9
|
||||
self._matrix[0] = 1.0
|
||||
self._matrix[4] = 1.0
|
||||
self._matrix[8] = 1.0
|
||||
|
||||
def load(self, src: "Matrix3f"):
|
||||
self._matrix = list(src._matrix)
|
||||
|
||||
def loadMultiply(self, lhs: "Matrix3f", rhs: "Matrix3f"):
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
self._matrix[i * 3 + j] = sum(
|
||||
lhs._matrix[i * 3 + k] * rhs._matrix[k * 3 + j]
|
||||
for k in range(3)
|
||||
)
|
||||
|
||||
def multiply(self, rhs: "Matrix3f"):
|
||||
result = [0.0] * 9
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
result[i * 3 + j] = sum(
|
||||
self._matrix[i * 3 + k] * rhs._matrix[k * 3 + j]
|
||||
for k in range(3)
|
||||
)
|
||||
self._matrix = result
|
||||
|
||||
def transpose(self):
|
||||
for i in range(3):
|
||||
for j in range(i + 1, 3):
|
||||
self._matrix[i * 3 + j], self._matrix[j * 3 + i] = \
|
||||
self._matrix[j * 3 + i], self._matrix[i * 3 + j]
|
||||
|
||||
def toString(self) -> str:
|
||||
rows = []
|
||||
for i in range(3):
|
||||
row = self._matrix[i*3:(i+1)*3]
|
||||
rows.append(f"[{', '.join(f'{v:.4f}' for v in row)}]")
|
||||
return f"Matrix3f({', '.join(rows)})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(tuple(self._matrix)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Matrix3f):
|
||||
return False
|
||||
return self._matrix == other._matrix
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Script (base)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Script(Stub):
|
||||
"""Script — RenderScript 脚本基类"""
|
||||
|
||||
def __init__(self, rs: Optional[RenderScript] = None):
|
||||
super().__init__()
|
||||
self._rs = rs
|
||||
self._kernel_groups: List[Any] = []
|
||||
|
||||
def setVar(self, index: int, value: Any):
|
||||
"""设置脚本变量"""
|
||||
pass
|
||||
|
||||
def getVar(self, index: int) -> Any:
|
||||
return None
|
||||
|
||||
def setTimeZone(self, timezone: str):
|
||||
pass
|
||||
|
||||
def forEach(self, slot: int, ains: List[Allocation],
|
||||
aout: Allocation, vdata: Any = None):
|
||||
"""执行 forEach 内核"""
|
||||
raise AndroidOnlyError("Script.forEach() 仅在 Android (RenderScript) 设备上可用")
|
||||
|
||||
def invoke(self, slot: int):
|
||||
"""调用可调用函数"""
|
||||
pass
|
||||
|
||||
def bindAllocation(self, va: Allocation, slot: int):
|
||||
"""绑定分配"""
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Script@{hex(id(self))}"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScriptC
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScriptC(Script):
|
||||
"""ScriptC — 编译的 RenderScript 内核脚本"""
|
||||
|
||||
def __init__(self, rs: RenderScript, resource_id: int = 0):
|
||||
super().__init__(rs)
|
||||
self._resource_id = resource_id
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ScriptC(resourceId={self._resource_id})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScriptIntrinsicBlur
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScriptIntrinsicBlur(Script):
|
||||
"""ScriptIntrinsicBlur — 高斯模糊内在函数"""
|
||||
|
||||
def __init__(self, rs: RenderScript):
|
||||
super().__init__(rs)
|
||||
self._radius: float = 5.0
|
||||
self._input: Optional[Allocation] = None
|
||||
|
||||
@staticmethod
|
||||
def create(rs: RenderScript, e: Element) -> "ScriptIntrinsicBlur":
|
||||
"""创建模糊内在函数"""
|
||||
return ScriptIntrinsicBlur(rs)
|
||||
|
||||
def setRadius(self, radius: float):
|
||||
"""设置模糊半径"""
|
||||
self._radius = max(0.0, min(radius, 25.0))
|
||||
|
||||
def setInput(self, alloc: Allocation):
|
||||
"""设置输入分配"""
|
||||
self._input = alloc
|
||||
|
||||
def forEach(self, aout: Allocation):
|
||||
"""执行模糊"""
|
||||
if self._input and aout:
|
||||
pass # 在桌面环境不执行实际模糊
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ScriptIntrinsicBlur(radius={self._radius})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ScriptIntrinsicConvolve3x3
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ScriptIntrinsicConvolve3x3(Script):
|
||||
"""ScriptIntrinsicConvolve3x3 — 3x3 卷积内在函数"""
|
||||
|
||||
def __init__(self, rs: RenderScript):
|
||||
super().__init__(rs)
|
||||
self._coefficients: List[float] = [0.0] * 9
|
||||
self._input: Optional[Allocation] = None
|
||||
|
||||
@staticmethod
|
||||
def create(rs: RenderScript, e: Element) -> "ScriptIntrinsicConvolve3x3":
|
||||
"""创建卷积内在函数"""
|
||||
return ScriptIntrinsicConvolve3x3(rs)
|
||||
|
||||
def setCoefficients(self, coeffs: List[float]):
|
||||
"""设置卷积系数"""
|
||||
for i in range(min(len(coeffs), 9)):
|
||||
self._coefficients[i] = coeffs[i]
|
||||
|
||||
def setInput(self, alloc: Allocation):
|
||||
"""设置输入分配"""
|
||||
self._input = alloc
|
||||
|
||||
def forEach(self, aout: Allocation):
|
||||
"""执行卷积"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ScriptIntrinsicConvolve3x3(coefficients={self._coefficients})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 初始化 Element 静态实例
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def _init_elements():
|
||||
"""初始化 Element 预定义实例(在模块导入时调用)"""
|
||||
rs = RenderScript.create()
|
||||
Element.F32 = Element.F32(rs)
|
||||
Element.F64 = Element.F64(rs)
|
||||
Element.I8 = Element.I8(rs)
|
||||
Element.I16 = Element.I16(rs)
|
||||
Element.I32 = Element.I32(rs)
|
||||
Element.I64 = Element.I64(rs)
|
||||
Element.U8 = Element.U8(rs)
|
||||
Element.U16 = Element.U16(rs)
|
||||
Element.U32 = Element.U32(rs)
|
||||
Element.U64 = Element.U64(rs)
|
||||
Element.BOOLEAN = Element.BOOL(rs)
|
||||
Element.ALLOCATION = Element(rs, Element.DataType.RS_ELEMENT, 1)
|
||||
Element.A_8 = Element.createPixel(rs, Element.DataType.UNSIGNED_8, 1)
|
||||
Element.RGB_565 = Element.createPixel(rs, Element.DataType.UNSIGNED_5_6_5, 3)
|
||||
Element.RGBA_4444 = Element.createPixel(rs, Element.DataType.UNSIGNED_4_4_4_4, 4)
|
||||
Element.RGBA_8888 = Element.createPixel(rs, Element.DataType.UNSIGNED_8, 4)
|
||||
Element.F32_2 = Element.createVector(rs, Element.DataType.FLOAT_32, 2)
|
||||
Element.F32_3 = Element.createVector(rs, Element.DataType.FLOAT_32, 3)
|
||||
Element.F32_4 = Element.createVector(rs, Element.DataType.FLOAT_32, 4)
|
||||
Element.F64_2 = Element.createVector(rs, Element.DataType.FLOAT_64, 2)
|
||||
Element.F64_3 = Element.createVector(rs, Element.DataType.FLOAT_64, 3)
|
||||
Element.F64_4 = Element.createVector(rs, Element.DataType.FLOAT_64, 4)
|
||||
Element.I8_2 = Element.createVector(rs, Element.DataType.SIGNED_8, 2)
|
||||
Element.I8_3 = Element.createVector(rs, Element.DataType.SIGNED_8, 3)
|
||||
Element.I8_4 = Element.createVector(rs, Element.DataType.SIGNED_8, 4)
|
||||
Element.I16_2 = Element.createVector(rs, Element.DataType.SIGNED_16, 2)
|
||||
Element.I16_3 = Element.createVector(rs, Element.DataType.SIGNED_16, 3)
|
||||
Element.I16_4 = Element.createVector(rs, Element.DataType.SIGNED_16, 4)
|
||||
Element.I32_2 = Element.createVector(rs, Element.DataType.SIGNED_32, 2)
|
||||
Element.I32_3 = Element.createVector(rs, Element.DataType.SIGNED_32, 3)
|
||||
Element.I32_4 = Element.createVector(rs, Element.DataType.SIGNED_32, 4)
|
||||
Element.I64_2 = Element.createVector(rs, Element.DataType.SIGNED_64, 2)
|
||||
Element.I64_3 = Element.createVector(rs, Element.DataType.SIGNED_64, 3)
|
||||
Element.I64_4 = Element.createVector(rs, Element.DataType.SIGNED_64, 4)
|
||||
Element.U8_2 = Element.createVector(rs, Element.DataType.UNSIGNED_8, 2)
|
||||
Element.U8_3 = Element.createVector(rs, Element.DataType.UNSIGNED_8, 3)
|
||||
Element.U8_4 = Element.createVector(rs, Element.DataType.UNSIGNED_8, 4)
|
||||
Element.U16_2 = Element.createVector(rs, Element.DataType.UNSIGNED_16, 2)
|
||||
Element.U16_3 = Element.createVector(rs, Element.DataType.UNSIGNED_16, 3)
|
||||
Element.U16_4 = Element.createVector(rs, Element.DataType.UNSIGNED_16, 4)
|
||||
Element.U32_2 = Element.createVector(rs, Element.DataType.UNSIGNED_32, 2)
|
||||
Element.U32_3 = Element.createVector(rs, Element.DataType.UNSIGNED_32, 3)
|
||||
Element.U32_4 = Element.createVector(rs, Element.DataType.UNSIGNED_32, 4)
|
||||
Element.U64_2 = Element.createVector(rs, Element.DataType.UNSIGNED_64, 2)
|
||||
Element.U64_3 = Element.createVector(rs, Element.DataType.UNSIGNED_64, 3)
|
||||
Element.U64_4 = Element.createVector(rs, Element.DataType.UNSIGNED_64, 4)
|
||||
|
||||
|
||||
_init_elements()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"RenderScript",
|
||||
"Allocation",
|
||||
"Element",
|
||||
"Type",
|
||||
"Script",
|
||||
"ScriptIntrinsicBlur",
|
||||
"ScriptIntrinsicConvolve3x3",
|
||||
"ScriptC",
|
||||
"Matrix4f",
|
||||
"Matrix3f",
|
||||
]
|
||||
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
Android se 组件 — SecureElement, Session, Channel, Reader, OmapiConnection 等
|
||||
方法签名完全对齐 Android SDK (SecureElement API)
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SecureElement
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SecureElement(Stub):
|
||||
"""SecureElement — 安全元件"""
|
||||
|
||||
SERVICE_INTERFACE = "android.se.SECURE_ELEMENT_SERVICE"
|
||||
EXTRA_READER = "android.se.extra.READER"
|
||||
EXTRA_READER_NAME = "android.se.extra.READER_NAME"
|
||||
EXTRA_SLOT_ID = "android.se.extra.SLOT_ID"
|
||||
EXTRA_CONNECT_RESPONSE = "android.se.extra.CONNECT_RESPONSE"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Reader
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Reader(Stub):
|
||||
"""Reader — 安全元件读取器"""
|
||||
|
||||
def __init__(self, name: str = "eSE"):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._sessions: List[Session] = []
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def openSession(self) -> "Session":
|
||||
session = Session(self)
|
||||
self._sessions.append(session)
|
||||
return session
|
||||
|
||||
def closeSessions(self):
|
||||
for s in self._sessions:
|
||||
s.close()
|
||||
self._sessions.clear()
|
||||
|
||||
def isSecureElementPresent(self) -> bool:
|
||||
return True
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Reader(name={self._name})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Session
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Session(Stub):
|
||||
"""Session — 安全元件会话"""
|
||||
|
||||
def __init__(self, reader: Optional[Reader] = None):
|
||||
super().__init__()
|
||||
self._reader = reader
|
||||
self._channels: List[Channel] = []
|
||||
self._closed = False
|
||||
|
||||
def openLogicalChannel(self, aid: bytes) -> Optional["Channel"]:
|
||||
if self._closed:
|
||||
return None
|
||||
channel = Channel(self, logical=True)
|
||||
self._channels.append(channel)
|
||||
channel._select_response = bytes([0x90, 0x00]) # SW_OK
|
||||
return channel
|
||||
|
||||
def openBasicChannel(self, aid: bytes) -> Optional["Channel"]:
|
||||
if self._closed:
|
||||
return None
|
||||
channel = Channel(self, logical=False)
|
||||
self._channels.append(channel)
|
||||
channel._select_response = bytes([0x90, 0x00]) # SW_OK
|
||||
return channel
|
||||
|
||||
def close(self):
|
||||
for c in self._channels:
|
||||
c.close()
|
||||
self._channels.clear()
|
||||
self._closed = True
|
||||
|
||||
def getSessions(self) -> List["Session"]:
|
||||
return [self]
|
||||
|
||||
def getChannels(self) -> List["Channel"]:
|
||||
return list(self._channels)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Session(reader={self._reader}, channels={len(self._channels)})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Channel
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Channel(Stub):
|
||||
"""Channel — 安全元件通道"""
|
||||
|
||||
def __init__(self, session: Session, logical: bool = True):
|
||||
super().__init__()
|
||||
self._session = session
|
||||
self._logical = logical
|
||||
self._closed = False
|
||||
self._select_response: Optional[bytes] = None
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
def sendData(self, command: bytes) -> bytes:
|
||||
if self._closed:
|
||||
return bytes([0x6F, 0x00]) # No such EF
|
||||
# Simulate successful APDU response
|
||||
return bytes([0x90, 0x00])
|
||||
|
||||
def getSelectResponse(self) -> Optional[bytes]:
|
||||
return self._select_response
|
||||
|
||||
def getSession(self) -> Session:
|
||||
return self._session
|
||||
|
||||
def isBasicChannel(self) -> bool:
|
||||
return not self._logical
|
||||
|
||||
def isClosed(self) -> bool:
|
||||
return self._closed
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Channel(logical={self._logical}, closed={self._closed})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# OmapiConnection
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class OmapiConnection(Stub):
|
||||
"""OmapiConnection — OMAPI 连接入口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def getInstance(context: Any = None) -> "OmapiConnection":
|
||||
return OmapiConnection()
|
||||
|
||||
def getReaders(self) -> List[Reader]:
|
||||
return [Reader("eSE")]
|
||||
|
||||
def getReader(self, name: str) -> Optional[Reader]:
|
||||
return Reader(name)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"SecureElement",
|
||||
"Reader",
|
||||
"Session",
|
||||
"Channel",
|
||||
"OmapiConnection",
|
||||
]
|
||||
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Android Security 组件 — KeyChain, NetworkSecurityPolicy, AppUriAuthenticationPolicy 等
|
||||
方法签名完全对齐 Android SDK (android.security 包)
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# KeyChain
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class KeyChain(Stub):
|
||||
"""KeyChain — 系统级密钥链访问 (桌面环境不可用)"""
|
||||
|
||||
ACTION_KEYCHAIN_CHANGED = "android.security.action.KEYCHAIN_CHANGED"
|
||||
ACTION_KEY_ACCESS_CHANGED = "android.security.action.KEY_ACCESS_CHANGED"
|
||||
EXTRA_KEY_ACCESSIBLE = "android.security.extra.KEY_ACCESSIBLE"
|
||||
EXTRA_KEY_ALIAS = "android.security.extra.KEY_ALIAS"
|
||||
EXTRA_RESULT = "android.security.extra.RESULT"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def createInstallIntent() -> Any:
|
||||
"""创建安装证书的 Intent (Android-only)"""
|
||||
raise AndroidOnlyError("KeyChain.createInstallIntent() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def choosePrivateKeyAlias(activity: Any, response: Any,
|
||||
keyTypes: Optional[List[str]] = None,
|
||||
issuers: Optional[List[Any]] = None,
|
||||
host: Optional[str] = None,
|
||||
port: int = -1,
|
||||
alias: Optional[str] = None):
|
||||
"""选择私钥别名 (Android-only)"""
|
||||
raise AndroidOnlyError("KeyChain.choosePrivateKeyAlias() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def isKeyAlgorithmSupported(algorithm: str) -> bool:
|
||||
"""检查密钥算法是否支持"""
|
||||
supported = {"RSA", "EC", "DSA", "AES", "HmacSHA256", "HmacSHA1"}
|
||||
return algorithm in supported
|
||||
|
||||
@staticmethod
|
||||
def isBoundKeyAlgorithm(algorithm: str) -> bool:
|
||||
"""检查是否是绑定到硬件的密钥算法 (桌面环境不支持)"""
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def getPrivateKey(context: Any, alias: str) -> Any:
|
||||
"""获取私钥 (Android-only)"""
|
||||
raise AndroidOnlyError("KeyChain.getPrivateKey() 仅在 Android 设备上可用")
|
||||
|
||||
@staticmethod
|
||||
def getCertificateChain(context: Any, alias: str) -> Optional[List[Any]]:
|
||||
"""获取证书链 (Android-only)"""
|
||||
raise AndroidOnlyError("KeyChain.getCertificateChain() 仅在 Android 设备上可用")
|
||||
|
||||
def toString(self) -> str:
|
||||
return "KeyChain(desktop-stub)"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash("android.security.KeyChain") & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return isinstance(other, KeyChain)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AppUriAuthenticationPolicy
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AppUriAuthenticationPolicy(Stub):
|
||||
"""AppUriAuthenticationPolicy — URI 认证策略 (Android 11+)"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._rules: Dict[str, List[str]] = {}
|
||||
|
||||
def addAppAndUriMapping(self, app: Any, uri: Any, alias: str):
|
||||
"""添加应用与 URI 映射"""
|
||||
app_pkg = str(app) if app else ""
|
||||
uri_str = str(uri) if uri else ""
|
||||
if app_pkg not in self._rules:
|
||||
self._rules[app_pkg] = []
|
||||
self._rules[app_pkg].append(uri_str)
|
||||
|
||||
def getAppAndUriMappings(self) -> Dict[str, List[str]]:
|
||||
"""获取所有规则映射"""
|
||||
return dict(self._rules)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AppUriAuthenticationPolicy(rules={self._rules})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(str(self._rules)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, AppUriAuthenticationPolicy):
|
||||
return False
|
||||
return self._rules == other._rules
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# KeyChainAliasResponse
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class KeyChainAliasResponse(Stub):
|
||||
"""KeyChainAliasResponse — 密钥别名选择响应"""
|
||||
|
||||
def __init__(self, alias: Optional[str] = None):
|
||||
super().__init__()
|
||||
self._alias = alias
|
||||
|
||||
def getAlias(self) -> Optional[str]:
|
||||
"""获取选定的密钥别名"""
|
||||
return self._alias
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"KeyChainAliasResponse(alias={self._alias})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._alias) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, KeyChainAliasResponse):
|
||||
return False
|
||||
return self._alias == other._alias
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NetworkSecurityPolicy
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NetworkSecurityPolicy(Stub):
|
||||
"""NetworkSecurityPolicy — 网络安全策略"""
|
||||
|
||||
_instance: Optional["NetworkSecurityPolicy"] = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._cleartext_permitted = True
|
||||
|
||||
@staticmethod
|
||||
def getInstance() -> "NetworkSecurityPolicy":
|
||||
"""获取全局 NetworkSecurityPolicy 实例"""
|
||||
if NetworkSecurityPolicy._instance is None:
|
||||
NetworkSecurityPolicy._instance = NetworkSecurityPolicy()
|
||||
return NetworkSecurityPolicy._instance
|
||||
|
||||
def isCleartextTrafficPermitted(self, host: Optional[str] = None) -> bool:
|
||||
"""检查是否允许明文流量 (桌面默认允许)"""
|
||||
return self._cleartext_permitted
|
||||
|
||||
def setCleartextTrafficPermitted(self, permitted: bool):
|
||||
"""设置是否允许明文流量"""
|
||||
self._cleartext_permitted = permitted
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"NetworkSecurityPolicy(cleartextPermitted={self._cleartext_permitted})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"KeyChain",
|
||||
"AppUriAuthenticationPolicy",
|
||||
"KeyChainAliasResponse",
|
||||
"NetworkSecurityPolicy",
|
||||
]
|
||||
@@ -0,0 +1,423 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""
|
||||
Android Service Autofill 组件 — AutofillService, FillRequest, FillResponse,
|
||||
SaveRequest, SaveInfo, Dataset, AutofillFieldClassificationService 等
|
||||
方法签名完全对齐 Android SDK (android.service.autofill 包)
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Set
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FillRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FillRequest(Stub):
|
||||
"""FillRequest — 自动填充请求"""
|
||||
|
||||
# 请求标志
|
||||
FLAG_MANUAL_REQUEST = 0x0001
|
||||
FLAG_COMPATIBILITY_MODE_REQUEST = 0x0002
|
||||
|
||||
def __init__(self, request_id: int = 0, flags: int = 0,
|
||||
fill_contexts: Optional[List[Any]] = None):
|
||||
super().__init__()
|
||||
self._request_id = request_id
|
||||
self._flags = flags
|
||||
self._fill_contexts = fill_contexts or []
|
||||
self._client_state: Optional[Any] = None
|
||||
|
||||
def getId(self) -> int:
|
||||
"""获取请求 ID"""
|
||||
return self._request_id
|
||||
|
||||
def getFlags(self) -> int:
|
||||
"""获取标志"""
|
||||
return self._flags
|
||||
|
||||
def getFillContexts(self) -> List[Any]:
|
||||
"""获取填充上下文"""
|
||||
return list(self._fill_contexts)
|
||||
|
||||
def getClientState(self) -> Optional[Any]:
|
||||
return self._client_state
|
||||
|
||||
def setClientState(self, state: Any):
|
||||
self._client_state = state
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"FillRequest(id={self._request_id}, "
|
||||
f"flags={self._flags}, contexts={len(self._fill_contexts)})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._request_id & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, FillRequest):
|
||||
return False
|
||||
return self._request_id == other._request_id
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Dataset
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Dataset(Stub):
|
||||
"""Dataset — 自动填充数据集"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._field_values: Dict[Any, Any] = {}
|
||||
self._id: Optional[Any] = None
|
||||
self._authentication: Optional[Any] = None
|
||||
|
||||
def getFieldValues(self) -> Dict[Any, Any]:
|
||||
return dict(self._field_values)
|
||||
|
||||
def getId(self) -> Optional[Any]:
|
||||
return self._id
|
||||
|
||||
def setId(self, id: Any):
|
||||
self._id = id
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Dataset(fields={len(self._field_values)}, id={self._id})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(str(self._id)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Dataset):
|
||||
return False
|
||||
return self._id == other._id
|
||||
|
||||
# ── Builder ──
|
||||
|
||||
class Builder(Stub):
|
||||
"""Builder — Dataset 构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._dataset = Dataset()
|
||||
|
||||
def setId(self, id: Any) -> "Dataset.Builder":
|
||||
self._dataset._id = id
|
||||
return self
|
||||
|
||||
def setValue(self, id: Any, value: Any) -> "Dataset.Builder":
|
||||
self._dataset._field_values[id] = value
|
||||
return self
|
||||
|
||||
def setAuthentication(self, authentication: Any) -> "Dataset.Builder":
|
||||
self._dataset._authentication = authentication
|
||||
return self
|
||||
|
||||
def build(self) -> Dataset:
|
||||
return self._dataset
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SaveInfo
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SaveInfo(Stub):
|
||||
"""SaveInfo — 保存信息"""
|
||||
|
||||
# 保存类型
|
||||
SAVE_DATA_TYPE_EMAIL = 0x0001
|
||||
SAVE_DATA_TYPE_PASSWORD = 0x0002
|
||||
SAVE_DATA_TYPE_USERNAME = 0x0004
|
||||
SAVE_DATA_TYPE_CREDIT_CARD = 0x0008
|
||||
SAVE_DATA_TYPE_ADDRESS = 0x0010
|
||||
SAVE_DATA_TYPE_GENERIC = 0x0020
|
||||
SAVE_DATA_TYPE_PASSWORD_V2 = 0x0040
|
||||
SAVE_DATA_TYPE_SMS_2FA = 0x0080
|
||||
SAVE_DATA_TYPE_BIRTH_DATE = 0x0100
|
||||
SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x0200
|
||||
SAVE_DATA_TYPE_PHONE = 0x0400
|
||||
SAVE_DATA_TYPE_USERNAME_V2 = 0x0800
|
||||
SAVE_DATA_TYPE_CARD_NUMBER = 0x1000
|
||||
SAVE_DATA_TYPE_CARD_EXPIRATION_DATE = 0x2000
|
||||
SAVE_DATA_TYPE_CARD_SECURITY_CODE = 0x4000
|
||||
SAVE_DATA_TYPE_FI_ACCOUNT_NUMBER = 0x8000
|
||||
SAVE_DATA_TYPE_FI_ROUTING_NUMBER = 0x10000
|
||||
SAVE_DATA_TYPE_PASSPORT = 0x20000
|
||||
SAVE_DATA_TYPE_DRIVERS_LICENSE = 0x40000
|
||||
SAVE_DATA_TYPE_SSN = 0x80000
|
||||
|
||||
# 标志
|
||||
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x0001
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._type: int = SaveInfo.SAVE_DATA_TYPE_GENERIC
|
||||
self._required_ids: List[Any] = []
|
||||
self._optional_ids: List[Any] = []
|
||||
self._description: Optional[Any] = None
|
||||
self._flags: int = 0
|
||||
self._trigger_ids: List[Any] = []
|
||||
self._validator: Optional[Any] = None
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._type
|
||||
|
||||
def getRequiredIds(self) -> List[Any]:
|
||||
return list(self._required_ids)
|
||||
|
||||
def getOptionalIds(self) -> List[Any]:
|
||||
return list(self._optional_ids)
|
||||
|
||||
def getDescription(self) -> Optional[Any]:
|
||||
return self._description
|
||||
|
||||
def getFlags(self) -> int:
|
||||
return self._flags
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"SaveInfo(type={self._type}, required={len(self._required_ids)}, "
|
||||
f"optional={len(self._optional_ids)})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._type, tuple(self._required_ids))) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, SaveInfo):
|
||||
return False
|
||||
return self._type == other._type
|
||||
|
||||
# ── Builder ──
|
||||
|
||||
class Builder(Stub):
|
||||
"""Builder — SaveInfo 构建器"""
|
||||
|
||||
def __init__(self, type_: int, required_ids: List[Any]):
|
||||
super().__init__()
|
||||
self._save_info = SaveInfo()
|
||||
self._save_info._type = type_
|
||||
self._save_info._required_ids = list(required_ids)
|
||||
|
||||
def setOptionalIds(self, optional_ids: List[Any]) -> "SaveInfo.Builder":
|
||||
self._save_info._optional_ids = list(optional_ids)
|
||||
return self
|
||||
|
||||
def setDescription(self, description: Any) -> "SaveInfo.Builder":
|
||||
self._save_info._description = description
|
||||
return self
|
||||
|
||||
def setFlags(self, flags: int) -> "SaveInfo.Builder":
|
||||
self._save_info._flags = flags
|
||||
return self
|
||||
|
||||
def setTriggerIds(self, trigger_ids: List[Any]) -> "SaveInfo.Builder":
|
||||
self._save_info._trigger_ids = list(trigger_ids)
|
||||
return self
|
||||
|
||||
def setValidator(self, validator: Any) -> "SaveInfo.Builder":
|
||||
self._save_info._validator = validator
|
||||
return self
|
||||
|
||||
def build(self) -> SaveInfo:
|
||||
return self._save_info
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FillResponse
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FillResponse(Stub):
|
||||
"""FillResponse — 自动填充响应"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._datasets: List[Dataset] = []
|
||||
self._save_info: Optional[SaveInfo] = None
|
||||
self._client_state: Optional[Any] = None
|
||||
self._authentication_result: Optional[Any] = None
|
||||
|
||||
def getDatasets(self) -> List[Dataset]:
|
||||
return list(self._datasets)
|
||||
|
||||
def getSaveInfo(self) -> Optional[SaveInfo]:
|
||||
return self._save_info
|
||||
|
||||
def getClientState(self) -> Optional[Any]:
|
||||
return self._client_state
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"FillResponse(datasets={len(self._datasets)}, "
|
||||
f"saveInfo={'set' if self._save_info else 'none'})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
# ── Builder ──
|
||||
|
||||
class Builder(Stub):
|
||||
"""Builder — FillResponse 构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._response = FillResponse()
|
||||
|
||||
def addDataset(self, dataset: Dataset) -> "FillResponse.Builder":
|
||||
self._response._datasets.append(dataset)
|
||||
return self
|
||||
|
||||
def setDatasets(self, datasets: List[Dataset]) -> "FillResponse.Builder":
|
||||
self._response._datasets = list(datasets)
|
||||
return self
|
||||
|
||||
def setSaveInfo(self, save_info: SaveInfo) -> "FillResponse.Builder":
|
||||
self._response._save_info = save_info
|
||||
return self
|
||||
|
||||
def setClientState(self, client_state: Any) -> "FillResponse.Builder":
|
||||
self._response._client_state = client_state
|
||||
return self
|
||||
|
||||
def setAuthenticationResult(self, result: Any) -> "FillResponse.Builder":
|
||||
self._response._authentication_result = result
|
||||
return self
|
||||
|
||||
def build(self) -> FillResponse:
|
||||
return self._response
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SaveRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SaveRequest(Stub):
|
||||
"""SaveRequest — 保存请求"""
|
||||
|
||||
def __init__(self, request_id: int = 0,
|
||||
client_state: Optional[Any] = None,
|
||||
fill_contexts: Optional[List[Any]] = None):
|
||||
super().__init__()
|
||||
self._request_id = request_id
|
||||
self._client_state = client_state
|
||||
self._fill_contexts = fill_contexts or []
|
||||
|
||||
def getId(self) -> int:
|
||||
return self._request_id
|
||||
|
||||
def getClientState(self) -> Optional[Any]:
|
||||
return self._client_state
|
||||
|
||||
def getFillContexts(self) -> List[Any]:
|
||||
return list(self._fill_contexts)
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"SaveRequest(id={self._request_id}, "
|
||||
f"contexts={len(self._fill_contexts)})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._request_id & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, SaveRequest):
|
||||
return False
|
||||
return self._request_id == other._request_id
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AutofillService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AutofillService(Stub):
|
||||
"""AutofillService — 自动填充服务"""
|
||||
|
||||
SERVICE_INTERFACE = "android.service.autofill.AutofillService"
|
||||
SERVICE_META_DATA = "android.autofill"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onFillRequest(self, request: FillRequest,
|
||||
cancellation_signal: Any,
|
||||
callback: Any):
|
||||
"""填充请求回调"""
|
||||
response = FillResponse()
|
||||
if callback and hasattr(callback, 'onSuccess'):
|
||||
callback.onSuccess(response)
|
||||
|
||||
def onSaveRequest(self, request: SaveRequest, callback: Any):
|
||||
"""保存请求回调"""
|
||||
if callback and hasattr(callback, 'onSuccess'):
|
||||
callback.onSuccess()
|
||||
|
||||
def onConnected(self):
|
||||
"""服务已连接"""
|
||||
pass
|
||||
|
||||
def onDisconnected(self):
|
||||
"""服务已断开"""
|
||||
pass
|
||||
|
||||
def onUnbind(self, intent: Any) -> bool:
|
||||
return False
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AutofillService@{hex(id(self))}"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AutofillFieldClassificationService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AutofillFieldClassificationService(Stub):
|
||||
"""AutofillFieldClassificationService — 自动填充字段分类服务"""
|
||||
|
||||
SERVICE_INTERFACE = (
|
||||
"android.service.autofill.AutofillFieldClassificationService"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onConnected(self):
|
||||
pass
|
||||
|
||||
def onDisconnected(self):
|
||||
pass
|
||||
|
||||
def onClassificationRequest(self, request: Any, callback: Any):
|
||||
if callback and hasattr(callback, 'onSuccess'):
|
||||
callback.onSuccess(None)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AutofillFieldClassificationService@{hex(id(self))}"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"AutofillService",
|
||||
"FillRequest",
|
||||
"FillResponse",
|
||||
"SaveRequest",
|
||||
"SaveInfo",
|
||||
"Dataset",
|
||||
"AutofillFieldClassificationService",
|
||||
]
|
||||
@@ -0,0 +1,350 @@
|
||||
"""
|
||||
Android Service Controls 组件 — ControlsProviderService, Control, DeviceTypes,
|
||||
StatefulControl, StatelessControl 等
|
||||
方法签名完全对齐 Android SDK (android.service.controls 包)
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional, Set
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DeviceTypes
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DeviceTypes(Stub):
|
||||
"""DeviceTypes — 设备类型常量"""
|
||||
|
||||
TYPE_AC_HEATER = "AC_HEATER"
|
||||
TYPE_AC_UNIT = "AC_UNIT"
|
||||
TYPE_AIR_FRESHENER = "AIR_FRESHENER"
|
||||
TYPE_AIR_PURIFIER = "AIR_PURIFIER"
|
||||
TYPE_BLINDS = "BLINDS"
|
||||
TYPE_CAMERA = "CAMERA"
|
||||
TYPE_CLOSET = "CLOSET"
|
||||
TYPE_COFFEE_MAKER = "COFFEE_MAKER"
|
||||
TYPE_CURTAIN = "CURTAIN"
|
||||
TYPE_DEHUMIDIFIER = "DEHUMIDIFIER"
|
||||
TYPE_DISHWASHER = "DISHWASHER"
|
||||
TYPE_DISPLAY = "DISPLAY"
|
||||
TYPE_DOOR = "DOOR"
|
||||
TYPE_DOORBELL = "DOORBELL"
|
||||
TYPE_DRAWER = "DRAWER"
|
||||
TYPE_DRYER = "DRYER"
|
||||
TYPE_FAN = "FAN"
|
||||
TYPE_GARAGE = "GARAGE"
|
||||
TYPE_GATE = "GATE"
|
||||
TYPE_HEATER = "HEATER"
|
||||
TYPE_HOOD = "HOOD"
|
||||
TYPE_HUMIDIFIER = "HUMIDIFIER"
|
||||
TYPE_KETTLE = "KETTLE"
|
||||
TYPE_LIGHT = "LIGHT"
|
||||
TYPE_LIGHTBULB = "LIGHTBULB"
|
||||
TYPE_LOCK = "LOCK"
|
||||
TYPE_MICROWAVE = "MICROWAVE"
|
||||
TYPE_MOP = "MOP"
|
||||
TYPE_MOWER = "MOWER"
|
||||
TYPE_MULTICOOKER = "MULTICOOKER"
|
||||
TYPE_OUTLET = "OUTLET"
|
||||
TYPE_OVEN = "OVEN"
|
||||
TYPE_PERGOLLA = "PERGOLLA"
|
||||
TYPE_RADIATOR = "RADIATOR"
|
||||
TYPE_REFRIGERATOR = "REFRIGERATOR"
|
||||
TYPE_REMOTE_STAND = "REMOTE_STAND"
|
||||
TYPE_RICE_COOKER = "RICE_COOKER"
|
||||
TYPE_ROUTINE = "ROUTINE"
|
||||
TYPE_SCENE = "SCENE"
|
||||
TYPE_SECURITY_SYSTEM = "SECURITY_SYSTEM"
|
||||
TYPE_SET_TOP = "SET_TOP"
|
||||
TYPE_SHOWER = "SHOWER"
|
||||
TYPE_SHUTTER = "SHUTTER"
|
||||
TYPE_SMOKE_DETECTOR = "SMOKE_DETECTOR"
|
||||
TYPE_SOUNDBAR = "SOUNDBAR"
|
||||
TYPE_SPRINKLER = "SPRINKLER"
|
||||
TYPE_STANDMIXER = "STANDMIXER"
|
||||
TYPE_THERMOSTAT = "THERMOSTAT"
|
||||
TYPE_TV = "TV"
|
||||
TYPE_VACUUM = "VACUUM"
|
||||
TYPE_VALVE = "VALVE"
|
||||
TYPE_WASHER = "WASHER"
|
||||
TYPE_WATER_HEATER = "WATER_HEATER"
|
||||
TYPE_WINDOW = "WINDOW"
|
||||
TYPE_WINE_COOLER = "WINE_COOLER"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Control
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Control(Stub):
|
||||
"""Control — 设备控件基类"""
|
||||
|
||||
# 状态
|
||||
STATUS_UNKNOWN = 0
|
||||
STATUS_OK = 1
|
||||
STATUS_NOT_RESPONDING = 2
|
||||
STATUS_ERROR = 3
|
||||
STATUS_FIRMWARE_UPDATE = 4
|
||||
|
||||
def __init__(self, control_id: str, title: str,
|
||||
subtitle: str, device_type: str):
|
||||
super().__init__()
|
||||
self._control_id = control_id
|
||||
self._title = title
|
||||
self._subtitle = subtitle
|
||||
self._device_type = device_type
|
||||
self._status = Control.STATUS_OK
|
||||
self._status_text: str = ""
|
||||
self._custom_icon: Optional[Any] = None
|
||||
self._custom_color: int = 0
|
||||
self._is_auth_required: bool = False
|
||||
self._structure: str = ""
|
||||
self._zone: str = ""
|
||||
self._is_favorite: bool = False
|
||||
self._device_types_list: List[str] = [device_type]
|
||||
self._parent_id: Optional[str] = None
|
||||
|
||||
def getControlId(self) -> str:
|
||||
"""获取控件 ID"""
|
||||
return self._control_id
|
||||
|
||||
def getTitle(self) -> str:
|
||||
"""获取标题"""
|
||||
return self._title
|
||||
|
||||
def getSubtitle(self) -> str:
|
||||
"""获取副标题"""
|
||||
return self._subtitle
|
||||
|
||||
def getDeviceType(self) -> str:
|
||||
"""获取设备类型"""
|
||||
return self._device_type
|
||||
|
||||
def getStatus(self) -> int:
|
||||
return self._status
|
||||
|
||||
def setStatus(self, status: int):
|
||||
self._status = status
|
||||
|
||||
def getStatusText(self) -> str:
|
||||
return self._status_text
|
||||
|
||||
def setStatusText(self, status_text: str):
|
||||
self._status_text = status_text
|
||||
|
||||
def getStructure(self) -> str:
|
||||
return self._structure
|
||||
|
||||
def setStructure(self, structure: str):
|
||||
self._structure = structure
|
||||
|
||||
def getZone(self) -> str:
|
||||
return self._zone
|
||||
|
||||
def setZone(self, zone: str):
|
||||
self._zone = zone
|
||||
|
||||
def isFavorite(self) -> bool:
|
||||
return self._is_favorite
|
||||
|
||||
def setFavorite(self, favorite: bool):
|
||||
self._is_favorite = favorite
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Control(id={self._control_id}, title={self._title}, "
|
||||
f"type={self._device_type}, status={self._status})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._control_id) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Control):
|
||||
return False
|
||||
return self._control_id == other._control_id
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# StatefulControl
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StatefulControl(Control):
|
||||
"""StatefulControl — 有状态控件"""
|
||||
|
||||
def __init__(self, control_id: str, title: str,
|
||||
subtitle: str, device_type: str):
|
||||
super().__init__(control_id, title, subtitle, device_type)
|
||||
self._current_value: Any = None
|
||||
self._min_value: Optional[float] = None
|
||||
self._max_value: Optional[float] = None
|
||||
self._step: Optional[float] = None
|
||||
self._unit: Optional[str] = None
|
||||
self._is_on: bool = False
|
||||
self._mode: str = ""
|
||||
|
||||
def setStatus(self, status: int) -> "StatefulControl":
|
||||
"""设置状态"""
|
||||
self._status = status
|
||||
return self
|
||||
|
||||
def setStatusText(self, status_text: str) -> "StatefulControl":
|
||||
"""设置状态文本"""
|
||||
self._status_text = status_text
|
||||
return self
|
||||
|
||||
def getCurrentValue(self) -> Any:
|
||||
return self._current_value
|
||||
|
||||
def setCurrentValue(self, value: Any) -> "StatefulControl":
|
||||
self._current_value = value
|
||||
return self
|
||||
|
||||
def getMinValue(self) -> Optional[float]:
|
||||
return self._min_value
|
||||
|
||||
def setMinValue(self, min_value: float) -> "StatefulControl":
|
||||
self._min_value = min_value
|
||||
return self
|
||||
|
||||
def getMaxValue(self) -> Optional[float]:
|
||||
return self._max_value
|
||||
|
||||
def setMaxValue(self, max_value: float) -> "StatefulControl":
|
||||
self._max_value = max_value
|
||||
return self
|
||||
|
||||
def getStep(self) -> Optional[float]:
|
||||
return self._step
|
||||
|
||||
def setStep(self, step: float) -> "StatefulControl":
|
||||
self._step = step
|
||||
return self
|
||||
|
||||
def isOn(self) -> bool:
|
||||
return self._is_on
|
||||
|
||||
def setOn(self, is_on: bool) -> "StatefulControl":
|
||||
self._is_on = is_on
|
||||
return self
|
||||
|
||||
def getMode(self) -> str:
|
||||
return self._mode
|
||||
|
||||
def setMode(self, mode: str) -> "StatefulControl":
|
||||
self._mode = mode
|
||||
return self
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"StatefulControl(id={self._control_id}, title={self._title}, "
|
||||
f"type={self._device_type}, on={self._is_on}, "
|
||||
f"value={self._current_value})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# StatelessControl
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StatelessControl(Control):
|
||||
"""StatelessControl — 无状态控件"""
|
||||
|
||||
def __init__(self, control_id: str, title: str,
|
||||
subtitle: str, device_type: str):
|
||||
super().__init__(control_id, title, subtitle, device_type)
|
||||
self._action_text: str = ""
|
||||
|
||||
def getActionText(self) -> str:
|
||||
return self._action_text
|
||||
|
||||
def setActionText(self, action_text: str) -> "StatelessControl":
|
||||
self._action_text = action_text
|
||||
return self
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"StatelessControl(id={self._control_id}, "
|
||||
f"title={self._title}, type={self._device_type})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ControlsProviderService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ControlsProviderService(Stub):
|
||||
"""ControlsProviderService — 控件提供者服务"""
|
||||
|
||||
SERVICE_INTERFACE = "android.service.controls.ControlsProviderService"
|
||||
|
||||
# 控件类型
|
||||
CONTROL_TYPE_TOGGLE = 1
|
||||
CONTROL_TYPE_RANGE = 2
|
||||
CONTROL_TYPE_STATELESS = 3
|
||||
CONTROL_TYPE_BUTTON = 4
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._controls: Dict[str, Control] = {}
|
||||
self._publishers: Dict[str, Any] = {}
|
||||
self._subscribers: Dict[str, Any] = {}
|
||||
|
||||
def onCreatePublisherFor(self, control_ids: List[str]) -> Any:
|
||||
"""为指定控件创建发布者"""
|
||||
publisher = type("Publisher", (), {
|
||||
"onUnsubscribe": lambda: None,
|
||||
"onRequest": lambda cid: None,
|
||||
})()
|
||||
for cid in control_ids:
|
||||
self._publishers[cid] = publisher
|
||||
return publisher
|
||||
|
||||
def onGetSubscriberFor(self, control_ids: List[str]) -> Any:
|
||||
"""获取控件订阅者"""
|
||||
subscriber = type("Subscriber", (), {
|
||||
"onSubscribe": lambda cid: None,
|
||||
"onUnsubscribe": lambda cid: None,
|
||||
})()
|
||||
for cid in control_ids:
|
||||
self._subscribers[cid] = subscriber
|
||||
return subscriber
|
||||
|
||||
def publishControls(self, control_ids: List[str], controls: List[Control]):
|
||||
"""发布控件状态"""
|
||||
for control in controls:
|
||||
self._controls[control.getControlId()] = control
|
||||
|
||||
def addControl(self, control: Control):
|
||||
"""添加控件"""
|
||||
self._controls[control.getControlId()] = control
|
||||
|
||||
def removeControl(self, control_id: str):
|
||||
"""移除控件"""
|
||||
self._controls.pop(control_id, None)
|
||||
|
||||
def getControl(self, control_id: str) -> Optional[Control]:
|
||||
return self._controls.get(control_id)
|
||||
|
||||
def getControls(self) -> List[Control]:
|
||||
return list(self._controls.values())
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ControlsProviderService(controls={len(self._controls)})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"ControlsProviderService",
|
||||
"Control",
|
||||
"DeviceTypes",
|
||||
"StatefulControl",
|
||||
"StatelessControl",
|
||||
]
|
||||
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
Android Service Dreams 组件 — DreamService 等
|
||||
方法签名完全对齐 Android SDK (android.service.dreams 包)
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DreamService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DreamService(Stub):
|
||||
"""DreamService — 屏保服务基类"""
|
||||
|
||||
# 服务接口
|
||||
SERVICE_INTERFACE = "android.service.dreams.DreamService"
|
||||
SERVICE_META_DATA = "android.service.dream"
|
||||
|
||||
# 退出原因
|
||||
EXIT_CAUSE_UNKNOWN = 0
|
||||
EXIT_CAUSE_TIMEOUT = 1
|
||||
EXIT_CAUSE_USER = 2
|
||||
EXIT_CAUSE_DREAM_FINISHED = 3
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._fullscreen: bool = False
|
||||
self._interactive: bool = False
|
||||
self._dreaming: bool = False
|
||||
self._window: Optional[Any] = None
|
||||
self._content_view: Optional[Any] = None
|
||||
self._exit_cause: int = DreamService.EXIT_CAUSE_UNKNOWN
|
||||
self._created: bool = False
|
||||
self._screen_bright: bool = True
|
||||
self._doze_component: Optional[Any] = None
|
||||
self._doze_mode: bool = False
|
||||
self._debug: bool = False
|
||||
|
||||
def setContentView(self, view: Any):
|
||||
"""设置内容视图"""
|
||||
self._content_view = view
|
||||
|
||||
def getContentView(self) -> Optional[Any]:
|
||||
return self._content_view
|
||||
|
||||
def setFullscreen(self, fullscreen: bool):
|
||||
"""设置全屏模式"""
|
||||
self._fullscreen = fullscreen
|
||||
|
||||
def isFullscreen(self) -> bool:
|
||||
"""检查是否全屏"""
|
||||
return self._fullscreen
|
||||
|
||||
def isInteractive(self) -> bool:
|
||||
"""检查是否可交互"""
|
||||
return self._interactive
|
||||
|
||||
def setInteractive(self, interactive: bool):
|
||||
"""设置可交互"""
|
||||
self._interactive = interactive
|
||||
|
||||
def getWindow(self) -> Optional[Any]:
|
||||
"""获取窗口"""
|
||||
return self._window
|
||||
|
||||
def startDream(self):
|
||||
"""开始屏保"""
|
||||
self._dreaming = True
|
||||
|
||||
def awaken(self):
|
||||
"""唤醒/结束屏保"""
|
||||
self._dreaming = False
|
||||
|
||||
def onDreamingStarted(self):
|
||||
"""屏保开始时回调"""
|
||||
self._dreaming = True
|
||||
|
||||
def onDreamingStopped(self):
|
||||
"""屏保停止时回调"""
|
||||
self._dreaming = False
|
||||
|
||||
def onCreate(self):
|
||||
"""创建时回调"""
|
||||
self._created = True
|
||||
|
||||
def onDestroy(self):
|
||||
"""销毁时回调"""
|
||||
self._created = False
|
||||
self._dreaming = False
|
||||
|
||||
def onAttachedToWindow(self):
|
||||
"""附加到窗口时回调"""
|
||||
pass
|
||||
|
||||
def onDetachedFromWindow(self):
|
||||
"""从窗口分离时回调"""
|
||||
pass
|
||||
|
||||
def isDreaming(self) -> bool:
|
||||
"""检查是否正在运行屏保"""
|
||||
return self._dreaming
|
||||
|
||||
def getExitCause(self) -> int:
|
||||
return self._exit_cause
|
||||
|
||||
def setExitCause(self, cause: int):
|
||||
self._exit_cause = cause
|
||||
|
||||
def onWakeUp(self):
|
||||
"""唤醒回调"""
|
||||
self.awaken()
|
||||
|
||||
def canDoze(self) -> bool:
|
||||
return self._doze_mode
|
||||
|
||||
def setDozeComponent(self, component: Any):
|
||||
self._doze_component = component
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"DreamService(dreaming={self._dreaming}, "
|
||||
f"fullscreen={self._fullscreen}, "
|
||||
f"interactive={self._interactive})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"DreamService",
|
||||
]
|
||||
@@ -0,0 +1,378 @@
|
||||
"""
|
||||
Android Service Notification 组件 — NotificationListenerService, StatusBarNotification, RankingMap 等
|
||||
方法签名完全对齐 Android SDK (android.service.notification 包)
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# StatusBarNotification
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StatusBarNotification(Stub):
|
||||
"""StatusBarNotification — 状态栏通知"""
|
||||
|
||||
def __init__(self, pkg: str = "", op_pkg: str = "",
|
||||
id: int = 0, tag: Optional[str] = None,
|
||||
uid: int = 10001, pid: int = 1000,
|
||||
user: Any = None, notification: Any = None,
|
||||
post_time: int = 0):
|
||||
super().__init__()
|
||||
self._pkg = pkg
|
||||
self._op_pkg = op_pkg or pkg
|
||||
self._id = id
|
||||
self._tag = tag
|
||||
self._uid = uid
|
||||
self._pid = pid
|
||||
self._user = user
|
||||
self._notification = notification
|
||||
self._post_time = post_time or int(time.time() * 1000)
|
||||
self._is_clearable = True
|
||||
self._is_ongoing = False
|
||||
self._key = f"{pkg}|{id}|{tag or ''}"
|
||||
self._override_group_key: Optional[str] = None
|
||||
self._override_public = False
|
||||
self._initial_source_id = 0
|
||||
self._initial_source_uid = uid
|
||||
self._initial_source_pkg = pkg
|
||||
|
||||
def getPackageName(self) -> str:
|
||||
"""获取通知所属包名"""
|
||||
return self._pkg
|
||||
|
||||
def getOpPkg(self) -> str:
|
||||
return self._op_pkg
|
||||
|
||||
def getNotification(self) -> Any:
|
||||
"""获取 Notification 对象"""
|
||||
return self._notification
|
||||
|
||||
def getId(self) -> int:
|
||||
"""获取通知 ID"""
|
||||
return self._id
|
||||
|
||||
def getTag(self) -> Optional[str]:
|
||||
"""获取通知标签"""
|
||||
return self._tag
|
||||
|
||||
def getPostTime(self) -> int:
|
||||
"""获取发送时间 (毫秒)"""
|
||||
return self._post_time
|
||||
|
||||
def getUid(self) -> int:
|
||||
return self._uid
|
||||
|
||||
def getPid(self) -> int:
|
||||
return self._pid
|
||||
|
||||
def getUser(self) -> Any:
|
||||
return self._user
|
||||
|
||||
def getKey(self) -> str:
|
||||
return self._key
|
||||
|
||||
def isClearable(self) -> bool:
|
||||
"""是否可以清除"""
|
||||
return self._is_clearable
|
||||
|
||||
def isOngoing(self) -> bool:
|
||||
"""是否是持续通知"""
|
||||
return self._is_ongoing
|
||||
|
||||
def setClearable(self, clearable: bool):
|
||||
self._is_clearable = clearable
|
||||
|
||||
def setOngoing(self, ongoing: bool):
|
||||
self._is_ongoing = ongoing
|
||||
|
||||
def getOverrideGroupKey(self) -> Optional[str]:
|
||||
return self._override_group_key
|
||||
|
||||
def setOverrideGroupKey(self, override_group_key: Optional[str]):
|
||||
self._override_group_key = override_group_key
|
||||
|
||||
def isGroup(self) -> bool:
|
||||
return self._override_group_key is not None
|
||||
|
||||
def clone(self) -> "StatusBarNotification":
|
||||
"""克隆通知"""
|
||||
clone = StatusBarNotification(
|
||||
pkg=self._pkg, op_pkg=self._op_pkg, id=self._id,
|
||||
tag=self._tag, uid=self._uid, pid=self._pid,
|
||||
user=self._user, notification=self._notification,
|
||||
post_time=self._post_time,
|
||||
)
|
||||
clone._is_clearable = self._is_clearable
|
||||
clone._is_ongoing = self._is_ongoing
|
||||
clone._key = self._key
|
||||
clone._override_group_key = self._override_group_key
|
||||
return clone
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"StatusBarNotification(pkg={self._pkg}, id={self._id}, "
|
||||
f"tag={self._tag}, postTime={self._post_time})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._key) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, StatusBarNotification):
|
||||
return False
|
||||
return self._key == other._key
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NotificationListenerService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class NotificationListenerService(Stub):
|
||||
"""NotificationListenerService — 通知监听服务"""
|
||||
|
||||
# 取消原因
|
||||
REASON_LISTENER_CANCEL = 1
|
||||
REASON_CLICK = 2
|
||||
REASON_CANCEL = 3
|
||||
REASON_DELETE = 4
|
||||
REASON_PACKAGE_CHANGED = 5
|
||||
REASON_USER_STOPPED = 6
|
||||
REASON_PACKACE_BANNED = 7
|
||||
REASON_APP_CANCEL = 8
|
||||
REASON_APP_CANCEL_ALL = 9
|
||||
REASON_CHANNEL_BANNED = 10
|
||||
REASON_SNOOZED = 11
|
||||
REASON_TIMEOUT = 12
|
||||
REASON_ERROR = 13
|
||||
REASON_GROUP_SUMMARY_CANCELED = 14
|
||||
REASON_ASSISTANT_CANCEL = 15
|
||||
|
||||
# 中断过滤器
|
||||
INTERRUPTION_FILTER_NONE = 0
|
||||
INTERRUPTION_FILTER_PRIORITY = 1
|
||||
INTERRUPTION_FILTER_ALARMS = 2
|
||||
INTERRUPTION_FILTER_ALL = 3
|
||||
INTERRUPTION_FILTER_UNKNOWN = 4
|
||||
|
||||
# 提示
|
||||
HINT_HOST_DISABLE_CALL_EFFECTS = 1
|
||||
HINT_HOST_DISABLE_EFFECTS = 2
|
||||
HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 3
|
||||
|
||||
# SUW 组件
|
||||
TRAMPOLINE_ACTIVITY_CLASS = (
|
||||
"com.android.settings.Settings$NotificationAppListActivity"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._active_notifications: Dict[str, StatusBarNotification] = {}
|
||||
self._keys_list: List[str] = []
|
||||
self._interruption_filter: int = (
|
||||
NotificationListenerService.INTERRUPTION_FILTER_ALL
|
||||
)
|
||||
self._ranking_map: Optional["NotificationListenerService.RankingMap"] = None
|
||||
self._is_listener_bound = False
|
||||
|
||||
@staticmethod
|
||||
def requestRebind(component_name: Any):
|
||||
"""请求重新绑定通知监听器"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def requestUnbind(component_name: Any):
|
||||
"""请求解绑通知监听器"""
|
||||
pass
|
||||
|
||||
def onNotificationPosted(self, sbn: StatusBarNotification,
|
||||
ranking_map: Optional["RankingMap"] = None):
|
||||
"""通知被发送时回调"""
|
||||
if sbn:
|
||||
self._active_notifications[sbn.getKey()] = sbn
|
||||
self._keys_list = list(self._active_notifications.keys())
|
||||
if ranking_map:
|
||||
self._ranking_map = ranking_map
|
||||
|
||||
def onNotificationRemoved(self, sbn: StatusBarNotification,
|
||||
ranking_map: Optional["RankingMap"] = None,
|
||||
reason: int = REASON_LISTENER_CANCEL):
|
||||
"""通知被移除时回调"""
|
||||
if sbn:
|
||||
key = sbn.getKey()
|
||||
self._active_notifications.pop(key, None)
|
||||
if key in self._keys_list:
|
||||
self._keys_list.remove(key)
|
||||
|
||||
def onListenerConnected(self):
|
||||
"""监听器已连接"""
|
||||
self._is_listener_bound = True
|
||||
|
||||
def onListenerDisconnected(self):
|
||||
"""监听器已断开"""
|
||||
self._is_listener_bound = False
|
||||
|
||||
def onNotificationRemovedReason(self, sbn: StatusBarNotification,
|
||||
ranking_map: "RankingMap",
|
||||
reason: int):
|
||||
"""通知被移除 (带原因)"""
|
||||
self.onNotificationRemoved(sbn, ranking_map, reason)
|
||||
|
||||
def onNotificationPostedWithRankingMap(self, sbn: StatusBarNotification,
|
||||
ranking_map: "RankingMap"):
|
||||
"""通知被发送 (带排名映射)"""
|
||||
self.onNotificationPosted(sbn, ranking_map)
|
||||
|
||||
def getActiveNotifications(self) -> List[StatusBarNotification]:
|
||||
"""获取活动通知列表"""
|
||||
return list(self._active_notifications.values())
|
||||
|
||||
def getActiveNotificationsWithRankingMap(self) -> Tuple[
|
||||
List[StatusBarNotification],
|
||||
Optional["RankingMap"]]:
|
||||
"""获取活动通知及排名映射"""
|
||||
return list(self._active_notifications.values()), self._ranking_map
|
||||
|
||||
def getCurrentInterruptionFilter(self) -> int:
|
||||
"""获取当前中断过滤器"""
|
||||
return self._interruption_filter
|
||||
|
||||
def setInterruptionFilter(self, interruption_filter: int):
|
||||
"""设置中断过滤器"""
|
||||
self._interruption_filter = interruption_filter
|
||||
|
||||
def cancelNotification(self, key: str):
|
||||
"""取消指定通知"""
|
||||
self._active_notifications.pop(key, None)
|
||||
if key in self._keys_list:
|
||||
self._keys_list.remove(key)
|
||||
|
||||
def cancelAllNotifications(self):
|
||||
"""取消所有通知"""
|
||||
self._active_notifications.clear()
|
||||
self._keys_list.clear()
|
||||
|
||||
def requestRebind(self, component_name: Any):
|
||||
"""请求重新绑定"""
|
||||
pass
|
||||
|
||||
def requestUnbind(self):
|
||||
"""请求解绑"""
|
||||
pass
|
||||
|
||||
def registerAsSystemService(self, context: Any, component_name: Any,
|
||||
user: int):
|
||||
pass
|
||||
|
||||
def unregisterAsSystemService(self):
|
||||
pass
|
||||
|
||||
def isBound(self) -> bool:
|
||||
return self._is_listener_bound
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"NotificationListenerService("
|
||||
f"notifications={len(self._active_notifications)}, "
|
||||
f"filter={self._interruption_filter})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
# ── RankingMap ──
|
||||
|
||||
class RankingMap(Stub):
|
||||
"""RankingMap — 通知排名映射"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._rankings: Dict[str, "NotificationListenerService.Ranking"] = {}
|
||||
|
||||
def getOrderedKeys(self) -> List[str]:
|
||||
"""获取有序键列表"""
|
||||
return list(self._rankings.keys())
|
||||
|
||||
def getRanking(self, key: str) -> Optional["NotificationListenerService.Ranking"]:
|
||||
"""获取指定通知的排名信息"""
|
||||
return self._rankings.get(key)
|
||||
|
||||
def getRawRanking(self, key: str) -> Optional[Any]:
|
||||
return self._rankings.get(key)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"RankingMap(keys={len(self._rankings)})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(tuple(self._rankings.keys())) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, NotificationListenerService.RankingMap):
|
||||
return False
|
||||
return self._rankings == other._rankings
|
||||
|
||||
class Ranking(Stub):
|
||||
"""Ranking — 通知排名"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._key: str = ""
|
||||
self._rank: int = 0
|
||||
self._is_ambient: bool = False
|
||||
self._matches_interruption_filter: bool = True
|
||||
self._suppressed_visual_effects: int = 0
|
||||
self._importance: int = 3 # IMPORTANCE_DEFAULT
|
||||
self._importance_explanation: Optional[str] = None
|
||||
self._group_key: Optional[str] = None
|
||||
self._override_group_key: Optional[str] = None
|
||||
self._channel: Optional[Any] = None
|
||||
self._is_suspended: bool = False
|
||||
|
||||
def getKey(self) -> str:
|
||||
return self._key
|
||||
|
||||
def getRank(self) -> int:
|
||||
return self._rank
|
||||
|
||||
def isAmbient(self) -> bool:
|
||||
return self._is_ambient
|
||||
|
||||
def matchesInterruptionFilter(self) -> bool:
|
||||
return self._matches_interruption_filter
|
||||
|
||||
def getSuppressedVisualEffects(self) -> int:
|
||||
return self._suppressed_visual_effects
|
||||
|
||||
def getImportance(self) -> int:
|
||||
return self._importance
|
||||
|
||||
def getImportanceExplanation(self) -> Optional[str]:
|
||||
return self._importance_explanation
|
||||
|
||||
def getGroupKey(self) -> Optional[str]:
|
||||
return self._group_key
|
||||
|
||||
def getOverrideGroupKey(self) -> Optional[str]:
|
||||
return self._override_group_key
|
||||
|
||||
def getChannel(self) -> Optional[Any]:
|
||||
return self._channel
|
||||
|
||||
def isSuspended(self) -> bool:
|
||||
return self._is_suspended
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Ranking(key={self._key}, rank={self._rank}, "
|
||||
f"importance={self._importance})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"NotificationListenerService",
|
||||
"StatusBarNotification",
|
||||
]
|
||||
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
Android Service QuickSettings 组件 — TileService, Tile 等
|
||||
方法签名完全对齐 Android SDK (android.service.quicksettings 包)
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Tile
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Tile(Stub):
|
||||
"""Tile — 快捷设置磁贴"""
|
||||
|
||||
# 状态常量
|
||||
STATE_ACTIVE = 1
|
||||
STATE_INACTIVE = 2
|
||||
STATE_UNAVAILABLE = 0
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._state: int = Tile.STATE_INACTIVE
|
||||
self._label: str = ""
|
||||
self._subtitle: str = ""
|
||||
self._content_description: str = ""
|
||||
self._icon: Optional[Any] = None
|
||||
self._icon_res_id: int = 0
|
||||
self._meta_data: Optional[Dict[str, Any]] = None
|
||||
self._last_updated: int = 0
|
||||
|
||||
def getState(self) -> int:
|
||||
"""获取磁贴状态"""
|
||||
return self._state
|
||||
|
||||
def setState(self, state: int):
|
||||
"""设置磁贴状态"""
|
||||
self._state = state
|
||||
self._last_updated = int(time.time() * 1000)
|
||||
|
||||
def getLabel(self) -> str:
|
||||
"""获取标签"""
|
||||
return self._label
|
||||
|
||||
def setLabel(self, label: str):
|
||||
"""设置标签"""
|
||||
self._label = label
|
||||
self._last_updated = int(time.time() * 1000)
|
||||
|
||||
def getSubtitle(self) -> str:
|
||||
"""获取副标题"""
|
||||
return self._subtitle
|
||||
|
||||
def setSubtitle(self, subtitle: str):
|
||||
"""设置副标题"""
|
||||
self._subtitle = subtitle
|
||||
self._last_updated = int(time.time() * 1000)
|
||||
|
||||
def getContentDescription(self) -> str:
|
||||
"""获取内容描述"""
|
||||
return self._content_description
|
||||
|
||||
def setContentDescription(self, content_description: str):
|
||||
"""设置内容描述"""
|
||||
self._content_description = content_description
|
||||
|
||||
def getIcon(self) -> Optional[Any]:
|
||||
"""获取图标"""
|
||||
return self._icon
|
||||
|
||||
def setIcon(self, icon: Any):
|
||||
"""设置图标"""
|
||||
self._icon = icon
|
||||
self._last_updated = int(time.time() * 1000)
|
||||
|
||||
def updateTile(self):
|
||||
"""更新磁贴 (通知系统刷新)"""
|
||||
self._last_updated = int(time.time() * 1000)
|
||||
|
||||
def getMetaData(self) -> Optional[Dict[str, Any]]:
|
||||
return self._meta_data
|
||||
|
||||
def setMetaData(self, meta_data: Dict[str, Any]):
|
||||
self._meta_data = meta_data
|
||||
|
||||
def toString(self) -> str:
|
||||
state_names = {
|
||||
Tile.STATE_ACTIVE: "ACTIVE",
|
||||
Tile.STATE_INACTIVE: "INACTIVE",
|
||||
Tile.STATE_UNAVAILABLE: "UNAVAILABLE",
|
||||
}
|
||||
return (f"Tile(label={self._label}, state="
|
||||
f"{state_names.get(self._state, self._state)})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._label, self._state)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Tile):
|
||||
return False
|
||||
return (self._label == other._label and self._state == other._state)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TileService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TileService(Stub):
|
||||
"""TileService — 快捷设置磁贴服务"""
|
||||
|
||||
# 动作
|
||||
ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE"
|
||||
ACTION_REQUEST_LISTENING = (
|
||||
"android.service.quicksettings.action.REQUEST_LISTENING"
|
||||
)
|
||||
META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._qs_tile: Optional[Tile] = None
|
||||
self._locked: bool = False
|
||||
self._secure: bool = False
|
||||
self._listening: bool = False
|
||||
self._tile_added: bool = False
|
||||
self._tile_removed: bool = False
|
||||
|
||||
@staticmethod
|
||||
def requestListeningState(context: Any, component: Any):
|
||||
"""请求监听状态 (Android-only)"""
|
||||
pass
|
||||
|
||||
def isLocked(self) -> bool:
|
||||
"""检查设备是否锁定"""
|
||||
return self._locked
|
||||
|
||||
def isSecure(self) -> bool:
|
||||
"""检查设备是否安全锁定"""
|
||||
return self._secure
|
||||
|
||||
def startActivity(self, intent: Any):
|
||||
"""启动 Activity (Android-only)"""
|
||||
raise AndroidOnlyError(
|
||||
"TileService.startActivity() 仅在 Android 设备上可用"
|
||||
)
|
||||
|
||||
def showDialog(self, dialog: Any):
|
||||
"""显示对话框"""
|
||||
pass
|
||||
|
||||
def getQsTile(self) -> Optional[Tile]:
|
||||
"""获取快速设置磁贴"""
|
||||
return self._qs_tile
|
||||
|
||||
def setQsTile(self, tile: Tile):
|
||||
self._qs_tile = tile
|
||||
|
||||
def unlockAndRun(self, runnable: Callable):
|
||||
"""解锁并运行"""
|
||||
runnable()
|
||||
|
||||
def onTileAdded(self):
|
||||
"""磁贴被添加时回调"""
|
||||
self._tile_added = True
|
||||
|
||||
def onTileRemoved(self):
|
||||
"""磁贴被移除时回调"""
|
||||
self._tile_removed = True
|
||||
|
||||
def onStartListening(self):
|
||||
"""开始监听时回调"""
|
||||
self._listening = True
|
||||
|
||||
def onStopListening(self):
|
||||
"""停止监听时回调"""
|
||||
self._listening = False
|
||||
|
||||
def onClick(self):
|
||||
"""被点击时回调"""
|
||||
if self._qs_tile:
|
||||
current_state = self._qs_tile.getState()
|
||||
if current_state == Tile.STATE_ACTIVE:
|
||||
self._qs_tile.setState(Tile.STATE_INACTIVE)
|
||||
elif current_state == Tile.STATE_INACTIVE:
|
||||
self._qs_tile.setState(Tile.STATE_ACTIVE)
|
||||
|
||||
def onDestroy(self):
|
||||
"""销毁时回调"""
|
||||
pass
|
||||
|
||||
def isListening(self) -> bool:
|
||||
return self._listening
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"TileService(locked={self._locked}, secure={self._secure}, "
|
||||
f"listening={self._listening})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"TileService",
|
||||
"Tile",
|
||||
]
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Android Service VR 组件 — VrListenerService 等
|
||||
方法签名完全对齐 Android SDK (android.service.vr 包)
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# VrListenerService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class VrListenerService(Stub):
|
||||
"""VrListenerService — VR 监听服务 (已弃用, 仅存根)"""
|
||||
|
||||
SERVICE_INTERFACE = "android.service.vr.VrListenerService"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._current_vr_activity: Optional[str] = None
|
||||
self._is_listening: bool = False
|
||||
|
||||
def onCurrentVrActivityChanged(self, component: Any):
|
||||
"""当前 VR Activity 变化时回调"""
|
||||
if component:
|
||||
self._current_vr_activity = str(component)
|
||||
|
||||
def getCurrentVrActivity(self) -> Optional[str]:
|
||||
"""获取当前 VR Activity"""
|
||||
return self._current_vr_activity
|
||||
|
||||
def onConnected(self):
|
||||
"""服务连接回调"""
|
||||
self._is_listening = True
|
||||
|
||||
def onDisconnected(self):
|
||||
"""服务断开回调"""
|
||||
self._is_listening = False
|
||||
|
||||
def isListening(self) -> bool:
|
||||
return self._is_listening
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"VrListenerService("
|
||||
f"activity={self._current_vr_activity})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"VrListenerService",
|
||||
]
|
||||
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
Android Service Wallpaper 组件 — WallpaperService, Engine, SurfaceHolder 等
|
||||
方法签名完全对齐 Android SDK (android.service.wallpaper 包)
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SurfaceHolder
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SurfaceHolder(Stub):
|
||||
"""SurfaceHolder — 表面持有者 (桌面存根)"""
|
||||
|
||||
# 表面类型
|
||||
SURFACE_TYPE_NORMAL = 0
|
||||
SURFACE_TYPE_HARDWARE = 1
|
||||
SURFACE_TYPE_GPU = 2
|
||||
SURFACE_TYPE_PUSH_BUFFERS = 3
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._surface: Optional[Any] = None
|
||||
self._surface_frame: Optional[Any] = None
|
||||
self._surface_type: int = SurfaceHolder.SURFACE_TYPE_NORMAL
|
||||
self._fixed_size: Optional[Tuple[int, int]] = None
|
||||
self._format: int = 0 # PixelFormat.UNKNOWN
|
||||
self._callbacks: List[Any] = []
|
||||
|
||||
def getSurface(self) -> Any:
|
||||
"""获取 Surface"""
|
||||
return self._surface
|
||||
|
||||
def getSurfaceFrame(self) -> Any:
|
||||
return self._surface_frame
|
||||
|
||||
def isCreating(self) -> bool:
|
||||
return False
|
||||
|
||||
def setType(self, surface_type: int):
|
||||
self._surface_type = surface_type
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._surface_type
|
||||
|
||||
def setFormat(self, format: int):
|
||||
self._format = format
|
||||
|
||||
def getFormat(self) -> int:
|
||||
return self._format
|
||||
|
||||
def setFixedSize(self, width: int, height: int):
|
||||
self._fixed_size = (width, height)
|
||||
|
||||
def setSizeFromLayout(self):
|
||||
pass
|
||||
|
||||
def addCallback(self, callback: Any):
|
||||
if callback not in self._callbacks:
|
||||
self._callbacks.append(callback)
|
||||
|
||||
def removeCallback(self, callback: Any):
|
||||
if callback in self._callbacks:
|
||||
self._callbacks.remove(callback)
|
||||
|
||||
def notifySurfaceCreated(self):
|
||||
for cb in self._callbacks:
|
||||
if hasattr(cb, 'surfaceCreated'):
|
||||
cb.surfaceCreated(self)
|
||||
|
||||
def notifySurfaceChanged(self, format: int, width: int, height: int):
|
||||
for cb in self._callbacks:
|
||||
if hasattr(cb, 'surfaceChanged'):
|
||||
cb.surfaceChanged(self, format, width, height)
|
||||
|
||||
def notifySurfaceDestroyed(self):
|
||||
for cb in self._callbacks:
|
||||
if hasattr(cb, 'surfaceDestroyed'):
|
||||
cb.surfaceDestroyed(self)
|
||||
|
||||
def lockCanvas(self) -> Any:
|
||||
"""锁定画布"""
|
||||
return None
|
||||
|
||||
def lockCanvas(self, dirty: Any) -> Any:
|
||||
"""锁定画布 (指定脏区域)"""
|
||||
return None
|
||||
|
||||
def unlockCanvasAndPost(self, canvas: Any):
|
||||
"""解锁并提交画布"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
size_str = (f"{self._fixed_size[0]}x{self._fixed_size[1]}"
|
||||
if self._fixed_size else "dynamic")
|
||||
return f"SurfaceHolder(size={size_str}, callbacks={len(self._callbacks)})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# WallpaperService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class WallpaperService(Stub):
|
||||
"""WallpaperService — 动态壁纸服务基类"""
|
||||
|
||||
# 服务接口
|
||||
SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService"
|
||||
ACTION_LIVE_WALLPAPER_CHOOSER = (
|
||||
"android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"
|
||||
)
|
||||
|
||||
# 元数据
|
||||
META_DATA_WALLPAPER = "android.service.wallpaper"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onCreate(self):
|
||||
"""创建壁纸服务"""
|
||||
pass
|
||||
|
||||
def onCreateEngine(self) -> "WallpaperService.Engine":
|
||||
"""创建壁纸引擎"""
|
||||
return WallpaperService.Engine(self)
|
||||
|
||||
def onDestroy(self):
|
||||
"""销毁壁纸服务"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"WallpaperService@{hex(id(self))}"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
# ── Engine ──
|
||||
|
||||
class Engine(Stub):
|
||||
"""Engine — 壁纸引擎"""
|
||||
|
||||
def __init__(self, service: "WallpaperService" = None):
|
||||
super().__init__()
|
||||
self._service = service
|
||||
self._is_visible: bool = True
|
||||
self._is_created: bool = False
|
||||
self._surface_holder = SurfaceHolder()
|
||||
self._surface_holder.addCallback(self)
|
||||
self._offset: Tuple[float, float, float, float, float] = (0, 0, 0, 0, 0)
|
||||
self._x_ppw: float = 1.0
|
||||
self._y_ppw: float = 1.0
|
||||
self._desired_width: int = 0
|
||||
self._desired_height: int = 0
|
||||
self._touch_events_enabled: bool = False
|
||||
self._offset_notifications_enabled: bool = False
|
||||
self._draw_count: int = 0
|
||||
|
||||
def onCreate(self, surface_holder: SurfaceHolder):
|
||||
"""创建引擎"""
|
||||
self._is_created = True
|
||||
|
||||
def onSurfaceCreated(self, holder: SurfaceHolder):
|
||||
"""表面创建时回调"""
|
||||
pass
|
||||
|
||||
def onSurfaceDestroyed(self, holder: SurfaceHolder):
|
||||
"""表面销毁时回调"""
|
||||
pass
|
||||
|
||||
def onSurfaceChanged(self, holder: SurfaceHolder,
|
||||
format: int, width: int, height: int):
|
||||
"""表面尺寸变化时回调"""
|
||||
pass
|
||||
|
||||
def onDraw(self, canvas: Any):
|
||||
"""绘制壁纸"""
|
||||
self._draw_count += 1
|
||||
|
||||
def onOffsetsChanged(self, x_offset: float, y_offset: float,
|
||||
x_step: float, y_step: float,
|
||||
x_pixel_offset: int, y_pixel_offset: int):
|
||||
"""偏移量变化回调"""
|
||||
self._offset = (x_offset, y_offset, x_step, y_step)
|
||||
|
||||
def onTouchEvent(self, motion_event: Any):
|
||||
"""触摸事件回调"""
|
||||
pass
|
||||
|
||||
def onVisibilityChanged(self, visible: bool):
|
||||
"""可见性变化回调"""
|
||||
self._is_visible = visible
|
||||
|
||||
def setOffsetNotifications(self, x_ppw: float, y_ppw: float):
|
||||
"""设置偏移通知"""
|
||||
self._x_ppw = x_ppw
|
||||
self._y_ppw = y_ppw
|
||||
self._offset_notifications_enabled = True
|
||||
|
||||
def getSurfaceHolder(self) -> SurfaceHolder:
|
||||
"""获取表面持有者"""
|
||||
return self._surface_holder
|
||||
|
||||
def getDesiredMinimumWidth(self) -> int:
|
||||
return self._desired_width
|
||||
|
||||
def getDesiredMinimumHeight(self) -> int:
|
||||
return self._desired_height
|
||||
|
||||
def isVisible(self) -> bool:
|
||||
return self._is_visible
|
||||
|
||||
def isPreview(self) -> bool:
|
||||
return False
|
||||
|
||||
def setTouchEventsEnabled(self, enabled: bool):
|
||||
self._touch_events_enabled = enabled
|
||||
|
||||
def setCreateSurfaceFromSurfaceControlResult(self, result: Any):
|
||||
pass
|
||||
|
||||
def surfaceCreated(self, holder: SurfaceHolder):
|
||||
"""SurfaceHolder.Callback — 表面创建"""
|
||||
self.onSurfaceCreated(holder)
|
||||
|
||||
def surfaceChanged(self, holder: SurfaceHolder,
|
||||
format: int, width: int, height: int):
|
||||
"""SurfaceHolder.Callback — 表面变化"""
|
||||
self.onSurfaceChanged(holder, format, width, height)
|
||||
|
||||
def surfaceDestroyed(self, holder: SurfaceHolder):
|
||||
"""SurfaceHolder.Callback — 表面销毁"""
|
||||
self.onSurfaceDestroyed(holder)
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Engine(visible={self._is_visible}, "
|
||||
f"draws={self._draw_count})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"WallpaperService",
|
||||
"SurfaceHolder",
|
||||
]
|
||||
@@ -0,0 +1,519 @@
|
||||
"""
|
||||
Android SIP API — SipManager, SipProfile, SipSession, SipAudioCall
|
||||
(已废弃但存在于 SDK 中)
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"SipManager",
|
||||
"SipProfile",
|
||||
"SipSession",
|
||||
"SipAudioCall",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SipManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SipManager(Stub):
|
||||
"""SipManager — SIP 会话管理器 (桌面存根, 已废弃)"""
|
||||
|
||||
EXTRA_CALL_ID = "android.sip.extra.CALL_ID"
|
||||
EXTRA_OFFER_SD = "android.sip.extra.OFFER_SD"
|
||||
EXTRA_PEER_PROFILE = "android.sip.extra.PEER_PROFILE"
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._opened = False
|
||||
self._registered = False
|
||||
self._session: Optional["SipSession"] = None
|
||||
self._listener: Optional[Callable] = None
|
||||
|
||||
@staticmethod
|
||||
def isSipWifiOnly(context: Any) -> bool:
|
||||
"""检查 SIP 是否仅限 WiFi"""
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def isVoipSupported(context: Any) -> bool:
|
||||
"""检查是否支持 VoIP"""
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def isApiSupported(context: Any) -> bool:
|
||||
"""检查 SIP API 是否支持"""
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def newInstance(context: Any) -> "SipManager":
|
||||
"""创建新实例"""
|
||||
return SipManager(context)
|
||||
|
||||
def open(self, localProfile: "SipProfile") -> "SipSession":
|
||||
"""打开 SIP 会话"""
|
||||
self._opened = True
|
||||
self._session = SipSession(localProfile)
|
||||
return self._session
|
||||
|
||||
def setRegistrationListener(
|
||||
self, localProfileUri: str, listener: "SipSession.Listener"
|
||||
):
|
||||
"""设置注册监听器"""
|
||||
if self._session:
|
||||
self._session.setListener(listener)
|
||||
|
||||
def close(self, localProfileUri: str):
|
||||
"""关闭 SIP 会话"""
|
||||
self._opened = False
|
||||
self._registered = False
|
||||
self._session = None
|
||||
|
||||
def getSessionFor(self, intent: Any) -> Optional["SipSession"]:
|
||||
"""获取 Intent 对应的 SIP 会话"""
|
||||
return self._session
|
||||
|
||||
def register(
|
||||
self, localProfile: "SipProfile", expiryTime: int, listener: "SipSession.Listener"
|
||||
):
|
||||
"""注册 SIP"""
|
||||
raise AndroidOnlyError("SipManager.register() 仅在 Android 设备上可用")
|
||||
|
||||
def unregister(
|
||||
self, localProfile: "SipProfile", listener: "SipSession.Listener"
|
||||
):
|
||||
"""注销 SIP"""
|
||||
raise AndroidOnlyError("SipManager.unregister() 仅在 Android 设备上可用")
|
||||
|
||||
def isOpened(self, localProfileUri: str) -> bool:
|
||||
"""检查是否已打开"""
|
||||
return self._opened
|
||||
|
||||
def isRegistered(self, localProfileUri: str) -> bool:
|
||||
"""检查是否已注册"""
|
||||
return self._registered
|
||||
|
||||
def createSipSessionIfNeeded(self, localProfile: "SipProfile") -> "SipSession":
|
||||
"""如有必要则创建 SIP 会话"""
|
||||
if self._session is None:
|
||||
self._session = SipSession(localProfile)
|
||||
return self._session
|
||||
|
||||
def createSipProfile(self, intent: Any) -> Optional["SipProfile"]:
|
||||
"""从 Intent 创建 SIP 配置文件"""
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SipManager(opened={self._opened}, registered={self._registered})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SipProfile
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SipProfile(Stub):
|
||||
"""SipProfile — SIP 配置文件"""
|
||||
|
||||
# --- Protocol constants ---
|
||||
PROTOCOL_UDP = "UDP"
|
||||
PROTOCOL_TCP = "TCP"
|
||||
PROTOCOL_TLS = "TLS"
|
||||
|
||||
class Builder(Stub):
|
||||
"""SipProfile.Builder — SIP 配置文件构建器"""
|
||||
|
||||
def __init__(self, uri: str = ""):
|
||||
super().__init__()
|
||||
self._profile = SipProfile()
|
||||
self._profile._uri = uri
|
||||
|
||||
def setProfileName(self, name: str) -> "SipProfile.Builder":
|
||||
self._profile._profile_name = name
|
||||
return self
|
||||
|
||||
def setUserName(self, userName: str) -> "SipProfile.Builder":
|
||||
self._profile._user_name = userName
|
||||
return self
|
||||
|
||||
def setDomain(self, domain: str) -> "SipProfile.Builder":
|
||||
self._profile._domain = domain
|
||||
return self
|
||||
|
||||
def setPassword(self, password: str) -> "SipProfile.Builder":
|
||||
self._profile._password = password
|
||||
return self
|
||||
|
||||
def setPort(self, port: int) -> "SipProfile.Builder":
|
||||
self._profile._port = port
|
||||
return self
|
||||
|
||||
def setProtocol(self, protocol: str) -> "SipProfile.Builder":
|
||||
self._profile._protocol = protocol
|
||||
return self
|
||||
|
||||
def setSendKeepAlive(self, enabled: bool) -> "SipProfile.Builder":
|
||||
self._profile._send_keep_alive = enabled
|
||||
return self
|
||||
|
||||
def setAutoRegistration(self, enabled: bool) -> "SipProfile.Builder":
|
||||
self._profile._auto_registration = enabled
|
||||
return self
|
||||
|
||||
def setDisplayName(self, displayName: str) -> "SipProfile.Builder":
|
||||
self._profile._display_name = displayName
|
||||
return self
|
||||
|
||||
def setAuthUserName(self, authUserName: str) -> "SipProfile.Builder":
|
||||
self._profile._auth_user_name = authUserName
|
||||
return self
|
||||
|
||||
def build(self) -> "SipProfile":
|
||||
return self._profile
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SipProfile.Builder(name={self._profile._profile_name})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._uri = ""
|
||||
self._profile_name = ""
|
||||
self._user_name = ""
|
||||
self._domain = ""
|
||||
self._display_name = ""
|
||||
self._password = ""
|
||||
self._protocol = self.PROTOCOL_UDP
|
||||
self._port = 5060
|
||||
self._send_keep_alive = False
|
||||
self._auto_registration = True
|
||||
self._auth_user_name = ""
|
||||
|
||||
def getProfileName(self) -> str:
|
||||
return self._profile_name
|
||||
|
||||
def getUserName(self) -> str:
|
||||
return self._user_name
|
||||
|
||||
def getDomain(self) -> str:
|
||||
return self._domain
|
||||
|
||||
def getDisplayName(self) -> str:
|
||||
return self._display_name
|
||||
|
||||
def getPassword(self) -> str:
|
||||
return self._password
|
||||
|
||||
def getProtocol(self) -> str:
|
||||
return self._protocol
|
||||
|
||||
def getPort(self) -> int:
|
||||
return self._port
|
||||
|
||||
def getSendKeepAlive(self) -> bool:
|
||||
return self._send_keep_alive
|
||||
|
||||
def isAutoRegistration(self) -> bool:
|
||||
return self._auto_registration
|
||||
|
||||
def getUri(self) -> str:
|
||||
return self._uri
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"SipProfile(name={self._profile_name}, "
|
||||
f"user={self._user_name}@{self._domain})")
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, SipProfile):
|
||||
return False
|
||||
return self._uri == other._uri
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._uri) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SipSession
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SipSession(Stub):
|
||||
"""SipSession — SIP 会话"""
|
||||
|
||||
# --- State constants ---
|
||||
STATE_READY_TO_CALL = 0
|
||||
STATE_INCOMING_CALL = 1
|
||||
STATE_INCOMING_CALL_ANSWERING = 2
|
||||
STATE_IN_CALL = 3
|
||||
STATE_OUTGOING_CALL = 4
|
||||
STATE_OUTGOING_CALL_RINGING = 5
|
||||
STATE_OUTGOING_CALL_CANCELING = 6
|
||||
STATE_PINGING = 7
|
||||
STATE_DEREGISTERING = 8
|
||||
STATE_CLOSED = 9
|
||||
|
||||
def __init__(self, profile: "SipProfile" = None):
|
||||
super().__init__()
|
||||
self._profile = profile
|
||||
self._listener: Optional["SipSession.Listener"] = None
|
||||
self._state = self.STATE_CLOSED
|
||||
self._call_id = ""
|
||||
self._peer_profile: Optional[SipProfile] = None
|
||||
self._in_call = False
|
||||
|
||||
def setListener(self, listener: "SipSession.Listener"):
|
||||
"""设置会话监听器"""
|
||||
self._listener = listener
|
||||
|
||||
def register(self, duration: int):
|
||||
"""注册 SIP"""
|
||||
raise AndroidOnlyError("SipSession.register() 仅在 Android 设备上可用")
|
||||
|
||||
def unregister(self):
|
||||
"""注销 SIP"""
|
||||
raise AndroidOnlyError("SipSession.unregister() 仅在 Android 设备上可用")
|
||||
|
||||
def makeAudioCall(
|
||||
self, localProfile: "SipProfile", peerProfile: "SipProfile"
|
||||
):
|
||||
"""发起音频呼叫"""
|
||||
raise AndroidOnlyError("SipSession.makeAudioCall() 仅在 Android 设备上可用")
|
||||
|
||||
def answerCall(self, callId: str, sessionDescription: int):
|
||||
"""接听呼叫"""
|
||||
raise AndroidOnlyError("SipSession.answerCall() 仅在 Android 设备上可用")
|
||||
|
||||
def endCall(self):
|
||||
"""结束呼叫"""
|
||||
raise AndroidOnlyError("SipSession.endCall() 仅在 Android 设备上可用")
|
||||
|
||||
def changeCall(self, callId: str, sessionDescription: int):
|
||||
"""更改呼叫"""
|
||||
raise AndroidOnlyError("SipSession.changeCall() 仅在 Android 设备上可用")
|
||||
|
||||
def getCallId(self) -> str:
|
||||
return self._call_id
|
||||
|
||||
def getPeerProfile(self) -> Optional["SipProfile"]:
|
||||
return self._peer_profile
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def isInCall(self) -> bool:
|
||||
return self._in_call
|
||||
|
||||
def getLocalProfile(self) -> Optional["SipProfile"]:
|
||||
return self._profile
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Listener
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Listener(Stub):
|
||||
"""SipSession.Listener — SIP 会话监听器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onCalling(self, session: "SipSession"):
|
||||
"""呼叫中回调"""
|
||||
pass
|
||||
|
||||
def onRinging(self, session: "SipSession", caller: "SipProfile"):
|
||||
"""响铃回调"""
|
||||
pass
|
||||
|
||||
def onCallEstablished(self, session: "SipSession", sessionDescription: str):
|
||||
"""呼叫建立回调"""
|
||||
pass
|
||||
|
||||
def onCallEnded(self, session: "SipSession"):
|
||||
"""呼叫结束回调"""
|
||||
pass
|
||||
|
||||
def onCallBusy(self, session: "SipSession"):
|
||||
"""呼叫忙碌回调"""
|
||||
pass
|
||||
|
||||
def onError(self, session: "SipSession", errorCode: int, errorMessage: str):
|
||||
"""错误回调"""
|
||||
pass
|
||||
|
||||
def onCallChangeFailed(self, session: "SipSession", errorCode: int, errorMessage: str):
|
||||
"""呼叫变更失败回调"""
|
||||
pass
|
||||
|
||||
def onRegistrationDone(self, session: "SipSession", duration: int):
|
||||
"""注册成功回调"""
|
||||
pass
|
||||
|
||||
def onRegistrationFailed(
|
||||
self, session: "SipSession", errorCode: int, errorMessage: str
|
||||
):
|
||||
"""注册失败回调"""
|
||||
pass
|
||||
|
||||
def onRegistrationTimeout(self, session: "SipSession"):
|
||||
"""注册超时回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SipSession.Listener@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SipSession(state={self._state})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SipAudioCall
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SipAudioCall(Stub):
|
||||
"""SipAudioCall — SIP 音频呼叫"""
|
||||
|
||||
def __init__(self, context: Any = None, localProfile: "SipProfile" = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._local_profile = localProfile
|
||||
self._listener: Optional["SipAudioCall.Listener"] = None
|
||||
self._muted = False
|
||||
self._holding = False
|
||||
self._in_call = False
|
||||
|
||||
def setListener(self, listener: "SipAudioCall.Listener"):
|
||||
"""设置音频呼叫监听器"""
|
||||
self._listener = listener
|
||||
|
||||
def startAudio(self):
|
||||
"""开始音频"""
|
||||
raise AndroidOnlyError("SipAudioCall.startAudio() 仅在 Android 设备上可用")
|
||||
|
||||
def stopAudio(self):
|
||||
"""停止音频"""
|
||||
raise AndroidOnlyError("SipAudioCall.stopAudio() 仅在 Android 设备上可用")
|
||||
|
||||
def close(self):
|
||||
"""关闭音频呼叫"""
|
||||
self._in_call = False
|
||||
self._holding = False
|
||||
|
||||
def isMuted(self) -> bool:
|
||||
return self._muted
|
||||
|
||||
def toggleMute(self):
|
||||
"""切换静音"""
|
||||
self._muted = not self._muted
|
||||
|
||||
def hold(self, hold: int):
|
||||
"""保持呼叫"""
|
||||
self._holding = (hold != 0)
|
||||
|
||||
def continueCall(self, sessionDescription: int):
|
||||
"""继续呼叫"""
|
||||
raise AndroidOnlyError("SipAudioCall.continueCall() 仅在 Android 设备上可用")
|
||||
|
||||
def makeCall(
|
||||
self, localProfile: "SipProfile", peerProfile: "SipProfile", timeout: int
|
||||
):
|
||||
"""发起呼叫"""
|
||||
raise AndroidOnlyError("SipAudioCall.makeCall() 仅在 Android 设备上可用")
|
||||
|
||||
def answerCall(self, sessionDescription: int):
|
||||
"""接听呼叫"""
|
||||
raise AndroidOnlyError("SipAudioCall.answerCall() 仅在 Android 设备上可用")
|
||||
|
||||
def sendDtmf(self, code: int):
|
||||
"""发送 DTMF"""
|
||||
pass
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Listener
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Listener(Stub):
|
||||
"""SipAudioCall.Listener — 音频呼叫监听器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onReadyToCall(self, call: "SipAudioCall"):
|
||||
"""准备呼叫回调"""
|
||||
pass
|
||||
|
||||
def onCallEstablished(self, call: "SipAudioCall", sessionDescription: str):
|
||||
"""呼叫建立回调"""
|
||||
pass
|
||||
|
||||
def onCallEnded(self, call: "SipAudioCall"):
|
||||
"""呼叫结束回调"""
|
||||
pass
|
||||
|
||||
def onCallHeld(self, call: "SipAudioCall"):
|
||||
"""呼叫保持回调"""
|
||||
pass
|
||||
|
||||
def onCallChanged(self, call: "SipAudioCall"):
|
||||
"""呼叫变更回调"""
|
||||
pass
|
||||
|
||||
def onError(self, call: "SipAudioCall", errorCode: int, errorMessage: str):
|
||||
"""错误回调"""
|
||||
pass
|
||||
|
||||
def onRingingBack(self, call: "SipAudioCall"):
|
||||
"""回铃回调"""
|
||||
pass
|
||||
|
||||
def onRinging(self, call: "SipAudioCall", caller: "SipProfile"):
|
||||
"""响铃回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SipAudioCall.Listener@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SipAudioCall(inCall={self._in_call})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,370 @@
|
||||
"""
|
||||
Android Speech API — SpeechRecognizer, RecognitionListener, RecognitionService, RecognizerIntent
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"SpeechRecognizer",
|
||||
"RecognitionListener",
|
||||
"RecognitionService",
|
||||
"RecognizerIntent",
|
||||
"RecognizerResultsIntent",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SpeechRecognizer
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SpeechRecognizer(Stub):
|
||||
"""SpeechRecognizer — 语音识别器 (桌面麦克风模拟)"""
|
||||
|
||||
RESULTS_RECOGNITION = "results_recognition"
|
||||
CONFIDENCE_SCORES = "confidence_scores"
|
||||
|
||||
ERROR_NETWORK_TIMEOUT = 1
|
||||
ERROR_NETWORK = 2
|
||||
ERROR_AUDIO = 3
|
||||
ERROR_SERVER = 4
|
||||
ERROR_CLIENT = 5
|
||||
ERROR_SPEECH_TIMEOUT = 6
|
||||
ERROR_NO_MATCH = 7
|
||||
ERROR_RECOGNIZER_BUSY = 8
|
||||
ERROR_INSUFFICIENT_PERMISSIONS = 9
|
||||
ERROR_TOO_MANY_REQUESTS = 10
|
||||
ERROR_LANGUAGE_NOT_SUPPORTED = 11
|
||||
ERROR_LANGUAGE_UNAVAILABLE = 12
|
||||
ERROR_SERVER_DISCONNECTED = 13
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._listener: Optional["RecognitionListener"] = None
|
||||
self._is_listening = False
|
||||
self._destroyed = False
|
||||
self._simulate_timer: Optional[threading.Timer] = None
|
||||
|
||||
@staticmethod
|
||||
def createSpeechRecognizer(context: Any) -> "SpeechRecognizer":
|
||||
"""创建语音识别器"""
|
||||
return SpeechRecognizer(context)
|
||||
|
||||
@staticmethod
|
||||
def isRecognitionAvailable(context: Any) -> bool:
|
||||
"""检查语音识别是否可用"""
|
||||
try:
|
||||
import speech_recognition as sr
|
||||
return True
|
||||
except ImportError:
|
||||
try:
|
||||
import whisper # noqa: F401
|
||||
return True
|
||||
except ImportError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def setRecognitionListener(self, listener: "RecognitionListener"):
|
||||
"""设置识别监听器"""
|
||||
self._listener = listener
|
||||
|
||||
def startListening(self, intent: Any):
|
||||
"""开始语音识别"""
|
||||
if self._destroyed:
|
||||
return
|
||||
|
||||
self._is_listening = True
|
||||
|
||||
# 使用简单识别模拟
|
||||
if self._listener:
|
||||
self._listener.onReadyForSpeech(None)
|
||||
self._listener.onBeginningOfSpeech()
|
||||
|
||||
# 模拟麦克风音量变化
|
||||
for i in range(3):
|
||||
self._listener.onRmsChanged(5.0 + i * 2.0)
|
||||
time.sleep(0.05)
|
||||
|
||||
self._listener.onBufferReceived(bytearray(1024))
|
||||
self._listener.onEndOfSpeech()
|
||||
|
||||
# 模拟识别结果
|
||||
results = {self.RESULTS_RECOGNITION: ["hello world"], self.CONFIDENCE_SCORES: [0.95]}
|
||||
self._listener.onResults(results)
|
||||
|
||||
def stopListening(self):
|
||||
"""停止语音识别"""
|
||||
self._is_listening = False
|
||||
if self._simulate_timer:
|
||||
self._simulate_timer.cancel()
|
||||
self._simulate_timer = None
|
||||
|
||||
def cancel(self):
|
||||
"""取消语音识别"""
|
||||
self._is_listening = False
|
||||
if self._simulate_timer:
|
||||
self._simulate_timer.cancel()
|
||||
self._simulate_timer = None
|
||||
if self._listener:
|
||||
self._listener.onError(self.ERROR_NO_MATCH)
|
||||
|
||||
def destroy(self):
|
||||
"""销毁语音识别器"""
|
||||
self._destroyed = True
|
||||
self._is_listening = False
|
||||
if self._simulate_timer:
|
||||
self._simulate_timer.cancel()
|
||||
self._simulate_timer = None
|
||||
self._listener = None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SpeechRecognizer(listening={self._is_listening})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RecognitionListener
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RecognitionListener(Stub):
|
||||
"""RecognitionListener — 语音识别监听器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onReadyForSpeech(self, params: Any):
|
||||
"""准备开始语音识别回调"""
|
||||
pass
|
||||
|
||||
def onBeginningOfSpeech(self):
|
||||
"""开始说话回调"""
|
||||
pass
|
||||
|
||||
def onRmsChanged(self, rmsdB: float):
|
||||
"""音量变化回调"""
|
||||
pass
|
||||
|
||||
def onBufferReceived(self, buffer: bytearray):
|
||||
"""接收音频缓冲区回调"""
|
||||
pass
|
||||
|
||||
def onEndOfSpeech(self):
|
||||
"""说话结束回调"""
|
||||
pass
|
||||
|
||||
def onError(self, error: int):
|
||||
"""错误回调"""
|
||||
pass
|
||||
|
||||
def onResults(self, results: Dict[str, Any]):
|
||||
"""识别结果回调"""
|
||||
pass
|
||||
|
||||
def onPartialResults(self, partialResults: Dict[str, Any]):
|
||||
"""部分识别结果回调"""
|
||||
pass
|
||||
|
||||
def onSegmentResults(self, segmentResults: Dict[str, Any]):
|
||||
"""分段结果回调"""
|
||||
pass
|
||||
|
||||
def onEndOfSegmentedSession(self, sessionId: int):
|
||||
"""分段会话结束回调"""
|
||||
pass
|
||||
|
||||
def onLanguageDetection(self, language: str):
|
||||
"""语言检测回调"""
|
||||
pass
|
||||
|
||||
def onEvent(self, eventType: int, params: Any):
|
||||
"""事件回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"RecognitionListener@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RecognitionService
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RecognitionService(Stub):
|
||||
"""RecognitionService — 语音识别服务"""
|
||||
|
||||
SERVICE_INTERFACE = "android.speech.RecognitionService"
|
||||
SERVICE_META_DATA = "android.speech"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._callback: Optional["RecognitionService.Callback"] = None
|
||||
|
||||
def onStartListening(self, intent: Any, listener: "RecognitionService.Callback"):
|
||||
"""开始监听回调"""
|
||||
pass
|
||||
|
||||
def onCancel(self, listener: "RecognitionService.Callback"):
|
||||
"""取消回调"""
|
||||
pass
|
||||
|
||||
def onStopListening(self, listener: "RecognitionService.Callback"):
|
||||
"""停止监听回调"""
|
||||
pass
|
||||
|
||||
def onDestroy(self):
|
||||
"""销毁回调"""
|
||||
pass
|
||||
|
||||
def oneShotListening(self, intent: Any, listener: "RecognitionService.Callback"):
|
||||
"""一次性监听"""
|
||||
if listener:
|
||||
listener.readyForSpeech(None)
|
||||
listener.beginningOfSpeech()
|
||||
listener.endOfSpeech()
|
||||
results = {SpeechRecognizer.RESULTS_RECOGNITION: ["hello"]}
|
||||
listener.results(results)
|
||||
|
||||
def getCurrentCallback(self) -> Optional["RecognitionService.Callback"]:
|
||||
return self._callback
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Callback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Callback(Stub):
|
||||
"""RecognitionService.Callback — 识别服务回调"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def results(self, results: Dict[str, Any]):
|
||||
"""返回识别结果"""
|
||||
pass
|
||||
|
||||
def partialResults(self, partialResults: Dict[str, Any]):
|
||||
"""返回部分结果"""
|
||||
pass
|
||||
|
||||
def error(self, error: int):
|
||||
"""返回错误"""
|
||||
pass
|
||||
|
||||
def beginningOfSpeech(self):
|
||||
"""开始说话回调"""
|
||||
pass
|
||||
|
||||
def bufferReceived(self, buffer: bytearray):
|
||||
"""接收音频缓冲区回调"""
|
||||
pass
|
||||
|
||||
def endOfSpeech(self):
|
||||
"""说话结束回调"""
|
||||
pass
|
||||
|
||||
def readyForSpeech(self, params: Any):
|
||||
"""准备开始语音识别"""
|
||||
pass
|
||||
|
||||
def rmsChanged(self, rmsdB: float):
|
||||
"""音量变化回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"RecognitionService.Callback@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"RecognitionService@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RecognizerIntent
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RecognizerIntent(Stub):
|
||||
"""RecognizerIntent — 识别器 Intent 常量"""
|
||||
|
||||
ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH"
|
||||
ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH"
|
||||
ACTION_VOICE_SEARCH_HANDS_FREE = "android.speech.action.VOICE_SEARCH_HANDS_FREE"
|
||||
ACTION_GET_LANGUAGE_DETAILS = "android.speech.action.GET_LANGUAGE_DETAILS"
|
||||
|
||||
EXTRA_LANGUAGE_MODEL = "android.speech.extra.LANGUAGE_MODEL"
|
||||
EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE"
|
||||
EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS"
|
||||
EXTRA_PARTIAL_RESULTS = "android.speech.extra.PARTIAL_RESULTS"
|
||||
EXTRA_CALLING_PACKAGE = "android.speech.extra.CALLING_PACKAGE"
|
||||
EXTRA_PROMPT = "android.speech.extra.PROMPT"
|
||||
EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS = "android.speech.extra.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS"
|
||||
EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS = "android.speech.extra.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS"
|
||||
EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS = "android.speech.extra.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS"
|
||||
EXTRA_CONFIDENCE_SCORES = "android.speech.extra.CONFIDENCE_SCORES"
|
||||
EXTRA_RESULTS = "android.speech.extra.RESULTS"
|
||||
EXTRA_SECURE = "android.speech.extra.SECURE"
|
||||
EXTRA_SUPPORTED_LANGUAGES = "android.speech.extra.SUPPORTED_LANGUAGES"
|
||||
EXTRA_LANGUAGE_PREFERENCE = "android.speech.extra.LANGUAGE_PREFERENCE"
|
||||
EXTRA_ONLY_RETURN_LANGUAGE_PREFERENCE = "android.speech.extra.ONLY_RETURN_LANGUAGE_PREFERENCE"
|
||||
EXTRA_LANGUAGE_DETAILS = "android.speech.extra.LANGUAGE_DETAILS"
|
||||
|
||||
LANGUAGE_MODEL_FREE_FORM = "free_form"
|
||||
LANGUAGE_MODEL_WEB_SEARCH = "web_search"
|
||||
|
||||
RESULT_AUDIO_ERROR = 1
|
||||
RESULT_CLIENT_ERROR = 2
|
||||
RESULT_NETWORK_ERROR = 3
|
||||
RESULT_SERVER_ERROR = 4
|
||||
RESULT_NO_MATCH = 5
|
||||
|
||||
DETAIL_META_DATA = "android.speech.DETAIL_META_DATA"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RecognizerResultsIntent
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RecognizerResultsIntent(Stub):
|
||||
"""RecognizerResultsIntent — 识别结果 Intent 常量"""
|
||||
|
||||
ACTION_VOICE_SEARCH_RESULTS = "android.speech.action.VOICE_SEARCH_RESULTS"
|
||||
EXTRA_VOICE_SEARCH_RESULT_STRINGS = "android.speech.extra.VOICE_SEARCH_RESULT_STRINGS"
|
||||
EXTRA_VOICE_SEARCH_RESULT_URLS = "android.speech.extra.VOICE_SEARCH_RESULT_URLS"
|
||||
EXTRA_VOICE_SEARCH_RESULT_HTML = "android.speech.extra.VOICE_SEARCH_RESULT_HTML"
|
||||
EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS = "android.speech.extra.VOICE_SEARCH_RESULT_HTML_BASE_URLS"
|
||||
URI_SCHEME_INLINE = "inline"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -0,0 +1,592 @@
|
||||
"""
|
||||
Android System 组件 — Os, StructStat, StructUtsname, OsConstants 等
|
||||
方法签名完全对齐 Android SDK (android.system 包)
|
||||
"""
|
||||
|
||||
import os as _os
|
||||
import stat as _stat
|
||||
import platform
|
||||
import errno
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# OsConstants
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class OsConstants(Stub):
|
||||
"""OsConstants — 系统调用常量,映射自 os/stat 模块"""
|
||||
|
||||
# 文件访问常量
|
||||
R_OK = _os.R_OK
|
||||
W_OK = _os.W_OK
|
||||
X_OK = _os.X_OK
|
||||
F_OK = _os.F_OK
|
||||
|
||||
# 文件权限常量
|
||||
S_IRWXU = _stat.S_IRWXU
|
||||
S_IRUSR = _stat.S_IRUSR
|
||||
S_IWUSR = _stat.S_IWUSR
|
||||
S_IXUSR = _stat.S_IXUSR
|
||||
S_IRWXG = _stat.S_IRWXG
|
||||
S_IRGRP = _stat.S_IRGRP
|
||||
S_IWGRP = _stat.S_IWGRP
|
||||
S_IXGRP = _stat.S_IXGRP
|
||||
S_IRWXO = _stat.S_IRWXO
|
||||
S_IROTH = _stat.S_IROTH
|
||||
S_IWOTH = _stat.S_IWOTH
|
||||
S_IXOTH = _stat.S_IXOTH
|
||||
S_ISUID = _stat.S_ISUID
|
||||
S_ISGID = _stat.S_ISGID
|
||||
S_ISVTX = _stat.S_ISVTX
|
||||
|
||||
# 文件类型常量
|
||||
S_IFMT = _stat.S_IFMT
|
||||
S_IFSOCK = _stat.S_IFSOCK
|
||||
S_IFLNK = _stat.S_IFLNK
|
||||
S_IFREG = _stat.S_IFREG
|
||||
S_IFBLK = _stat.S_IFBLK
|
||||
S_IFDIR = _stat.S_IFDIR
|
||||
S_IFCHR = _stat.S_IFCHR
|
||||
S_IFIFO = _stat.S_IFIFO
|
||||
|
||||
# 标准文件描述符
|
||||
STDIN_FILENO = 0
|
||||
STDOUT_FILENO = 1
|
||||
STDERR_FILENO = 2
|
||||
|
||||
# 信号常量
|
||||
SIGHUP = 1
|
||||
SIGINT = 2
|
||||
SIGQUIT = 3
|
||||
SIGILL = 4
|
||||
SIGTRAP = 5
|
||||
SIGABRT = 6
|
||||
SIGBUS = 7
|
||||
SIGFPE = 8
|
||||
SIGKILL = 9
|
||||
SIGUSR1 = 10
|
||||
SIGSEGV = 11
|
||||
SIGUSR2 = 12
|
||||
SIGPIPE = 13
|
||||
SIGALRM = 14
|
||||
SIGTERM = 15
|
||||
SIGCHLD = 17
|
||||
SIGCONT = 18
|
||||
SIGSTOP = 19
|
||||
SIGTSTP = 20
|
||||
SIGTTIN = 21
|
||||
SIGTTOU = 22
|
||||
|
||||
# errno 常量
|
||||
EPERM = errno.EPERM
|
||||
ENOENT = errno.ENOENT
|
||||
ESRCH = errno.ESRCH
|
||||
EINTR = errno.EINTR
|
||||
EIO = errno.EIO
|
||||
ENXIO = errno.ENXIO
|
||||
E2BIG = errno.E2BIG
|
||||
ENOEXEC = errno.ENOEXEC
|
||||
EBADF = errno.EBADF
|
||||
ECHILD = errno.ECHILD
|
||||
EAGAIN = errno.EAGAIN
|
||||
ENOMEM = errno.ENOMEM
|
||||
EACCES = errno.EACCES
|
||||
EFAULT = errno.EFAULT
|
||||
ENOTBLK = errno.ENOTBLK
|
||||
EBUSY = errno.EBUSY
|
||||
EEXIST = errno.EEXIST
|
||||
EXDEV = errno.EXDEV
|
||||
ENODEV = errno.ENODEV
|
||||
ENOTDIR = errno.ENOTDIR
|
||||
EISDIR = errno.EISDIR
|
||||
EINVAL = errno.EINVAL
|
||||
ENFILE = errno.ENFILE
|
||||
EMFILE = errno.EMFILE
|
||||
ENOTTY = errno.ENOTTY
|
||||
ETXTBSY = errno.ETXTBSY
|
||||
EFBIG = errno.EFBIG
|
||||
ENOSPC = errno.ENOSPC
|
||||
ESPIPE = errno.ESPIPE
|
||||
EROFS = errno.EROFS
|
||||
EMLINK = errno.EMLINK
|
||||
EPIPE = errno.EPIPE
|
||||
EDOM = errno.EDOM
|
||||
ERANGE = errno.ERANGE
|
||||
EDEADLK = errno.EDEADLK
|
||||
ENAMETOOLONG = errno.ENAMETOOLONG
|
||||
ENOLCK = errno.ENOLCK
|
||||
ENOSYS = errno.ENOSYS
|
||||
ENOTEMPTY = errno.ENOTEMPTY
|
||||
ELOOP = errno.ELOOP
|
||||
ENOMSG = errno.ENOMSG
|
||||
EIDRM = errno.EIDRM
|
||||
ENOSTR = errno.ENOSTR
|
||||
ENODATA = errno.ENODATA
|
||||
ETIME = errno.ETIME
|
||||
ENOSR = errno.ENOSR
|
||||
EREMOTE = errno.EREMOTE
|
||||
ENOLINK = errno.ENOLINK
|
||||
EPROTO = errno.EPROTO
|
||||
EMULTIHOP = errno.EMULTIHOP
|
||||
EBADMSG = errno.EBADMSG
|
||||
EOVERFLOW = errno.EOVERFLOW
|
||||
EILSEQ = errno.EILSEQ
|
||||
ENOTSOCK = errno.ENOTSOCK
|
||||
EDESTADDRREQ = errno.EDESTADDRREQ
|
||||
EMSGSIZE = errno.EMSGSIZE
|
||||
EPROTOTYPE = errno.EPROTOTYPE
|
||||
ENOPROTOOPT = errno.ENOPROTOOPT
|
||||
EPROTONOSUPPORT = errno.EPROTONOSUPPORT
|
||||
ESOCKTNOSUPPORT = errno.ESOCKTNOSUPPORT
|
||||
EOPNOTSUPP = errno.EOPNOTSUPP
|
||||
EPFNOSUPPORT = errno.EPFNOSUPPORT
|
||||
EAFNOSUPPORT = errno.EAFNOSUPPORT
|
||||
EADDRINUSE = errno.EADDRINUSE
|
||||
EADDRNOTAVAIL = errno.EADDRNOTAVAIL
|
||||
ENETDOWN = errno.ENETDOWN
|
||||
ENETUNREACH = errno.ENETUNREACH
|
||||
ENETRESET = errno.ENETRESET
|
||||
ECONNABORTED = errno.ECONNABORTED
|
||||
ECONNRESET = errno.ECONNRESET
|
||||
ENOBUFS = errno.ENOBUFS
|
||||
EISCONN = errno.EISCONN
|
||||
ENOTCONN = errno.ENOTCONN
|
||||
ESHUTDOWN = errno.ESHUTDOWN
|
||||
ETOOMANYREFS = errno.ETOOMANYREFS
|
||||
ETIMEDOUT = errno.ETIMEDOUT
|
||||
ECONNREFUSED = errno.ECONNREFUSED
|
||||
EHOSTDOWN = errno.EHOSTDOWN
|
||||
EHOSTUNREACH = errno.EHOSTUNREACH
|
||||
EALREADY = errno.EALREADY
|
||||
EINPROGRESS = errno.EINPROGRESS
|
||||
ESTALE = errno.ESTALE
|
||||
EUCLEAN = errno.EUCLEAN
|
||||
ENOTNAM = errno.ENOTNAM
|
||||
ENAVAIL = errno.ENAVAIL
|
||||
EISNAM = errno.EISNAM
|
||||
EREMOTEIO = errno.EREMOTEIO
|
||||
EDQUOT = errno.EDQUOT
|
||||
ENOMEDIUM = errno.ENOMEDIUM
|
||||
EMEDIUMTYPE = errno.EMEDIUMTYPE
|
||||
ECANCELED = errno.ECANCELED
|
||||
ENOKEY = errno.ENOKEY
|
||||
EKEYEXPIRED = errno.EKEYEXPIRED
|
||||
EKEYREVOKED = errno.EKEYREVOKED
|
||||
EKEYREJECTED = errno.EKEYREJECTED
|
||||
EOWNERDEAD = errno.EOWNERDEAD
|
||||
ENOTRECOVERABLE = errno.ENOTRECOVERABLE
|
||||
ERFKILL = errno.ERFKILL
|
||||
EHWPOISON = getattr(errno, 'EHWPOISON', 1001)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# StructStat
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StructStat(Stub):
|
||||
"""StructStat — stat() 返回的文件状态结构体"""
|
||||
|
||||
def __init__(self, st_mode: int = 0, st_ino: int = 0, st_dev: int = 0,
|
||||
st_nlink: int = 0, st_uid: int = 0, st_gid: int = 0,
|
||||
st_size: int = 0, st_atime: int = 0, st_mtime: int = 0,
|
||||
st_ctime: int = 0, st_blksize: int = 4096,
|
||||
st_blocks: int = 0, st_rdev: int = 0):
|
||||
super().__init__()
|
||||
self.st_mode = st_mode
|
||||
self.st_ino = st_ino
|
||||
self.st_dev = st_dev
|
||||
self.st_nlink = st_nlink
|
||||
self.st_uid = st_uid
|
||||
self.st_gid = st_gid
|
||||
self.st_size = st_size
|
||||
self.st_atime = st_atime
|
||||
self.st_mtime = st_mtime
|
||||
self.st_ctime = st_ctime
|
||||
self.st_blksize = st_blksize
|
||||
self.st_blocks = st_blocks
|
||||
self.st_rdev = st_rdev
|
||||
self.st_atime_ns = st_atime * 1_000_000_000
|
||||
self.st_mtime_ns = st_mtime * 1_000_000_000
|
||||
self.st_ctime_ns = st_ctime * 1_000_000_000
|
||||
|
||||
@staticmethod
|
||||
def from_os_stat(st: _os.stat_result) -> "StructStat":
|
||||
"""从 Python os.stat_result 创建 StructStat"""
|
||||
return StructStat(
|
||||
st_mode=st.st_mode,
|
||||
st_ino=st.st_ino,
|
||||
st_dev=st.st_dev,
|
||||
st_nlink=st.st_nlink,
|
||||
st_uid=st.st_uid,
|
||||
st_gid=st.st_gid,
|
||||
st_size=st.st_size,
|
||||
st_atime=int(st.st_atime),
|
||||
st_mtime=int(st.st_mtime),
|
||||
st_ctime=int(st.st_ctime),
|
||||
st_blksize=st.st_blksize,
|
||||
st_blocks=st.st_blocks,
|
||||
st_rdev=st.st_rdev,
|
||||
)
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"StructStat(st_mode={oct(self.st_mode)}, st_size={self.st_size}, "
|
||||
f"st_mtime={self.st_mtime}, st_uid={self.st_uid}, st_gid={self.st_gid})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self.st_dev, self.st_ino, self.st_mode)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, StructStat):
|
||||
return False
|
||||
return (self.st_dev == other.st_dev and self.st_ino == other.st_ino
|
||||
and self.st_mode == other.st_mode and self.st_size == other.st_size)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# StructUtsname
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StructUtsname(Stub):
|
||||
"""StructUtsname — uname() 返回的系统信息结构体"""
|
||||
|
||||
def __init__(self, sysname: str = "", nodename: str = "",
|
||||
release: str = "", version: str = "", machine: str = ""):
|
||||
super().__init__()
|
||||
self.sysname = sysname or platform.system()
|
||||
self.nodename = nodename or platform.node()
|
||||
self.release = release or platform.release()
|
||||
self.version = version or platform.version()
|
||||
self.machine = machine or platform.machine()
|
||||
|
||||
@staticmethod
|
||||
def from_platform() -> "StructUtsname":
|
||||
"""从 Python platform 模块创建 StructUtsname"""
|
||||
uname = platform.uname()
|
||||
return StructUtsname(
|
||||
sysname=uname.system,
|
||||
nodename=uname.node,
|
||||
release=uname.release,
|
||||
version=uname.version,
|
||||
machine=uname.machine,
|
||||
)
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"StructUtsname(sysname={self.sysname}, nodename={self.nodename}, "
|
||||
f"release={self.release}, version={self.version}, machine={self.machine})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self.sysname, self.nodename, self.release)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, StructUtsname):
|
||||
return False
|
||||
return (self.sysname == other.sysname and self.nodename == other.nodename
|
||||
and self.release == other.release)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Os
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Os(Stub):
|
||||
"""Os — 系统调用包装器,代理到 Python os 模块"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# ── 文件操作 ──
|
||||
|
||||
@staticmethod
|
||||
def stat(path: str) -> StructStat:
|
||||
"""获取文件状态"""
|
||||
st = _os.stat(path)
|
||||
return StructStat.from_os_stat(st)
|
||||
|
||||
@staticmethod
|
||||
def lstat(path: str) -> StructStat:
|
||||
"""获取符号链接自身状态"""
|
||||
st = _os.lstat(path)
|
||||
return StructStat.from_os_stat(st)
|
||||
|
||||
@staticmethod
|
||||
def chmod(path: str, mode: int):
|
||||
"""修改文件权限"""
|
||||
_os.chmod(path, mode)
|
||||
|
||||
@staticmethod
|
||||
def chown(path: str, uid: int, gid: int):
|
||||
"""修改文件所有者"""
|
||||
_os.chown(path, uid, gid)
|
||||
|
||||
@staticmethod
|
||||
def link(oldpath: str, newpath: str):
|
||||
"""创建硬链接"""
|
||||
_os.link(oldpath, newpath)
|
||||
|
||||
@staticmethod
|
||||
def symlink(target: str, linkpath: str):
|
||||
"""创建符号链接"""
|
||||
_os.symlink(target, linkpath)
|
||||
|
||||
@staticmethod
|
||||
def readlink(path: str) -> str:
|
||||
"""读取符号链接目标"""
|
||||
return _os.readlink(path)
|
||||
|
||||
@staticmethod
|
||||
def unlink(pathname: str):
|
||||
"""删除文件(解除链接)"""
|
||||
_os.unlink(pathname)
|
||||
|
||||
@staticmethod
|
||||
def remove(path: str):
|
||||
"""删除文件"""
|
||||
_os.remove(path)
|
||||
|
||||
@staticmethod
|
||||
def rename(oldpath: str, newpath: str):
|
||||
"""重命名文件"""
|
||||
_os.rename(oldpath, newpath)
|
||||
|
||||
@staticmethod
|
||||
def mkdir(path: str, mode: int = 0o777):
|
||||
"""创建目录"""
|
||||
_os.mkdir(path, mode)
|
||||
|
||||
@staticmethod
|
||||
def rmdir(path: str):
|
||||
"""删除空目录"""
|
||||
_os.rmdir(path)
|
||||
|
||||
@staticmethod
|
||||
def listdir(path: str) -> List[str]:
|
||||
"""列出目录内容"""
|
||||
return _os.listdir(path)
|
||||
|
||||
@staticmethod
|
||||
def truncate(path: str, length: int):
|
||||
"""截断文件到指定长度"""
|
||||
with open(path, 'ab') as f:
|
||||
f.truncate(length)
|
||||
|
||||
@staticmethod
|
||||
def access(path: str, mode: int) -> bool:
|
||||
"""检查文件访问权限"""
|
||||
return _os.access(path, mode)
|
||||
|
||||
# ── 文件描述符操作 ──
|
||||
|
||||
@staticmethod
|
||||
def dup(oldfd: int) -> int:
|
||||
"""复制文件描述符"""
|
||||
return _os.dup(oldfd)
|
||||
|
||||
@staticmethod
|
||||
def dup2(oldfd: int, newfd: int):
|
||||
"""复制文件描述符到指定值"""
|
||||
_os.dup2(oldfd, newfd)
|
||||
|
||||
@staticmethod
|
||||
def close(fd: int):
|
||||
"""关闭文件描述符"""
|
||||
_os.close(fd)
|
||||
|
||||
@staticmethod
|
||||
def read(fd: int, count: int) -> bytes:
|
||||
"""从文件描述符读取"""
|
||||
return _os.read(fd, count)
|
||||
|
||||
@staticmethod
|
||||
def write(fd: int, data: bytes) -> int:
|
||||
"""写入文件描述符"""
|
||||
return _os.write(fd, data)
|
||||
|
||||
@staticmethod
|
||||
def lseek(fd: int, offset: int, whence: int) -> int:
|
||||
"""移动文件读写位置"""
|
||||
return _os.lseek(fd, offset, whence)
|
||||
|
||||
@staticmethod
|
||||
def fsync(fd: int):
|
||||
"""同步文件描述符"""
|
||||
_os.fsync(fd)
|
||||
|
||||
@staticmethod
|
||||
def fstat(fd: int) -> StructStat:
|
||||
"""获取文件描述符对应文件状态"""
|
||||
st = _os.fstat(fd)
|
||||
return StructStat.from_os_stat(st)
|
||||
|
||||
@staticmethod
|
||||
def isatty(fd: int) -> bool:
|
||||
"""判断文件描述符是否为终端"""
|
||||
return _os.isatty(fd)
|
||||
|
||||
# ── 进程相关 ──
|
||||
|
||||
@staticmethod
|
||||
def getpid() -> int:
|
||||
"""获取当前进程 ID"""
|
||||
return _os.getpid()
|
||||
|
||||
@staticmethod
|
||||
def getppid() -> int:
|
||||
"""获取父进程 ID"""
|
||||
return _os.getppid()
|
||||
|
||||
@staticmethod
|
||||
def getuid() -> int:
|
||||
"""获取当前用户 ID"""
|
||||
return _os.getuid()
|
||||
|
||||
@staticmethod
|
||||
def getgid() -> int:
|
||||
"""获取当前组 ID"""
|
||||
return _os.getgid()
|
||||
|
||||
@staticmethod
|
||||
def geteuid() -> int:
|
||||
"""获取有效用户 ID"""
|
||||
return _os.geteuid()
|
||||
|
||||
@staticmethod
|
||||
def getegid() -> int:
|
||||
"""获取有效组 ID"""
|
||||
return _os.getegid()
|
||||
|
||||
@staticmethod
|
||||
def setuid(uid: int):
|
||||
"""设置用户 ID"""
|
||||
_os.setuid(uid)
|
||||
|
||||
@staticmethod
|
||||
def setgid(gid: int):
|
||||
"""设置组 ID"""
|
||||
_os.setgid(gid)
|
||||
|
||||
@staticmethod
|
||||
def setsid() -> int:
|
||||
"""创建新会话"""
|
||||
return _os.setsid()
|
||||
|
||||
@staticmethod
|
||||
def umask(mask: int) -> int:
|
||||
"""设置文件创建掩码"""
|
||||
return _os.umask(mask)
|
||||
|
||||
@staticmethod
|
||||
def kill(pid: int, sig: int):
|
||||
"""发送信号给进程"""
|
||||
_os.kill(pid, sig)
|
||||
|
||||
@staticmethod
|
||||
def getenv(name: str, default: Optional[str] = None) -> Optional[str]:
|
||||
"""获取环境变量"""
|
||||
return _os.environ.get(name, default)
|
||||
|
||||
@staticmethod
|
||||
def setenv(name: str, value: str, overwrite: bool = True):
|
||||
"""设置环境变量"""
|
||||
if overwrite or name not in _os.environ:
|
||||
_os.environ[name] = value
|
||||
|
||||
@staticmethod
|
||||
def unsetenv(name: str):
|
||||
"""删除环境变量"""
|
||||
_os.environ.pop(name, None)
|
||||
|
||||
@staticmethod
|
||||
def environ() -> Dict[str, str]:
|
||||
"""获取环境变量字典"""
|
||||
return dict(_os.environ)
|
||||
|
||||
# ── 系统信息 ──
|
||||
|
||||
@staticmethod
|
||||
def uname() -> StructUtsname:
|
||||
"""获取系统信息"""
|
||||
return StructUtsname.from_platform()
|
||||
|
||||
@staticmethod
|
||||
def sysconf(name: str) -> int:
|
||||
"""获取系统配置值"""
|
||||
conf_names = {
|
||||
"SC_CLK_TCK": 100,
|
||||
"SC_PAGE_SIZE": 4096,
|
||||
"SC_NPROCESSORS_CONF": _os.cpu_count() or 1,
|
||||
"SC_NPROCESSORS_ONLN": _os.cpu_count() or 1,
|
||||
}
|
||||
return conf_names.get(name, -1)
|
||||
|
||||
@staticmethod
|
||||
def getpagesize() -> int:
|
||||
"""获取内存页大小"""
|
||||
return 4096
|
||||
|
||||
@staticmethod
|
||||
def getloadavg() -> List[float]:
|
||||
"""获取系统负载平均值"""
|
||||
try:
|
||||
return list(_os.getloadavg())
|
||||
except AttributeError:
|
||||
return [0.0, 0.0, 0.0]
|
||||
|
||||
# ── 管道与 I/O ──
|
||||
|
||||
@staticmethod
|
||||
def pipe() -> Tuple[int, int]:
|
||||
"""创建管道"""
|
||||
return _os.pipe()
|
||||
|
||||
@staticmethod
|
||||
def mkfifo(path: str, mode: int = 0o666):
|
||||
"""创建命名管道 (FIFO)"""
|
||||
_os.mkfifo(path, mode)
|
||||
|
||||
@staticmethod
|
||||
def tmpfile() -> str:
|
||||
"""创建临时文件路径"""
|
||||
import tempfile
|
||||
return tempfile.mktemp()
|
||||
|
||||
# ── 时间 ──
|
||||
|
||||
@staticmethod
|
||||
def time() -> int:
|
||||
"""获取当前时间戳 (秒)"""
|
||||
import time
|
||||
return int(time.time())
|
||||
|
||||
# ── 其他 ──
|
||||
|
||||
@staticmethod
|
||||
def strerror(errno: int) -> str:
|
||||
"""将错误码转为错误描述"""
|
||||
return _os.strerror(errno)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Os(sysname={platform.system()}, release={platform.release()})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash("android.system.Os") & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return isinstance(other, Os)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"Os",
|
||||
"StructStat",
|
||||
"StructUtsname",
|
||||
"OsConstants",
|
||||
]
|
||||
@@ -0,0 +1,737 @@
|
||||
"""
|
||||
Android Telecom API — TelecomManager, Call, CallAudioState, DisconnectCause, PhoneAccount, Connection
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"TelecomManager",
|
||||
"Call",
|
||||
"CallAudioState",
|
||||
"DisconnectCause",
|
||||
"PhoneAccount",
|
||||
"Connection",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TelecomManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TelecomManager(Stub):
|
||||
"""TelecomManager — 电信管理器 (桌面存根)"""
|
||||
|
||||
ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER"
|
||||
ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS"
|
||||
ACTION_SETTINGS = "android.telecom.action.SETTINGS"
|
||||
ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION"
|
||||
ACTION_CALL_FORWARDING = "android.telecom.action.CALL_FORWARDING"
|
||||
ACTION_DEFAULT_CALL_SCREENING_APP = "android.telecom.action.DEFAULT_CALL_SCREENING_APP"
|
||||
ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED"
|
||||
ACTION_NEW_UNKNOWN_CALL = "android.telecom.action.NEW_UNKNOWN_CALL"
|
||||
ACTION_TTY_PREFERRED = "android.telecom.action.TTY_PREFERRED"
|
||||
ACTION_TTY_ENABLED = "android.telecom.action.TTY_ENABLED"
|
||||
ACTION_TTY_DISABLED = "android.telecom.action.TTY_DISABLED"
|
||||
ACTION_CONNECTION_SERVICE_CONFIGURE = "android.telecom.action.CONNECTION_SERVICE_CONFIGURE"
|
||||
ACTION_CALL_DIAGNOSTIC_STATE_CHANGED = "android.telecom.action.CALL_DIAGNOSTIC_STATE_CHANGED"
|
||||
DURATION_SHORT = "short"
|
||||
DURATION_LONG = "long"
|
||||
EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER"
|
||||
EXTRA_CALL_CREATED_TIME_MILLIS = "android.telecom.extra.CALL_CREATED_TIME_MILLIS"
|
||||
EXTRA_CALL_TECHNOLOGY_TYPE = "android.telecom.extra.CALL_TECHNOLOGY_TYPE"
|
||||
EXTRA_CALL_NETWORK_TYPE = "android.telecom.extra.CALL_NETWORK_TYPE"
|
||||
EXTRA_CALL_SUBSTATE = "android.telecom.extra.CALL_SUBSTATE"
|
||||
EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME"
|
||||
EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE"
|
||||
EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE"
|
||||
EXTRA_DISCONNECT_CAUSE_DESCRIPTION = "android.telecom.extra.DISCONNECT_CAUSE_DESCRIPTION"
|
||||
EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE"
|
||||
EXTRA_PRIORITY = "android.telecom.extra.PRIORITY"
|
||||
EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"
|
||||
EXTRA_HANDOVER_FROM = "android.telecom.extra.HANDOVER_FROM"
|
||||
EXTRA_HANDOVER_TO = "android.telecom.extra.HANDOVER_TO"
|
||||
EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER"
|
||||
TTY_MODE_OFF = 0
|
||||
TTY_MODE_FULL = 1
|
||||
TTY_MODE_HCO = 2
|
||||
TTY_MODE_VCO = 3
|
||||
PRESENTATION_ALLOWED = 1
|
||||
PRESENTATION_RESTRICTED = 2
|
||||
PRESENTATION_UNKNOWN = 3
|
||||
PRESENTATION_PAYPHONE = 4
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._default_dialer = "com.android.dialer"
|
||||
self._phone_accounts: Dict[str, Any] = {}
|
||||
|
||||
def getDefaultDialerPackage(self) -> str:
|
||||
"""获取默认拨号器包名"""
|
||||
return self._default_dialer
|
||||
|
||||
def getCurrentTtyMode(self) -> int:
|
||||
"""获取当前 TTY 模式"""
|
||||
return self.TTY_MODE_OFF
|
||||
|
||||
def getCallCapablePhoneAccounts(self) -> List[Any]:
|
||||
"""获取可通话的电话账户"""
|
||||
return list(self._phone_accounts.values())
|
||||
|
||||
def getPhoneAccount(self, accountHandle: Any) -> Optional[Any]:
|
||||
"""获取指定电话账户"""
|
||||
return self._phone_accounts.get(str(accountHandle))
|
||||
|
||||
def getPhoneAccountList(self) -> List[Any]:
|
||||
"""获取电话账户列表"""
|
||||
return list(self._phone_accounts.values())
|
||||
|
||||
def getSimConfig(self, accountHandle: Any) -> Optional[Any]:
|
||||
"""获取 SIM 配置"""
|
||||
return None
|
||||
|
||||
def registerPhoneAccount(self, account: "PhoneAccount"):
|
||||
"""注册电话账户"""
|
||||
self._phone_accounts[str(account.getAccountHandle())] = account
|
||||
|
||||
def unregisterPhoneAccount(self, accountHandle: Any):
|
||||
"""注销电话账户"""
|
||||
self._phone_accounts.pop(str(accountHandle), None)
|
||||
|
||||
def clearPhoneAccounts(self):
|
||||
"""清除所有电话账户"""
|
||||
self._phone_accounts.clear()
|
||||
|
||||
def clearAccounts(self):
|
||||
"""清除所有账户"""
|
||||
self._phone_accounts.clear()
|
||||
|
||||
def showInCallScreen(self, showDialpad: bool):
|
||||
"""显示通话界面"""
|
||||
pass
|
||||
|
||||
def acceptRingingCall(self):
|
||||
"""接听响铃呼叫"""
|
||||
pass
|
||||
|
||||
def silenceRinger(self):
|
||||
"""静音铃声"""
|
||||
pass
|
||||
|
||||
def isInCall(self) -> bool:
|
||||
"""检查是否正在通话"""
|
||||
return False
|
||||
|
||||
def isInManagedCall(self) -> bool:
|
||||
"""检查是否在托管通话中"""
|
||||
return False
|
||||
|
||||
def addNewIncomingCall(self, phoneAccount: "PhoneAccount", extras: Any = None):
|
||||
"""添加新的来电"""
|
||||
pass
|
||||
|
||||
def addNewUnknownCall(self, phoneAccount: "PhoneAccount", extras: Any = None):
|
||||
"""添加新的未知呼叫"""
|
||||
pass
|
||||
|
||||
def handleCreateConferenceComplete(
|
||||
self, callId: str, response: Any
|
||||
):
|
||||
"""处理创建会议完成"""
|
||||
pass
|
||||
|
||||
def transferCall(self, callId: str, targetPhoneAccount: Any):
|
||||
"""转移呼叫"""
|
||||
pass
|
||||
|
||||
def setUserSelectedOutgoingPhoneAccount(
|
||||
self, accountHandle: Any
|
||||
):
|
||||
"""设置用户选择的呼出电话账户"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"TelecomManager@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Call
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Call(Stub):
|
||||
"""Call — 通话 (桌面存根)"""
|
||||
|
||||
STATE_ACTIVE = 1
|
||||
STATE_HOLDING = 2
|
||||
STATE_DIALING = 3
|
||||
STATE_RINGING = 4
|
||||
STATE_CONNECTING = 5
|
||||
STATE_DISCONNECTED = 6
|
||||
STATE_DISCONNECTING = 7
|
||||
STATE_SELECT_PHONE_ACCOUNT = 8
|
||||
STATE_NEW = 9
|
||||
STATE_PULLING_CALL = 10
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._state = self.STATE_NEW
|
||||
self._details: Optional["Call.Details"] = None
|
||||
self._conferenceable_calls: List["Call"] = []
|
||||
self._callbacks: List[Callable] = []
|
||||
self._listeners: List[Callable] = []
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def getDetails(self) -> Optional["Call.Details"]:
|
||||
return self._details
|
||||
|
||||
def getConferenceableCalls(self) -> List["Call"]:
|
||||
return list(self._conferenceable_calls)
|
||||
|
||||
def registerCallback(self, callback: Callable):
|
||||
if callback not in self._callbacks:
|
||||
self._callbacks.append(callback)
|
||||
|
||||
def unregisterCallback(self, callback: Callable):
|
||||
if callback in self._callbacks:
|
||||
self._callbacks.remove(callback)
|
||||
|
||||
def addListener(self, listener: Callable):
|
||||
if listener not in self._listeners:
|
||||
self._listeners.append(listener)
|
||||
|
||||
def removeListener(self, listener: Callable):
|
||||
if listener in self._listeners:
|
||||
self._listeners.remove(listener)
|
||||
|
||||
def answer(self, videoState: int = 0):
|
||||
"""接听通话"""
|
||||
self._state = self.STATE_ACTIVE
|
||||
|
||||
def reject(self, rejectWithMessage: bool = False, textMessage: str = ""):
|
||||
"""拒绝通话"""
|
||||
self._state = self.STATE_DISCONNECTED
|
||||
|
||||
def disconnect(self):
|
||||
"""挂断通话"""
|
||||
self._state = self.STATE_DISCONNECTED
|
||||
|
||||
def hold(self):
|
||||
"""保持通话"""
|
||||
self._state = self.STATE_HOLDING
|
||||
|
||||
def unhold(self):
|
||||
"""取消保持"""
|
||||
self._state = self.STATE_ACTIVE
|
||||
|
||||
def playDtmfTone(self, digit: str):
|
||||
"""播放 DTMF 音"""
|
||||
pass
|
||||
|
||||
def stopDtmfTone(self):
|
||||
"""停止 DTMF 音"""
|
||||
pass
|
||||
|
||||
def postDialWait(self, remaining: str):
|
||||
"""后拨号等待"""
|
||||
pass
|
||||
|
||||
def mergeConference(self):
|
||||
"""合并会议"""
|
||||
pass
|
||||
|
||||
def splitFromConference(self):
|
||||
"""从会议分离"""
|
||||
pass
|
||||
|
||||
def sendCallEvent(self, event: str, extras: Any = None):
|
||||
"""发送通话事件"""
|
||||
pass
|
||||
|
||||
def putExtra(self, key: str, value: Any):
|
||||
"""设置额外信息"""
|
||||
pass
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Details
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Details(Stub):
|
||||
"""Call.Details — 通话详细信息"""
|
||||
|
||||
DIRECTION_INCOMING = 1
|
||||
DIRECTION_OUTGOING = 2
|
||||
|
||||
CAPABILITY_HOLD = 1
|
||||
CAPABILITY_SUPPORT_HOLD = 2
|
||||
CAPABILITY_MERGE_CONFERENCE = 4
|
||||
CAPABILITY_SWAP_CONFERENCE = 8
|
||||
CAPABILITY_RESPOND_VIA_TEXT = 16
|
||||
CAPABILITY_MUTE = 32
|
||||
CAPABILITY_MANAGE_CONFERENCE = 64
|
||||
CAPABILITY_SUPPORTS_VT_LOCAL = 128
|
||||
CAPABILITY_SUPPORTS_VT_REMOTE = 256
|
||||
CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 512
|
||||
CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 1024
|
||||
CAPABILITY_SUPPORTS_DOWNGRADE_VT_TO_AUDIO = 2048
|
||||
CAPABILITY_SUPPORTS_VIDEO_CALLING = 4096
|
||||
CAPABILITY_SUPPORTS_VT_LOCAL_RX = 8192
|
||||
CAPABILITY_SUPPORTS_VT_LOCAL_TX = 16384
|
||||
CAPABILITY_SUPPORTS_VT_REMOTE_RX = 32768
|
||||
CAPABILITY_SUPPORTS_VT_REMOTE_TX = 65536
|
||||
CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 131072
|
||||
CAPABILITY_CAN_PAUSE_VIDEO = 262144
|
||||
CAPABILITY_SELF_MANAGED = 524288
|
||||
CAPABILITY_SUPPORTS_VOIP = 1048576
|
||||
CAPABILITY_ADD_PARTICIPANT = 2097152
|
||||
CAPABILITY_CAN_UPGRADE_TO_VIDEO = 4194304
|
||||
|
||||
PROPERTY_HIGH_DEF_AUDIO = 1
|
||||
PROPERTY_WIFI = 2
|
||||
PROPERTY_EMERGENCY_CALLBACK = 4
|
||||
PROPERTY_IS_EXTERNAL_CALL = 8
|
||||
PROPERTY_HAS_CDMA_VOICE_PRIVACY = 16
|
||||
PROPERTY_SELF_MANAGED = 32
|
||||
PROPERTY_ASSISTED_DIALING = 64
|
||||
PROPERTY_NETWORK_IDENTIFIED = 128
|
||||
PROPERTY_CALL_AUTO_FORWARDED = 256
|
||||
PROPERTY_CALL_TYPE_UNKNOWN = 512
|
||||
PROPERTY_IS_VOIP_AUDIO_MODE = 1024
|
||||
PROPERTY_IS_DOWNGRADED_CALL = 2048
|
||||
PROPERTY_ENTITLEMENT_CHANNEL = 4096
|
||||
PROPERTY_PULLED_CALL = 8192
|
||||
PROPERTY_CROSS_SIM = 16384
|
||||
PROPERTY_VIDEO_CALL = 32768
|
||||
PROPERTY_RTT = 65536
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._handle: Any = None
|
||||
self._gateway_info: Any = None
|
||||
self._caller_display_name: str = ""
|
||||
self._call_direction = self.DIRECTION_INCOMING
|
||||
self._account_handle: Any = None
|
||||
self._call_capabilities = 0
|
||||
self._call_properties = 0
|
||||
self._caller_info: Any = None
|
||||
self._creation_time_millis = 0
|
||||
self._connect_time_millis = 0
|
||||
self._disconnect_cause: Any = None
|
||||
self._video_state = 0
|
||||
self._intent_extras: Any = None
|
||||
self._extras: Any = None
|
||||
|
||||
def getHandle(self) -> Optional[Any]:
|
||||
return self._handle
|
||||
|
||||
def getGatewayInfo(self) -> Optional[Any]:
|
||||
return self._gateway_info
|
||||
|
||||
def getCallerDisplayName(self) -> str:
|
||||
return self._caller_display_name
|
||||
|
||||
def getCallDirection(self) -> int:
|
||||
return self._call_direction
|
||||
|
||||
def getAccountHandle(self) -> Optional[Any]:
|
||||
return self._account_handle
|
||||
|
||||
def getCallCapabilities(self) -> int:
|
||||
return self._call_capabilities
|
||||
|
||||
def getCallProperties(self) -> int:
|
||||
return self._call_properties
|
||||
|
||||
def getCallerInfo(self) -> Optional[Any]:
|
||||
return self._caller_info
|
||||
|
||||
def getCreationTimeMillis(self) -> int:
|
||||
return self._creation_time_millis
|
||||
|
||||
def getConnectTimeMillis(self) -> int:
|
||||
return self._connect_time_millis
|
||||
|
||||
def getDisconnectCause(self) -> Optional["DisconnectCause"]:
|
||||
return self._disconnect_cause
|
||||
|
||||
def getVideoState(self) -> int:
|
||||
return self._video_state
|
||||
|
||||
def getIntentExtras(self) -> Optional[Any]:
|
||||
return self._intent_extras
|
||||
|
||||
def getExtras(self) -> Optional[Any]:
|
||||
return self._extras
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Call.Details(handle={self._handle})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Call(state={self._state})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Call):
|
||||
return False
|
||||
return self._state == other._state and self._details == other._details
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._state & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# CallAudioState
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class CallAudioState(Stub):
|
||||
"""CallAudioState — 通话音频状态"""
|
||||
|
||||
ROUTE_EARPIECE = 1
|
||||
ROUTE_SPEAKER = 2
|
||||
ROUTE_BLUETOOTH = 4
|
||||
ROUTE_WIRED_HEADSET = 8
|
||||
ROUTE_WIRED_OR_EARPIECE = 9
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._route = self.ROUTE_EARPIECE
|
||||
self._supported_route_mask = (
|
||||
self.ROUTE_EARPIECE | self.ROUTE_SPEAKER | self.ROUTE_WIRED_HEADSET
|
||||
)
|
||||
|
||||
def getRoute(self) -> int:
|
||||
return self._route
|
||||
|
||||
def getSupportedRouteMask(self) -> int:
|
||||
return self._supported_route_mask
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"CallAudioState(route={self._route})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, CallAudioState):
|
||||
return False
|
||||
return self._route == other._route
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._route & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DisconnectCause
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DisconnectCause(Stub):
|
||||
"""DisconnectCause — 断连原因"""
|
||||
|
||||
LOCAL = 1
|
||||
REMOTE = 2
|
||||
BUSY = 3
|
||||
CANCELED = 4
|
||||
NO_ANSWER = 5
|
||||
MISSED = 6
|
||||
ERROR = 7
|
||||
REJECTED = 8
|
||||
NUMBER_UNREACHABLE = 9
|
||||
RESTRICTED = 10
|
||||
OTHER = 11
|
||||
ANSWERED_ELSEWHERE = 12
|
||||
CALL_PULLED = 13
|
||||
|
||||
def __init__(self, causeCode: int = OTHER):
|
||||
super().__init__()
|
||||
self._cause_code = causeCode
|
||||
self._reason: str = ""
|
||||
self._description: str = ""
|
||||
|
||||
def getCode(self) -> int:
|
||||
return self._cause_code
|
||||
|
||||
def getReason(self) -> str:
|
||||
return self._reason
|
||||
|
||||
def getDescription(self) -> str:
|
||||
return self._description
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"DisconnectCause(code={self._cause_code})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, DisconnectCause):
|
||||
return False
|
||||
return self._cause_code == other._cause_code
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return self._cause_code & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PhoneAccount
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PhoneAccount(Stub):
|
||||
"""PhoneAccount — 电话账户"""
|
||||
|
||||
CAP_HOLD = 1
|
||||
CAP_ADD_CALL = 2
|
||||
CAP_MULTI_ENDPOINT = 4
|
||||
CAP_CONFERENCE = 8
|
||||
CAP_VIDEO_CALLING = 16
|
||||
CAP_CALL_COMPOSER = 32
|
||||
CAP_CONNECTION_MANAGER = 64
|
||||
|
||||
EXTRA_LOG_SELF_MANAGED_CALLS = "android.telecom.extra.LOG_SELF_MANAGED_CALLS"
|
||||
EXTRA_ADD_SELF_MANAGED_CALLS = "android.telecom.extra.ADD_SELF_MANAGED_CALLS"
|
||||
|
||||
SCREENING_ALLOW = 0
|
||||
SCREENING_STOP = 1
|
||||
SCREENING_SILENCE = 2
|
||||
SCREENING_DISALLOW = 3
|
||||
|
||||
SCHEME_SIP = "sip"
|
||||
SCHEME_TEL = "tel"
|
||||
SCHEME_VOICEMAIL = "voicemail"
|
||||
|
||||
class Builder(Stub):
|
||||
"""PhoneAccount.Builder — 电话账户构建器"""
|
||||
|
||||
def __init__(self, accountHandle: Any, label: str):
|
||||
super().__init__()
|
||||
self._account = PhoneAccount()
|
||||
self._account._account_handle = accountHandle
|
||||
self._account._label = label
|
||||
|
||||
def setCapabilities(self, capabilities: int) -> "PhoneAccount.Builder":
|
||||
self._account._capabilities = capabilities
|
||||
return self
|
||||
|
||||
def setAddress(self, address: Any) -> "PhoneAccount.Builder":
|
||||
self._account._address = address
|
||||
return self
|
||||
|
||||
def setSubscriptionAddress(self, address: Any) -> "PhoneAccount.Builder":
|
||||
self._account._subscription_address = address
|
||||
return self
|
||||
|
||||
def setSupportedUriSchemes(
|
||||
self, uriSchemes: List[str]
|
||||
) -> "PhoneAccount.Builder":
|
||||
self._account._supported_uri_schemes = list(uriSchemes)
|
||||
return self
|
||||
|
||||
def setIcon(self, icon: Any) -> "PhoneAccount.Builder":
|
||||
self._account._icon = icon
|
||||
return self
|
||||
|
||||
def setHighlightColor(self, color: int) -> "PhoneAccount.Builder":
|
||||
self._account._highlight_color = color
|
||||
return self
|
||||
|
||||
def setShortDescription(self, description: str) -> "PhoneAccount.Builder":
|
||||
self._account._short_description = description
|
||||
return self
|
||||
|
||||
def addSupportedUriScheme(
|
||||
self, uriScheme: str
|
||||
) -> "PhoneAccount.Builder":
|
||||
if uriScheme not in self._account._supported_uri_schemes:
|
||||
self._account._supported_uri_schemes.append(uriScheme)
|
||||
return self
|
||||
|
||||
def setExtras(self, extras: Any) -> "PhoneAccount.Builder":
|
||||
self._account._extras = extras
|
||||
return self
|
||||
|
||||
def build(self) -> "PhoneAccount":
|
||||
return self._account
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PhoneAccount.Builder(label={self._account._label})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._account_handle: Any = None
|
||||
self._label: str = ""
|
||||
self._capabilities: int = 0
|
||||
self._address: Any = None
|
||||
self._subscription_address: Any = None
|
||||
self._supported_uri_schemes: List[str] = [self.SCHEME_TEL]
|
||||
self._icon: Any = None
|
||||
self._highlight_color: int = 0
|
||||
self._short_description: str = ""
|
||||
self._extras: Any = None
|
||||
|
||||
def getAccountHandle(self) -> Optional[Any]:
|
||||
return self._account_handle
|
||||
|
||||
def getLabel(self) -> str:
|
||||
return self._label
|
||||
|
||||
def getCapabilities(self) -> int:
|
||||
return self._capabilities
|
||||
|
||||
def getAddress(self) -> Optional[Any]:
|
||||
return self._address
|
||||
|
||||
def getSubscriptionAddress(self) -> Optional[Any]:
|
||||
return self._subscription_address
|
||||
|
||||
def getSupportedUriSchemes(self) -> List[str]:
|
||||
return list(self._supported_uri_schemes)
|
||||
|
||||
def getIcon(self) -> Optional[Any]:
|
||||
return self._icon
|
||||
|
||||
def getHighlightColor(self) -> int:
|
||||
return self._highlight_color
|
||||
|
||||
def getShortDescription(self) -> str:
|
||||
return self._short_description
|
||||
|
||||
def getExtras(self) -> Optional[Any]:
|
||||
return self._extras
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"PhoneAccount(label={self._label})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, PhoneAccount):
|
||||
return False
|
||||
return self._account_handle == other._account_handle
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(str(self._account_handle)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Connection
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Connection(Stub):
|
||||
"""Connection — 通话连接 (桌面存根)"""
|
||||
|
||||
STATE_INITIALIZING = 0
|
||||
STATE_ACTIVE = 1
|
||||
STATE_DIALING = 2
|
||||
STATE_RINGING = 3
|
||||
STATE_HOLDING = 4
|
||||
STATE_DISCONNECTED = 5
|
||||
STATE_PULLING_CALL = 6
|
||||
|
||||
CAPABILITY_HOLD = 1
|
||||
CAPABILITY_SUPPORT_HOLD = 2
|
||||
CAPABILITY_MERGE_CONFERENCE = 4
|
||||
CAPABILITY_SWAP_CONFERENCE = 8
|
||||
CAPABILITY_RESPOND_VIA_TEXT = 16
|
||||
CAPABILITY_MUTE = 32
|
||||
CAPABILITY_MANAGE_CONFERENCE = 64
|
||||
CAPABILITY_SUPPORTS_VT_LOCAL = 128
|
||||
CAPABILITY_SUPPORTS_VT_REMOTE = 256
|
||||
CAPABILITY_SUPPORTS_VIDEO_CALLING = 4096
|
||||
CAPABILITY_SELF_MANAGED = 524288
|
||||
CAPABILITY_SUPPORTS_VOIP = 1048576
|
||||
CAPABILITY_ADD_PARTICIPANT = 2097152
|
||||
|
||||
PROPERTY_SELF_MANAGED = 32
|
||||
PROPERTY_IS_VOIP_AUDIO_MODE = 1024
|
||||
PROPERTY_HIGH_DEF_AUDIO = 1
|
||||
PROPERTY_WIFI = 2
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._state = self.STATE_INITIALIZING
|
||||
self._video_state = 0
|
||||
self._connection_capabilities = 0
|
||||
self._connection_properties = 0
|
||||
self._audio_mode_is_voip = False
|
||||
self._conferenceable_connections: List["Connection"] = []
|
||||
self._extras: Dict[str, Any] = {}
|
||||
|
||||
def setVideoState(self, videoState: int):
|
||||
self._video_state = videoState
|
||||
|
||||
def setActive(self):
|
||||
self._state = self.STATE_ACTIVE
|
||||
|
||||
def setRinging(self):
|
||||
self._state = self.STATE_RINGING
|
||||
|
||||
def setDialing(self):
|
||||
self._state = self.STATE_DIALING
|
||||
|
||||
def setHolding(self):
|
||||
self._state = self.STATE_HOLDING
|
||||
|
||||
def setDisconnected(self, disconnectCause: "DisconnectCause"):
|
||||
self._state = self.STATE_DISCONNECTED
|
||||
|
||||
def setPostDialWait(self, remaining: str):
|
||||
pass
|
||||
|
||||
def setConferenceableConnections(
|
||||
self, conferenceable: List["Connection"]
|
||||
):
|
||||
self._conferenceable_connections = list(conferenceable)
|
||||
|
||||
def setAudioModeIsVoip(self, isVoip: bool):
|
||||
self._audio_mode_is_voip = isVoip
|
||||
|
||||
def setConnectionCapabilities(self, capabilities: int):
|
||||
self._connection_capabilities = capabilities
|
||||
|
||||
def setConnectionProperties(self, properties: int):
|
||||
self._connection_properties = properties
|
||||
|
||||
def putExtra(self, key: str, value: Any):
|
||||
self._extras[key] = value
|
||||
|
||||
def onShowIncomingCallUi(self):
|
||||
pass
|
||||
|
||||
def onCallAudioStateChanged(self, audioState: "CallAudioState"):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
self._state = self.STATE_DISCONNECTED
|
||||
|
||||
def onCallFilteringCompleted(self, completion: Any):
|
||||
pass
|
||||
|
||||
def onSilence(self):
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Connection(state={self._state})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
@@ -0,0 +1,485 @@
|
||||
"""
|
||||
Android Test 组件 — InstrumentationTestRunner, InstrumentationTestCase,
|
||||
ActivityInstrumentationTestCase2, ActivityUnitTestCase, ServiceTestCase,
|
||||
ProviderTestCase2, ApplicationTestCase, Instrumentation 等
|
||||
方法签名完全对齐 Android SDK (android.test 包)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Type
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Instrumentation
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Instrumentation(Stub):
|
||||
"""Instrumentation — 测试仪器 (桌面版)"""
|
||||
|
||||
# 报告标志
|
||||
REPORT_KEY_IDENTIFIER = "id"
|
||||
REPORT_KEY_STREAM_RESULTS = "stream"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._context: Optional[Any] = None
|
||||
self._target_context: Optional[Any] = None
|
||||
self._started = False
|
||||
self._finished = False
|
||||
self._automatic_performance_snapshots = False
|
||||
self._instr_result: Dict[str, Any] = {}
|
||||
|
||||
def onCreate(self, arguments: Any):
|
||||
"""创建仪器"""
|
||||
self._started = True
|
||||
|
||||
def onStart(self):
|
||||
"""启动仪器"""
|
||||
pass
|
||||
|
||||
def onDestroy(self):
|
||||
"""销毁仪器"""
|
||||
self._finished = True
|
||||
|
||||
def getContext(self) -> Optional[Any]:
|
||||
"""获取上下文"""
|
||||
return self._context
|
||||
|
||||
def getTargetContext(self) -> Optional[Any]:
|
||||
"""获取目标上下文"""
|
||||
return self._target_context
|
||||
|
||||
def setContext(self, context: Any):
|
||||
self._context = context
|
||||
|
||||
def setTargetContext(self, target_context: Any):
|
||||
self._target_context = target_context
|
||||
|
||||
def startActivitySync(self, intent: Any) -> Any:
|
||||
"""同步启动 Activity"""
|
||||
return None
|
||||
|
||||
def sendKeySync(self, key_event: Any):
|
||||
"""发送按键事件"""
|
||||
pass
|
||||
|
||||
def sendCharacterSync(self, character: int):
|
||||
"""发送字符事件"""
|
||||
pass
|
||||
|
||||
def sendStringSync(self, text: str):
|
||||
"""发送字符串"""
|
||||
pass
|
||||
|
||||
def sendPointerSync(self, motion_event: Any):
|
||||
"""发送指针事件"""
|
||||
pass
|
||||
|
||||
def sendTrackballEventSync(self, motion_event: Any):
|
||||
"""发送轨迹球事件"""
|
||||
pass
|
||||
|
||||
def runOnMainSync(self, runner: Callable):
|
||||
"""在主线程上同步运行"""
|
||||
runner()
|
||||
|
||||
def waitForIdle(self):
|
||||
"""等待空闲"""
|
||||
pass
|
||||
|
||||
def waitForIdleSync(self):
|
||||
"""同步等待空闲"""
|
||||
pass
|
||||
|
||||
def callActivityOnCreate(self, activity: Any, icicle: Any):
|
||||
"""调用 Activity.onCreate"""
|
||||
if hasattr(activity, 'onCreate'):
|
||||
activity.onCreate(icicle)
|
||||
|
||||
def callActivityOnStart(self, activity: Any):
|
||||
"""调用 Activity.onStart"""
|
||||
if hasattr(activity, 'onStart'):
|
||||
activity.onStart()
|
||||
|
||||
def callActivityOnResume(self, activity: Any):
|
||||
"""调用 Activity.onResume"""
|
||||
if hasattr(activity, 'onResume'):
|
||||
activity.onResume()
|
||||
|
||||
def callActivityOnPause(self, activity: Any):
|
||||
"""调用 Activity.onPause"""
|
||||
if hasattr(activity, 'onPause'):
|
||||
activity.onPause()
|
||||
|
||||
def callActivityOnStop(self, activity: Any):
|
||||
"""调用 Activity.onStop"""
|
||||
if hasattr(activity, 'onStop'):
|
||||
activity.onStop()
|
||||
|
||||
def callActivityOnDestroy(self, activity: Any):
|
||||
"""调用 Activity.onDestroy"""
|
||||
if hasattr(activity, 'onDestroy'):
|
||||
activity.onDestroy()
|
||||
|
||||
def callActivityOnRestart(self, activity: Any):
|
||||
"""调用 Activity.onRestart"""
|
||||
if hasattr(activity, 'onRestart'):
|
||||
activity.onRestart()
|
||||
|
||||
def isProfiling(self) -> bool:
|
||||
return False
|
||||
|
||||
def startProfiling(self):
|
||||
pass
|
||||
|
||||
def stopProfiling(self):
|
||||
pass
|
||||
|
||||
def setInTouchMode(self, in_touch: bool):
|
||||
pass
|
||||
|
||||
def finish(self, result_code: int, results: Any = None):
|
||||
"""完成测试"""
|
||||
self._finished = True
|
||||
if results:
|
||||
self._instr_result.update(results)
|
||||
|
||||
def getComponentName(self) -> Any:
|
||||
return None
|
||||
|
||||
def getUiAutomation(self) -> Any:
|
||||
return None
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"Instrumentation(started={self._started}, "
|
||||
f"finished={self._finished})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InstrumentationTestRunner
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InstrumentationTestRunner(Stub):
|
||||
"""InstrumentationTestRunner — 仪器化测试运行器"""
|
||||
|
||||
REPORT_KEY_NAME = "name"
|
||||
REPORT_KEY_CLASS = "class"
|
||||
REPORT_KEY_TEST = "test"
|
||||
REPORT_KEY_NUM_TOTAL = "numtests"
|
||||
REPORT_KEY_NUM_CURRENT = "current"
|
||||
REPORT_KEY_NUM_ITERATIONS = "numiterations"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._instrumentation = Instrumentation()
|
||||
self._test_results: List[Dict[str, Any]] = []
|
||||
|
||||
def onCreate(self, arguments: Any):
|
||||
"""创建测试运行器"""
|
||||
self._instrumentation.onCreate(arguments)
|
||||
|
||||
def onStart(self):
|
||||
"""开始测试"""
|
||||
self._instrumentation.onStart()
|
||||
|
||||
def onDestroy(self):
|
||||
"""销毁"""
|
||||
self._instrumentation.onDestroy()
|
||||
|
||||
def getInstrumentation(self) -> Instrumentation:
|
||||
return self._instrumentation
|
||||
|
||||
def runTest(self, test_case: Any):
|
||||
"""运行测试用例"""
|
||||
try:
|
||||
if hasattr(test_case, 'setUp'):
|
||||
test_case.setUp()
|
||||
if hasattr(test_case, 'runTest'):
|
||||
test_case.runTest()
|
||||
if hasattr(test_case, 'tearDown'):
|
||||
test_case.tearDown()
|
||||
self._test_results.append({"success": True, "test": str(test_case)})
|
||||
except Exception as e:
|
||||
self._test_results.append({
|
||||
"success": False, "test": str(test_case), "error": str(e),
|
||||
})
|
||||
|
||||
def getTestResults(self) -> List[Dict[str, Any]]:
|
||||
return list(self._test_results)
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"InstrumentationTestRunner("
|
||||
f"results={len(self._test_results)})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# InstrumentationTestCase
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class InstrumentationTestCase(Stub):
|
||||
"""InstrumentationTestCase — 仪器化测试用例基类"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._instrumentation: Optional[Instrumentation] = None
|
||||
self._test_name: str = ""
|
||||
|
||||
def injectInstrumentation(self, instrumentation: Instrumentation):
|
||||
"""注入仪器"""
|
||||
self._instrumentation = instrumentation
|
||||
|
||||
def getInstrumentation(self) -> Optional[Instrumentation]:
|
||||
return self._instrumentation
|
||||
|
||||
def setUp(self):
|
||||
"""测试前准备"""
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
"""测试后清理"""
|
||||
pass
|
||||
|
||||
def runTest(self):
|
||||
"""执行测试"""
|
||||
pass
|
||||
|
||||
def sendKeys(self, *keys: str):
|
||||
"""发送按键"""
|
||||
pass
|
||||
|
||||
def touchDown(self, view: Any, x: float, y: float):
|
||||
"""触摸按下"""
|
||||
pass
|
||||
|
||||
def touchUp(self, view: Any, x: float, y: float):
|
||||
"""触摸抬起"""
|
||||
pass
|
||||
|
||||
def touchAndHold(self, view: Any, x: float, y: float):
|
||||
"""触摸并按住"""
|
||||
pass
|
||||
|
||||
def click(self, view: Any):
|
||||
"""点击"""
|
||||
pass
|
||||
|
||||
def typeText(self, view: Any, text: str):
|
||||
"""输入文本"""
|
||||
pass
|
||||
|
||||
def assertActivityRequiresFeature(self, activity_class: Any,
|
||||
feature: str) -> bool:
|
||||
return True
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"InstrumentationTestCase(name={self._test_name})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self._test_name) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, InstrumentationTestCase):
|
||||
return False
|
||||
return self._test_name == other._test_name
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ActivityInstrumentationTestCase2
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ActivityInstrumentationTestCase2(InstrumentationTestCase):
|
||||
"""ActivityInstrumentationTestCase2 — Activity 测试用例"""
|
||||
|
||||
def __init__(self, pkg: str, activity_class: Type):
|
||||
super().__init__()
|
||||
self._pkg = pkg
|
||||
self._activity_class = activity_class
|
||||
self._activity: Optional[Any] = None
|
||||
|
||||
def setActivityIntent(self, intent: Any):
|
||||
"""设置 Activity Intent"""
|
||||
pass
|
||||
|
||||
def getActivity(self) -> Optional[Any]:
|
||||
"""获取 Activity 实例"""
|
||||
return self._activity
|
||||
|
||||
def testActivityCreated(self) -> bool:
|
||||
return self._activity is not None
|
||||
|
||||
def testActivityResumed(self) -> bool:
|
||||
return self._activity is not None
|
||||
|
||||
def testActivityStarted(self) -> bool:
|
||||
return self._activity is not None
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"ActivityInstrumentationTestCase2("
|
||||
f"pkg={self._pkg}, cls={self._activity_class})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ActivityUnitTestCase
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ActivityUnitTestCase(InstrumentationTestCase):
|
||||
"""ActivityUnitTestCase — Activity 单元测试"""
|
||||
|
||||
def __init__(self, activity_class: Type):
|
||||
super().__init__()
|
||||
self._activity_class = activity_class
|
||||
self._activity: Optional[Any] = None
|
||||
|
||||
def setActivityContext(self, context: Any):
|
||||
"""设置 Activity 上下文"""
|
||||
if self._activity and hasattr(self._activity, 'setContext'):
|
||||
self._activity.setContext(context)
|
||||
|
||||
def setApplication(self, application: Any):
|
||||
"""设置 Application"""
|
||||
if self._activity and hasattr(self._activity, 'setApplication'):
|
||||
self._activity.setApplication(application)
|
||||
|
||||
def getActivity(self) -> Optional[Any]:
|
||||
return self._activity
|
||||
|
||||
def startActivity(self, intent: Any, bundle: Any = None,
|
||||
last_non_config_instance: Any = None) -> Any:
|
||||
"""启动 Activity"""
|
||||
return self._activity
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ActivityUnitTestCase(cls={self._activity_class})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ServiceTestCase
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ServiceTestCase(InstrumentationTestCase):
|
||||
"""ServiceTestCase — Service 测试用"""
|
||||
|
||||
def __init__(self, service_class: Type):
|
||||
super().__init__()
|
||||
self._service_class = service_class
|
||||
self._service: Optional[Any] = None
|
||||
self._service_binder: Optional[Any] = None
|
||||
self._service_created = False
|
||||
|
||||
def getService(self) -> Optional[Any]:
|
||||
return self._service
|
||||
|
||||
def bindService(self, intent: Any) -> Any:
|
||||
"""绑定 Service"""
|
||||
return self._service_binder
|
||||
|
||||
def bindServiceNotNeeded(self):
|
||||
"""Service 不需要绑定"""
|
||||
pass
|
||||
|
||||
def setService(self, service: Any):
|
||||
self._service = service
|
||||
|
||||
def testServiceCreated(self) -> bool:
|
||||
return self._service_created
|
||||
|
||||
def testServiceBound(self) -> bool:
|
||||
return self._service_binder is not None
|
||||
|
||||
def testServiceStarted(self) -> bool:
|
||||
return self._service is not None
|
||||
|
||||
def testServiceStopped(self) -> bool:
|
||||
return not self._service_created
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ServiceTestCase(cls={self._service_class})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ProviderTestCase2
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ProviderTestCase2(InstrumentationTestCase):
|
||||
"""ProviderTestCase2 — ContentProvider 测试用例"""
|
||||
|
||||
def __init__(self, provider_class: Type, provider_authority: str):
|
||||
super().__init__()
|
||||
self._provider_class = provider_class
|
||||
self._provider_authority = provider_authority
|
||||
self._provider: Optional[Any] = None
|
||||
self._resolver: Optional[Any] = None
|
||||
|
||||
def getProvider(self) -> Optional[Any]:
|
||||
return self._provider
|
||||
|
||||
def getMockContentResolver(self) -> Optional[Any]:
|
||||
return self._resolver
|
||||
|
||||
def setProvider(self, provider: Any):
|
||||
self._provider = provider
|
||||
|
||||
def setMockContentResolver(self, resolver: Any):
|
||||
self._resolver = resolver
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"ProviderTestCase2(cls={self._provider_class}, "
|
||||
f"authority={self._provider_authority})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ApplicationTestCase
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ApplicationTestCase(InstrumentationTestCase):
|
||||
"""ApplicationTestCase — Application 测试用例"""
|
||||
|
||||
def __init__(self, application_class: Type):
|
||||
super().__init__()
|
||||
self._application_class = application_class
|
||||
self._application: Optional[Any] = None
|
||||
|
||||
def getApplication(self) -> Optional[Any]:
|
||||
return self._application
|
||||
|
||||
def setApplication(self, application: Any):
|
||||
self._application = application
|
||||
|
||||
def testApplicationCreated(self) -> bool:
|
||||
return self._application is not None
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ApplicationTestCase(cls={self._application_class})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"InstrumentationTestRunner",
|
||||
"InstrumentationTestCase",
|
||||
"ActivityInstrumentationTestCase2",
|
||||
"ActivityUnitTestCase",
|
||||
"ServiceTestCase",
|
||||
"ProviderTestCase2",
|
||||
"ApplicationTestCase",
|
||||
"Instrumentation",
|
||||
]
|
||||
@@ -0,0 +1,504 @@
|
||||
"""
|
||||
Android Transition 组件 — Transition, TransitionManager, Scene, Fade, Slide, Explode 等
|
||||
方法签名完全对齐 Android SDK (android.transition 包)
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Scene
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Scene(Stub):
|
||||
"""Scene — 转场场景"""
|
||||
|
||||
def __init__(self, root: Any = None, layout: Any = None):
|
||||
super().__init__()
|
||||
self._root = root
|
||||
self._layout = layout
|
||||
self._entered = False
|
||||
|
||||
@staticmethod
|
||||
def getSceneForLayout(root: Any, layout_id: int, context: Any) -> "Scene":
|
||||
"""从布局资源创建场景"""
|
||||
return Scene(root, layout_id)
|
||||
|
||||
def getRoot(self) -> Any:
|
||||
return self._root
|
||||
|
||||
def enter(self):
|
||||
"""进入场景"""
|
||||
self._entered = True
|
||||
|
||||
def exit(self):
|
||||
"""退出场景"""
|
||||
self._entered = False
|
||||
|
||||
def isEntered(self) -> bool:
|
||||
return self._entered
|
||||
|
||||
def setEnterAction(self, action: Callable):
|
||||
"""设置进入动作"""
|
||||
self._enter_action = action
|
||||
|
||||
def setExitAction(self, action: Callable):
|
||||
"""设置退出动作"""
|
||||
self._exit_action = action
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Scene(root={self._root}, entered={self._entered})"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(id(self._root)) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, Scene):
|
||||
return False
|
||||
return self._root is other._root
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Transition
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Transition(Stub):
|
||||
"""Transition — 转场动画基类"""
|
||||
|
||||
# 匹配模式
|
||||
MATCH_VIEW_NAME = 0
|
||||
MATCH_ID = 1
|
||||
MATCH_INSTANCE = 2
|
||||
MATCH_ITEM_ID = 3
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._duration: int = 300
|
||||
self._start_delay: int = 0
|
||||
self._interpolator: Optional[Any] = None
|
||||
self._targets: Set[Any] = set()
|
||||
self._target_ids: Set[int] = set()
|
||||
self._target_names: Set[str] = set()
|
||||
self._target_types: Set[type] = set()
|
||||
self._exclude_targets: Set[Any] = set()
|
||||
self._exclude_ids: Set[int] = set()
|
||||
self._exclude_names: Set[str] = set()
|
||||
self._exclude_types: Set[type] = set()
|
||||
self._listeners: List[Any] = []
|
||||
self._name: str = ""
|
||||
self._epicenter_callback: Optional[Callable] = None
|
||||
self._path_motion: Optional[Any] = None
|
||||
self._propagation: Optional[Any] = None
|
||||
self._paused: bool = False
|
||||
self._started: bool = False
|
||||
self._ended: bool = False
|
||||
|
||||
def setDuration(self, duration_ms: int) -> "Transition":
|
||||
"""设置持续时间 (毫秒)"""
|
||||
self._duration = max(0, duration_ms)
|
||||
return self
|
||||
|
||||
def getDuration(self) -> int:
|
||||
return self._duration
|
||||
|
||||
def setStartDelay(self, start_delay: int) -> "Transition":
|
||||
"""设置起始延迟 (毫秒)"""
|
||||
self._start_delay = max(0, start_delay)
|
||||
return self
|
||||
|
||||
def getStartDelay(self) -> int:
|
||||
return self._start_delay
|
||||
|
||||
def setInterpolator(self, interpolator: Any) -> "Transition":
|
||||
"""设置插值器"""
|
||||
self._interpolator = interpolator
|
||||
return self
|
||||
|
||||
def getInterpolator(self) -> Any:
|
||||
return self._interpolator
|
||||
|
||||
def addTarget(self, target: Any) -> "Transition":
|
||||
"""添加动画目标"""
|
||||
if isinstance(target, int):
|
||||
self._target_ids.add(target)
|
||||
elif isinstance(target, str):
|
||||
self._target_names.add(target)
|
||||
elif isinstance(target, type):
|
||||
self._target_types.add(target)
|
||||
else:
|
||||
self._targets.add(target)
|
||||
return self
|
||||
|
||||
def removeTarget(self, target: Any) -> "Transition":
|
||||
"""移除动画目标"""
|
||||
if isinstance(target, int):
|
||||
self._target_ids.discard(target)
|
||||
elif isinstance(target, str):
|
||||
self._target_names.discard(target)
|
||||
elif isinstance(target, type):
|
||||
self._target_types.discard(target)
|
||||
else:
|
||||
self._targets.discard(target)
|
||||
return self
|
||||
|
||||
def getTargets(self) -> List[Any]:
|
||||
return list(self._targets)
|
||||
|
||||
def excludeTarget(self, target: Any, exclude: bool = True) -> "Transition":
|
||||
"""排除目标"""
|
||||
if exclude:
|
||||
if isinstance(target, int):
|
||||
self._exclude_ids.add(target)
|
||||
elif isinstance(target, str):
|
||||
self._exclude_names.add(target)
|
||||
elif isinstance(target, type):
|
||||
self._exclude_types.add(target)
|
||||
else:
|
||||
self._exclude_targets.add(target)
|
||||
else:
|
||||
if isinstance(target, int):
|
||||
self._exclude_ids.discard(target)
|
||||
elif isinstance(target, str):
|
||||
self._exclude_names.discard(target)
|
||||
elif isinstance(target, type):
|
||||
self._exclude_types.discard(target)
|
||||
else:
|
||||
self._exclude_targets.discard(target)
|
||||
return self
|
||||
|
||||
def addListener(self, listener: Any) -> "Transition":
|
||||
"""添加转场监听器"""
|
||||
if listener not in self._listeners:
|
||||
self._listeners.append(listener)
|
||||
return self
|
||||
|
||||
def removeListener(self, listener: Any) -> "Transition":
|
||||
"""移除转场监听器"""
|
||||
if listener in self._listeners:
|
||||
self._listeners.remove(listener)
|
||||
return self
|
||||
|
||||
def getListeners(self) -> List[Any]:
|
||||
return list(self._listeners)
|
||||
|
||||
def setName(self, name: str) -> "Transition":
|
||||
self._name = name
|
||||
return self
|
||||
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def setEpicenterCallback(self, callback: Callable) -> "Transition":
|
||||
self._epicenter_callback = callback
|
||||
return self
|
||||
|
||||
def getEpicenterCallback(self) -> Optional[Callable]:
|
||||
return self._epicenter_callback
|
||||
|
||||
def setPathMotion(self, path_motion: Any) -> "Transition":
|
||||
self._path_motion = path_motion
|
||||
return self
|
||||
|
||||
def getPathMotion(self) -> Any:
|
||||
return self._path_motion
|
||||
|
||||
def setPropagation(self, propagation: Any) -> "Transition":
|
||||
self._propagation = propagation
|
||||
return self
|
||||
|
||||
def getPropagation(self) -> Any:
|
||||
return self._propagation
|
||||
|
||||
def isPaused(self) -> bool:
|
||||
return self._paused
|
||||
|
||||
def pause(self):
|
||||
"""暂停转场"""
|
||||
self._paused = True
|
||||
|
||||
def resume(self):
|
||||
"""恢复转场"""
|
||||
self._paused = False
|
||||
|
||||
def cancel(self):
|
||||
"""取消转场"""
|
||||
self._started = False
|
||||
self._ended = True
|
||||
|
||||
def end(self):
|
||||
"""结束转场"""
|
||||
self._ended = True
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"{self.__class__.__name__}(duration={self._duration}, "
|
||||
f"startDelay={self._start_delay}, targets={len(self._targets)})")
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def captureStartValues(self, view: Any):
|
||||
"""捕获视图起始值"""
|
||||
pass
|
||||
|
||||
def captureEndValues(self, view: Any):
|
||||
"""捕获视图结束值"""
|
||||
pass
|
||||
|
||||
def createAnimator(self, view: Any, start_values: Any,
|
||||
end_values: Any) -> Optional[Any]:
|
||||
"""创建动画器"""
|
||||
return None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TransitionSet
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TransitionSet(Transition):
|
||||
"""TransitionSet — 转场合集"""
|
||||
|
||||
ORDERING_TOGETHER = 0
|
||||
ORDERING_SEQUENTIAL = 1
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._ordering = TransitionSet.ORDERING_TOGETHER
|
||||
self._transitions: List[Transition] = []
|
||||
|
||||
def setOrdering(self, ordering: int) -> "TransitionSet":
|
||||
"""设置播放顺序"""
|
||||
self._ordering = ordering
|
||||
return self
|
||||
|
||||
def getOrdering(self) -> int:
|
||||
return self._ordering
|
||||
|
||||
def addTransition(self, transition: Transition) -> "TransitionSet":
|
||||
"""添加子转场"""
|
||||
self._transitions.append(transition)
|
||||
return self
|
||||
|
||||
def removeTransition(self, transition: Transition) -> "TransitionSet":
|
||||
"""移除子转场"""
|
||||
if transition in self._transitions:
|
||||
self._transitions.remove(transition)
|
||||
return self
|
||||
|
||||
def getTransitionAt(self, index: int) -> Optional[Transition]:
|
||||
"""获取指定索引的子转场"""
|
||||
if 0 <= index < len(self._transitions):
|
||||
return self._transitions[index]
|
||||
return None
|
||||
|
||||
def getTransitionCount(self) -> int:
|
||||
return len(self._transitions)
|
||||
|
||||
def toString(self) -> str:
|
||||
return (f"TransitionSet(ordering={self._ordering}, "
|
||||
f"count={len(self._transitions)})")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AutoTransition
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AutoTransition(TransitionSet):
|
||||
"""AutoTransition — 自动过渡转场"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setOrdering(TransitionSet.ORDERING_TOGETHER)
|
||||
self._duration = 300
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AutoTransition(duration={self._duration})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Fade
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Fade(Transition):
|
||||
"""Fade — 淡入淡出转场"""
|
||||
|
||||
IN = 0x00000001
|
||||
OUT = 0x00000002
|
||||
|
||||
def __init__(self, fading_mode: int = IN | OUT):
|
||||
super().__init__()
|
||||
self._fading_mode = fading_mode
|
||||
|
||||
def getFadingMode(self) -> int:
|
||||
return self._fading_mode
|
||||
|
||||
def setFadingMode(self, fading_mode: int):
|
||||
self._fading_mode = fading_mode
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Fade(mode={self._fading_mode}, duration={self._duration})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ChangeBounds
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ChangeBounds(Transition):
|
||||
"""ChangeBounds — 位置变化转场"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._resize_clip: bool = False
|
||||
self._reparent: bool = False
|
||||
self._reparent_with_overlay: bool = True
|
||||
|
||||
def setResizeClip(self, resize_clip: bool):
|
||||
self._resize_clip = resize_clip
|
||||
|
||||
def getResizeClip(self) -> bool:
|
||||
return self._resize_clip
|
||||
|
||||
def setReparent(self, reparent: bool):
|
||||
self._reparent = reparent
|
||||
|
||||
def getReparent(self) -> bool:
|
||||
return self._reparent
|
||||
|
||||
def setReparentWithOverlay(self, reparent_with_overlay: bool):
|
||||
self._reparent_with_overlay = reparent_with_overlay
|
||||
|
||||
def getReparentWithOverlay(self) -> bool:
|
||||
return self._reparent_with_overlay
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ChangeBounds(duration={self._duration}, resizeClip={self._resize_clip})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ChangeTransform
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ChangeTransform(Transition):
|
||||
"""ChangeTransform — 变换属性转场"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._reparent: bool = False
|
||||
self._reparent_with_overlay: bool = True
|
||||
|
||||
def setReparent(self, reparent: bool):
|
||||
self._reparent = reparent
|
||||
|
||||
def getReparent(self) -> bool:
|
||||
return self._reparent
|
||||
|
||||
def setReparentWithOverlay(self, reparent_with_overlay: bool):
|
||||
self._reparent_with_overlay = reparent_with_overlay
|
||||
|
||||
def getReparentWithOverlay(self) -> bool:
|
||||
return self._reparent_with_overlay
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ChangeTransform(duration={self._duration})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Slide
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Slide(Transition):
|
||||
"""Slide — 滑动转场"""
|
||||
|
||||
TOP = 0x00000001
|
||||
BOTTOM = 0x00000002
|
||||
START = 0x00000003
|
||||
END = 0x00000004
|
||||
RIGHT = 0x00000005
|
||||
LEFT = 0x00000006
|
||||
|
||||
def __init__(self, slide_edge: int = BOTTOM):
|
||||
super().__init__()
|
||||
self._slide_edge = slide_edge
|
||||
|
||||
def getSlideEdge(self) -> int:
|
||||
return self._slide_edge
|
||||
|
||||
def setSlideEdge(self, slide_edge: int):
|
||||
self._slide_edge = slide_edge
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Slide(edge={self._slide_edge}, duration={self._duration})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Explode
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Explode(Transition):
|
||||
"""Explode — 爆炸/散开转场"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Explode(duration={self._duration})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TransitionManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TransitionManager(Stub):
|
||||
"""TransitionManager — 转场管理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def beginDelayedTransition(scene_root: Any,
|
||||
transition: Optional[Transition] = None):
|
||||
"""开始延迟转场"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def endTransitions(scene_root: Any):
|
||||
"""结束转场"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def go(scene: Scene, transition: Optional[Transition] = None):
|
||||
"""执行场景转场"""
|
||||
if scene:
|
||||
scene.enter()
|
||||
|
||||
def toString(self) -> str:
|
||||
return "TransitionManager(desktop-stub)"
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash("android.transition.TransitionManager") & 0x7FFFFFFF
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return isinstance(other, TransitionManager)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"Transition",
|
||||
"TransitionManager",
|
||||
"AutoTransition",
|
||||
"Fade",
|
||||
"ChangeBounds",
|
||||
"ChangeTransform",
|
||||
"Slide",
|
||||
"Explode",
|
||||
"TransitionSet",
|
||||
"Scene",
|
||||
]
|
||||
@@ -0,0 +1,420 @@
|
||||
"""
|
||||
Android uwb 组件 — UwbManager, RangingResult, RangingParameters, UwbAddress 等
|
||||
方法签名完全对齐 Android SDK (UWB API)
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UwbConstants
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UwbConstants(Stub):
|
||||
"""UwbConstants — UWB 常量"""
|
||||
|
||||
STATUS_OK = 0
|
||||
STATUS_FAILURE = 1
|
||||
STATUS_INVALID_PARAMETERS = 2
|
||||
STATUS_SESSION_DOES_NOT_EXIST = 3
|
||||
STATUS_MAX_RANGING_ROUND_RETRY = 4
|
||||
STATUS_RANGING_TX_FAILED = 5
|
||||
STATUS_SESSION_STATE_CHANGE_EXCEPTION = 6
|
||||
STATUS_UNKNOWN_ERROR = 255
|
||||
|
||||
CONFIG_ID_0 = 0
|
||||
CONFIG_ID_1 = 1
|
||||
CONFIG_ID_2 = 2
|
||||
CONFIG_ID_3 = 3
|
||||
|
||||
MEASUREMENT_REPORT_TYPE_DEFERRED = 0
|
||||
MEASUREMENT_REPORT_TYPE_NTF = 1
|
||||
|
||||
RANGING_UPDATE_RATE_AUTO = 0
|
||||
RANGING_UPDATE_RATE_FREQUENCY_1_HZ = 1
|
||||
RANGING_UPDATE_RATE_FREQUENCY_2_HZ = 2
|
||||
RANGING_UPDATE_RATE_FREQUENCY_5_HZ = 5
|
||||
RANGING_UPDATE_RATE_FREQUENCY_10_HZ = 10
|
||||
RANGING_UPDATE_RATE_FREQUENCY_20_HZ = 20
|
||||
RANGING_UPDATE_RATE_FREQUENCY_100_HZ = 100
|
||||
RANGING_UPDATE_RATE_FREQUENCY_250_HZ = 250
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UwbAddress
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UwbAddress(Stub):
|
||||
"""UwbAddress — UWB 地址"""
|
||||
|
||||
SHORT_MAC_ADDRESS = 0
|
||||
EXTENDED_MAC_ADDRESS = 1
|
||||
|
||||
def __init__(self, address: bytes = b"", type_: int = SHORT_MAC_ADDRESS):
|
||||
super().__init__()
|
||||
self._address = address
|
||||
self._type = type_
|
||||
|
||||
def getAddress(self) -> bytes:
|
||||
return self._address
|
||||
|
||||
def getType(self) -> int:
|
||||
return self._type
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"UwbAddress(type={self._type}, addr={self._address.hex()})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AngleOfArrival
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AngleOfArrival(Stub):
|
||||
"""AngleOfArrival — 到达角"""
|
||||
|
||||
STATUS_SUCCESS = 0
|
||||
STATUS_FAILURE = 1
|
||||
|
||||
def __init__(self, azimuth: float = 0.0, elevation: float = 0.0,
|
||||
azimuthConfidence: float = 0.0, elevationConfidence: float = 0.0,
|
||||
status: int = STATUS_FAILURE):
|
||||
super().__init__()
|
||||
self._azimuth = azimuth
|
||||
self._elevation = elevation
|
||||
self._azimuth_confidence = azimuthConfidence
|
||||
self._elevation_confidence = elevationConfidence
|
||||
self._status = status
|
||||
|
||||
def getAzimuth(self) -> float:
|
||||
return self._azimuth
|
||||
|
||||
def getElevation(self) -> float:
|
||||
return self._elevation
|
||||
|
||||
def getAzimuthConfidence(self) -> float:
|
||||
return self._azimuth_confidence
|
||||
|
||||
def getElevationConfidence(self) -> float:
|
||||
return self._elevation_confidence
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DistanceMeasurement
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class DistanceMeasurement(Stub):
|
||||
"""DistanceMeasurement — 距离测量"""
|
||||
|
||||
STATUS_SUCCESS = 0
|
||||
STATUS_FAILURE = 1
|
||||
|
||||
def __init__(self, meters: float = 0.0, confidence: float = 0.0,
|
||||
standardDeviation: float = 0.0, status: int = STATUS_FAILURE):
|
||||
super().__init__()
|
||||
self._meters = meters
|
||||
self._confidence = confidence
|
||||
self._standard_deviation = standardDeviation
|
||||
self._status = status
|
||||
|
||||
def getMeters(self) -> float:
|
||||
return self._meters
|
||||
|
||||
def getConfidence(self) -> float:
|
||||
return self._confidence
|
||||
|
||||
def getStandardDeviation(self) -> float:
|
||||
return self._standard_deviation
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RangingResult
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RangingResult(Stub):
|
||||
"""RangingResult — 测距结果"""
|
||||
|
||||
def __init__(self, uwbDevice: Any = None, remoteDevice: Any = None,
|
||||
status: int = 0, nofRounds: int = 0, nofMeasurements: int = 0,
|
||||
elapsedRealtimeNanos: int = 0,
|
||||
distanceMeasurement: Optional[DistanceMeasurement] = None,
|
||||
angleOfArrival: Optional[AngleOfArrival] = None,
|
||||
rssi: float = -90.0):
|
||||
super().__init__()
|
||||
self._uwb_device = uwbDevice
|
||||
self._remote_device = remoteDevice
|
||||
self._status = status
|
||||
self._nof_rounds = nofRounds
|
||||
self._nof_measurements = nofMeasurements
|
||||
self._elapsed_realtime_nanos = elapsedRealtimeNanos
|
||||
self._distance_measurement = distanceMeasurement
|
||||
self._angle_of_arrival = angleOfArrival
|
||||
self._rssi = rssi
|
||||
self._distance_reported = distanceMeasurement is not None
|
||||
self._aoa_reported = angleOfArrival is not None
|
||||
|
||||
def getUwbDevice(self) -> Any:
|
||||
return self._uwb_device
|
||||
|
||||
def getRemoteDevice(self) -> Any:
|
||||
return self._remote_device
|
||||
|
||||
def getStatus(self) -> int:
|
||||
return self._status
|
||||
|
||||
def getNofRounds(self) -> int:
|
||||
return self._nof_rounds
|
||||
|
||||
def getNofMeasurements(self) -> int:
|
||||
return self._nof_measurements
|
||||
|
||||
def getElapsedRealtimeNanos(self) -> int:
|
||||
return self._elapsed_realtime_nanos
|
||||
|
||||
def getDistanceMeasurement(self) -> Optional[DistanceMeasurement]:
|
||||
return self._distance_measurement
|
||||
|
||||
def getAngleOfArrival(self) -> Optional[AngleOfArrival]:
|
||||
return self._angle_of_arrival
|
||||
|
||||
def getAngleOfArrivalAzimuth(self) -> float:
|
||||
if self._angle_of_arrival:
|
||||
return self._angle_of_arrival.getAzimuth()
|
||||
return 0.0
|
||||
|
||||
def getAngleOfArrivalElevation(self) -> float:
|
||||
if self._angle_of_arrival:
|
||||
return self._angle_of_arrival.getElevation()
|
||||
return 0.0
|
||||
|
||||
def getAngleOfArrivalAzimuthConfidence(self) -> float:
|
||||
if self._angle_of_arrival:
|
||||
return self._angle_of_arrival.getAzimuthConfidence()
|
||||
return 0.0
|
||||
|
||||
def getAngleOfArrivalElevationConfidence(self) -> float:
|
||||
if self._angle_of_arrival:
|
||||
return self._angle_of_arrival.getElevationConfidence()
|
||||
return 0.0
|
||||
|
||||
def getRssi(self) -> float:
|
||||
return self._rssi
|
||||
|
||||
def isDistanceMeasurementReported(self) -> bool:
|
||||
return self._distance_reported
|
||||
|
||||
def isAngleOfArrivalReported(self) -> bool:
|
||||
return self._aoa_reported
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RangingParameters
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RangingParameters(Stub):
|
||||
"""RangingParameters — 测距参数"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""RangingParameters.Builder — 测距参数构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._uwb_config_id = 0
|
||||
self._session_id = 0
|
||||
self._sub_session_id = 0
|
||||
self._ranging_update_rate = 1
|
||||
self._slot_duration = 0
|
||||
self._measurement_report_type = 0
|
||||
self._number_of_controlee = 1
|
||||
self._destination_addresses: List[UwbAddress] = []
|
||||
self._device_configs: List[Any] = []
|
||||
self._vendor_specific_data: List[Any] = []
|
||||
|
||||
def setUwbConfigId(self, configId: int) -> "RangingParameters.Builder":
|
||||
self._uwb_config_id = configId
|
||||
return self
|
||||
|
||||
def setSessionId(self, sessionId: int) -> "RangingParameters.Builder":
|
||||
self._session_id = sessionId
|
||||
return self
|
||||
|
||||
def setSubSessionId(self, subSessionId: int) -> "RangingParameters.Builder":
|
||||
self._sub_session_id = subSessionId
|
||||
return self
|
||||
|
||||
def setRangingUpdateRate(self, rate: int) -> "RangingParameters.Builder":
|
||||
self._ranging_update_rate = rate
|
||||
return self
|
||||
|
||||
def setSlotDuration(self, duration: int) -> "RangingParameters.Builder":
|
||||
self._slot_duration = duration
|
||||
return self
|
||||
|
||||
def setMeasurementReportType(self, reportType: int) -> "RangingParameters.Builder":
|
||||
self._measurement_report_type = reportType
|
||||
return self
|
||||
|
||||
def setNumberOfControlee(self, count: int) -> "RangingParameters.Builder":
|
||||
self._number_of_controlee = count
|
||||
return self
|
||||
|
||||
def setDestinationAddresses(self, addrs: List[UwbAddress]) -> "RangingParameters.Builder":
|
||||
self._destination_addresses = addrs
|
||||
return self
|
||||
|
||||
def setDeviceConfigs(self, configs: List[Any]) -> "RangingParameters.Builder":
|
||||
self._device_configs = configs
|
||||
return self
|
||||
|
||||
def setVendorSpecificData(self, data: List[Any]) -> "RangingParameters.Builder":
|
||||
self._vendor_specific_data = data
|
||||
return self
|
||||
|
||||
def build(self) -> "RangingParameters":
|
||||
rp = RangingParameters()
|
||||
rp._uwb_config_id = self._uwb_config_id
|
||||
rp._session_id = self._session_id
|
||||
rp._sub_session_id = self._sub_session_id
|
||||
rp._ranging_update_rate = self._ranging_update_rate
|
||||
rp._slot_duration = self._slot_duration
|
||||
rp._measurement_report_type = self._measurement_report_type
|
||||
rp._number_of_controlee = self._number_of_controlee
|
||||
rp._destination_addresses = self._destination_addresses
|
||||
rp._device_configs = self._device_configs
|
||||
rp._vendor_specific_data = self._vendor_specific_data
|
||||
return rp
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UwbConfiguration / RangingConfig (stubs)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UwbConfiguration(Stub):
|
||||
"""UwbConfiguration — UWB 配置"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
class RangingConfig(Stub):
|
||||
"""RangingConfig — 测距配置"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RangingSession
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RangingSession(Stub):
|
||||
"""RangingSession — 测距会话"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._open = False
|
||||
|
||||
def open(self):
|
||||
self._open = True
|
||||
|
||||
def close(self):
|
||||
self._open = False
|
||||
|
||||
def reconfigure(self, params: RangingParameters):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# UwbManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class UwbManager(Stub):
|
||||
"""UwbManager — UWB 管理器"""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# UwbCallback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class UwbCallback(Stub):
|
||||
"""UwbManager.UwbCallback — UWB 回调"""
|
||||
|
||||
def onRangingResult(self, sessionId: int, result: RangingResult):
|
||||
pass
|
||||
|
||||
def onRangingFailure(self, sessionId: int, reason: int):
|
||||
pass
|
||||
|
||||
def onControleeResult(self, sessionId: int, status: int):
|
||||
pass
|
||||
|
||||
_instance: Optional["UwbManager"] = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def getInstance(context: Any = None) -> "UwbManager":
|
||||
if UwbManager._instance is None:
|
||||
UwbManager._instance = UwbManager()
|
||||
return UwbManager._instance
|
||||
|
||||
def open(self, sessionId: int) -> RangingSession:
|
||||
session = RangingSession()
|
||||
session.open()
|
||||
return session
|
||||
|
||||
def close(self, sessionId: int):
|
||||
pass
|
||||
|
||||
def range(self, sessionId: int, params: RangingParameters,
|
||||
callback: UwbCallback):
|
||||
if callback:
|
||||
result = RangingResult(
|
||||
status=UwbConstants.STATUS_OK,
|
||||
distanceMeasurement=DistanceMeasurement(
|
||||
meters=1.5, confidence=0.8,
|
||||
standardDeviation=0.1, status=DistanceMeasurement.STATUS_SUCCESS
|
||||
),
|
||||
rssi=-65.0
|
||||
)
|
||||
callback.onRangingResult(sessionId, result)
|
||||
|
||||
def stopRange(self, sessionId: int):
|
||||
pass
|
||||
|
||||
def addControlee(self, sessionId: int, address: UwbAddress):
|
||||
pass
|
||||
|
||||
def removeControlee(self, sessionId: int, address: UwbAddress):
|
||||
pass
|
||||
|
||||
def setMeasurementData(self, sessionId: int, data: bytes):
|
||||
pass
|
||||
|
||||
def sendMeasurementData(self, sessionId: int, data: bytes):
|
||||
pass
|
||||
|
||||
def getUwbDevice(self) -> Any:
|
||||
return None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"UwbManager",
|
||||
"UwbConstants",
|
||||
"UwbAddress",
|
||||
"AngleOfArrival",
|
||||
"DistanceMeasurement",
|
||||
"RangingResult",
|
||||
"RangingParameters",
|
||||
"UwbConfiguration",
|
||||
"RangingConfig",
|
||||
"RangingSession",
|
||||
]
|
||||
@@ -0,0 +1,313 @@
|
||||
"""
|
||||
Android view.autofill 组件 — AutofillManager, AutofillValue, AutofillId 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AutofillManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AutofillManager(Stub):
|
||||
"""AutofillManager — 自动填充管理器"""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# AutofillCallback
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class AutofillCallback(Stub):
|
||||
"""AutofillManager.AutofillCallback — 自动填充回调"""
|
||||
|
||||
def onAutofillEvent(self, view: Any, event: int):
|
||||
pass
|
||||
|
||||
def onAutofillUnavailableEvent(self, event: int):
|
||||
pass
|
||||
|
||||
def onAuthenticationShowing(self):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._enabled = False
|
||||
self._callbacks: List[AutofillManager.AutofillCallback] = []
|
||||
|
||||
def isEnabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
def hasEnabledAutofillServices(self) -> bool:
|
||||
return False
|
||||
|
||||
def isAutofillSupported(self) -> bool:
|
||||
return False
|
||||
|
||||
def disableAutofillServices(self):
|
||||
self._enabled = False
|
||||
|
||||
def notifyValueChanged(self, view: Any):
|
||||
pass
|
||||
|
||||
def commit(self):
|
||||
pass
|
||||
|
||||
def cancel(self):
|
||||
pass
|
||||
|
||||
def requestAutofill(self, view: Any):
|
||||
pass
|
||||
|
||||
def getAutofillServiceComponentName(self) -> Any:
|
||||
return None
|
||||
|
||||
def getAutofillServiceLabel(self) -> str:
|
||||
return ""
|
||||
|
||||
def getAvailableFieldClassificationAlgorithms(self) -> List[str]:
|
||||
return []
|
||||
|
||||
def getDefaultFieldClassificationAlgorithm(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
def getFillEventHistory(self) -> Optional["FillEventHistory"]:
|
||||
return None
|
||||
|
||||
def onAuthenticationResult(self, authenticationResult: Any):
|
||||
pass
|
||||
|
||||
def onAuthenticationFailed(self):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AutofillValue
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AutofillValue(Stub):
|
||||
"""AutofillValue — 自动填充值"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._text_value: Optional[str] = None
|
||||
self._date_value: Optional[int] = None
|
||||
self._toggle_value: Optional[bool] = None
|
||||
self._list_value: Optional[int] = None
|
||||
self._is_text = False
|
||||
self._is_date = False
|
||||
self._is_toggle = False
|
||||
self._is_list = False
|
||||
|
||||
@staticmethod
|
||||
def forText(value: str) -> "AutofillValue":
|
||||
v = AutofillValue()
|
||||
v._text_value = value
|
||||
v._is_text = True
|
||||
return v
|
||||
|
||||
@staticmethod
|
||||
def forDate(value: int) -> "AutofillValue":
|
||||
v = AutofillValue()
|
||||
v._date_value = value
|
||||
v._is_date = True
|
||||
return v
|
||||
|
||||
@staticmethod
|
||||
def forToggle(value: bool) -> "AutofillValue":
|
||||
v = AutofillValue()
|
||||
v._toggle_value = value
|
||||
v._is_toggle = True
|
||||
return v
|
||||
|
||||
@staticmethod
|
||||
def forList(value: int) -> "AutofillValue":
|
||||
v = AutofillValue()
|
||||
v._list_value = value
|
||||
v._is_list = True
|
||||
return v
|
||||
|
||||
def getTextValue(self) -> Optional[str]:
|
||||
return self._text_value
|
||||
|
||||
def getDateValue(self) -> Optional[int]:
|
||||
return self._date_value
|
||||
|
||||
def getToggleValue(self) -> Optional[bool]:
|
||||
return self._toggle_value
|
||||
|
||||
def getListValue(self) -> Optional[int]:
|
||||
return self._list_value
|
||||
|
||||
def isText(self) -> bool:
|
||||
return self._is_text
|
||||
|
||||
def isDate(self) -> bool:
|
||||
return self._is_date
|
||||
|
||||
def isToggle(self) -> bool:
|
||||
return self._is_toggle
|
||||
|
||||
def isList(self) -> bool:
|
||||
return self._is_list
|
||||
|
||||
def equals(self, other: Any) -> bool:
|
||||
if not isinstance(other, AutofillValue):
|
||||
return False
|
||||
return (self._text_value == other._text_value
|
||||
and self._date_value == other._date_value
|
||||
and self._toggle_value == other._toggle_value
|
||||
and self._list_value == other._list_value)
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._text_value, self._date_value,
|
||||
self._toggle_value, self._list_value)) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
if self._is_text:
|
||||
return f"AutofillValue(text={self._text_value})"
|
||||
if self._is_date:
|
||||
return f"AutofillValue(date={self._date_value})"
|
||||
if self._is_toggle:
|
||||
return f"AutofillValue(toggle={self._toggle_value})"
|
||||
if self._is_list:
|
||||
return f"AutofillValue(list={self._list_value})"
|
||||
return "AutofillValue(empty)"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AutofillId
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AutofillId(Stub):
|
||||
"""AutofillId — 自动填充 ID"""
|
||||
|
||||
FLAG_AUTOFILL_SESSION_SUPPORTS_INDEXING = 1
|
||||
FLAG_AUTOFILL_SESSION_SUPPORTS_AUGMENTED_AUTOFILL = 2
|
||||
|
||||
def __init__(self, viewId: int = 0, sessionId: int = 0,
|
||||
virtualChildId: int = -1, virtualIntId: int = -1):
|
||||
super().__init__()
|
||||
self._view_id = viewId
|
||||
self._session_id = sessionId
|
||||
self._virtual_child_id = virtualChildId
|
||||
self._virtual_int_id = virtualIntId
|
||||
|
||||
def getViewId(self) -> int:
|
||||
return self._view_id
|
||||
|
||||
def getSessionId(self) -> int:
|
||||
return self._session_id
|
||||
|
||||
def getVirtualChildId(self) -> int:
|
||||
return self._virtual_child_id
|
||||
|
||||
def getVirtualIntId(self) -> int:
|
||||
return self._virtual_int_id
|
||||
|
||||
def equals(self, other: Any) -> bool:
|
||||
if not isinstance(other, AutofillId):
|
||||
return False
|
||||
return (self._view_id == other._view_id
|
||||
and self._session_id == other._session_id
|
||||
and self._virtual_child_id == other._virtual_child_id)
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash((self._view_id, self._session_id,
|
||||
self._virtual_child_id)) & 0x7FFFFFFF
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"AutofillId(viewId={self._view_id}, sessionId={self._session_id})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FillEventHistory
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FillEventHistory(Stub):
|
||||
"""FillEventHistory — 填充事件历史"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._events: List[Any] = []
|
||||
self._client_state: Any = None
|
||||
|
||||
def getEvents(self) -> List[Any]:
|
||||
return self._events
|
||||
|
||||
def getClientState(self) -> Any:
|
||||
return self._client_state
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FillContext
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FillContext(Stub):
|
||||
"""FillContext — 填充上下文"""
|
||||
|
||||
def __init__(self, requestId: int = 0, autofillId: Optional[AutofillId] = None,
|
||||
focusedId: Optional[AutofillId] = None,
|
||||
focusedVirtualId: int = -1, extras: Any = None,
|
||||
autofillValue: Optional[AutofillValue] = None,
|
||||
visibility: Optional[int] = None,
|
||||
options: Optional[List[Any]] = None):
|
||||
super().__init__()
|
||||
self._request_id = requestId
|
||||
self._autofill_id = autofillId
|
||||
self._focused_id = focusedId
|
||||
self._focused_virtual_id = focusedVirtualId
|
||||
self._extras = extras
|
||||
self._autofill_value = autofillValue
|
||||
self._visibility = visibility
|
||||
self._options = options
|
||||
|
||||
def getRequestId(self) -> int:
|
||||
return self._request_id
|
||||
|
||||
def getAutofillId(self) -> Optional[AutofillId]:
|
||||
return self._autofill_id
|
||||
|
||||
def getFocusedId(self) -> Optional[AutofillId]:
|
||||
return self._focused_id
|
||||
|
||||
def getFocusedVirtualId(self) -> int:
|
||||
return self._focused_virtual_id
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
def getAutofillValue(self) -> Optional[AutofillValue]:
|
||||
return self._autofill_value
|
||||
|
||||
def getVisibility(self) -> Optional[int]:
|
||||
return self._visibility
|
||||
|
||||
def getOptions(self) -> Optional[List[Any]]:
|
||||
return self._options
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# AugmentedAutofillManager (stub)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class AugmentedAutofillManager(Stub):
|
||||
"""AugmentedAutofillManager — 增强自动填充管理器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"AutofillManager",
|
||||
"AutofillValue",
|
||||
"AutofillId",
|
||||
"FillEventHistory",
|
||||
"FillContext",
|
||||
"AugmentedAutofillManager",
|
||||
]
|
||||
@@ -0,0 +1,426 @@
|
||||
"""
|
||||
Android view.textclassifier 组件 — TextClassifier, TextClassification, TextLinks, TextSelection 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# EntityConfig
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class EntityConfig(Stub):
|
||||
"""EntityConfig — 实体配置"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""EntityConfig.Builder — 实体配置构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._include_types: Optional[Set[str]] = None
|
||||
self._exclude_types: Optional[Set[str]] = None
|
||||
self._include_from_textclassifier = True
|
||||
|
||||
def includeTypesFromTextClassifier(self) -> "EntityConfig.Builder":
|
||||
self._include_from_textclassifier = True
|
||||
return self
|
||||
|
||||
def excludeTypes(self, varargs: List[str]) -> "EntityConfig.Builder":
|
||||
self._exclude_types = set(varargs)
|
||||
return self
|
||||
|
||||
def includeTypes(self, varargs: List[str]) -> "EntityConfig.Builder":
|
||||
self._include_types = set(varargs)
|
||||
return self
|
||||
|
||||
def build(self) -> "EntityConfig":
|
||||
config = EntityConfig()
|
||||
config._include_types = self._include_types
|
||||
config._exclude_types = self._exclude_types
|
||||
config._include_from_textclassifier = self._include_from_textclassifier
|
||||
return config
|
||||
|
||||
def getEntityTypes(self) -> Optional[List[str]]:
|
||||
if self._include_types:
|
||||
return list(self._include_types)
|
||||
return None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._include_types: Optional[Set[str]] = None
|
||||
self._exclude_types: Optional[Set[str]] = None
|
||||
self._include_from_textclassifier = True
|
||||
|
||||
def getEntityTypes(self) -> Optional[List[str]]:
|
||||
if self._include_types:
|
||||
return list(self._include_types)
|
||||
return None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TextClassifier
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TextClassifier(Stub):
|
||||
"""TextClassifier — 文本分类器"""
|
||||
|
||||
TYPE_UNKNOWN = "unknown"
|
||||
TYPE_ADDRESS = "address"
|
||||
TYPE_DATE = "date"
|
||||
TYPE_DATE_TIME = "datetime"
|
||||
TYPE_DURATION = "duration"
|
||||
TYPE_EMAIL = "email"
|
||||
TYPE_FLIGHT_NUMBER = "flight"
|
||||
TYPE_IDENTIFIER = "identifier"
|
||||
TYPE_LINK = "link"
|
||||
TYPE_LIST = "list"
|
||||
TYPE_LOCATION = "location"
|
||||
TYPE_MEASUREMENT = "measurement"
|
||||
TYPE_MONEY = "money"
|
||||
TYPE_OTHER = "other"
|
||||
TYPE_PERIODIC = "periodic"
|
||||
TYPE_PHONE = "phone"
|
||||
TYPE_PRODUCT = "product"
|
||||
TYPE_RECIPE = "recipe"
|
||||
TYPE_TRACK_AIRLINE = "track_airline"
|
||||
TYPE_TRACK_FLIGHT = "track_flight"
|
||||
TYPE_TRACK_FOOD = "track_food"
|
||||
TYPE_TRACK_SPORT = "track_sport"
|
||||
TYPE_TRACK_WEATHER = "track_weather"
|
||||
TYPE_URL = "url"
|
||||
TYPE_VEHICLE = "vehicle"
|
||||
|
||||
WIDGET_TYPE_CLIPBOARD = "clipboard"
|
||||
WIDGET_TYPE_CUT_COPY = "cutcopy"
|
||||
WIDGET_TYPE_EDIT_TEXT = "edittext"
|
||||
WIDGET_TYPE_LINKIFY = "linkify"
|
||||
WIDGET_TYPE_NOTIFICATION = "notification"
|
||||
WIDGET_TYPE_OTHER = "other"
|
||||
WIDGET_TYPE_SELECTION = "selection"
|
||||
WIDGET_TYPE_TEXT = "text"
|
||||
WIDGET_TYPE_UNSELECTED_TEXT = "unselected_text"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Request
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
class Request(Stub):
|
||||
"""TextClassifier.Request — 分类请求"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._calling_package_name: Optional[str] = None
|
||||
self._text: str = ""
|
||||
self._start_index: int = 0
|
||||
self._end_index: int = 0
|
||||
self._extras: Any = None
|
||||
self._default_locales: Any = None
|
||||
self._dark_launch_allowed = False
|
||||
|
||||
def getCallingPackageName(self) -> Optional[str]:
|
||||
return self._calling_package_name
|
||||
|
||||
def getText(self) -> str:
|
||||
return self._text
|
||||
|
||||
def getStartIndex(self) -> int:
|
||||
return self._start_index
|
||||
|
||||
def getEndIndex(self) -> int:
|
||||
return self._end_index
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
def getDefaultLocales(self) -> Any:
|
||||
return self._default_locales
|
||||
|
||||
def isDarkLaunchAllowed(self) -> bool:
|
||||
return self._dark_launch_allowed
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def classifyText(self, request: Request) -> "TextClassification":
|
||||
text = request.getText()
|
||||
start = request.getStartIndex()
|
||||
end = request.getEndIndex()
|
||||
if end > len(text):
|
||||
end = len(text)
|
||||
selected = text[start:end] if start < end else text
|
||||
|
||||
entity_type = TextClassifier.TYPE_UNKNOWN
|
||||
confidence = 0.0
|
||||
|
||||
# Basic regex-based classification
|
||||
if re.match(r'^https?://', selected):
|
||||
entity_type = TextClassifier.TYPE_URL
|
||||
confidence = 0.95
|
||||
elif re.match(r'^[\w.+-]+@[\w-]+\.[\w.-]+$', selected):
|
||||
entity_type = TextClassifier.TYPE_EMAIL
|
||||
confidence = 0.95
|
||||
elif re.match(r'^\+?[\d\s\-\(\)]{7,15}$', selected):
|
||||
entity_type = TextClassifier.TYPE_PHONE
|
||||
confidence = 0.85
|
||||
elif re.match(r'^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}$', selected):
|
||||
entity_type = TextClassifier.TYPE_DATE
|
||||
confidence = 0.80
|
||||
elif re.match(r'^\d{1,2}:\d{2}(\s?[AP]M)?$', selected, re.IGNORECASE):
|
||||
entity_type = TextClassifier.TYPE_DATE_TIME
|
||||
confidence = 0.80
|
||||
|
||||
tc = TextClassification()
|
||||
tc._text = selected
|
||||
tc._entity_type = entity_type
|
||||
tc._confidence_score = confidence
|
||||
tc._entity_count = 1
|
||||
return tc
|
||||
|
||||
def suggestConversationActions(self, request: Request) -> List["ConversationAction"]:
|
||||
return []
|
||||
|
||||
def detectLanguage(self, text: str) -> "TextClassification":
|
||||
tc = TextClassification()
|
||||
tc._text = text
|
||||
tc._entity_type = TextClassifier.TYPE_UNKNOWN
|
||||
return tc
|
||||
|
||||
def onSelectionEvent(self, event: Any):
|
||||
pass
|
||||
|
||||
def onTextChanged(self, text: Any):
|
||||
pass
|
||||
|
||||
def onTextSelectionAction(self, action: Any):
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TextClassification
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TextClassification(Stub):
|
||||
"""TextClassification — 文本分类结果"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._text: str = ""
|
||||
self._entity_type: str = TextClassifier.TYPE_UNKNOWN
|
||||
self._confidence_score: float = 0.0
|
||||
self._entity_count: int = 0
|
||||
self._icon: Any = None
|
||||
self._label: str = ""
|
||||
self._actions: List[Any] = []
|
||||
self._extras: Any = None
|
||||
self._id: Optional[str] = None
|
||||
|
||||
def getText(self) -> str:
|
||||
return self._text
|
||||
|
||||
def getEntityCount(self) -> int:
|
||||
return self._entity_count
|
||||
|
||||
def getEntity(self, index: int) -> Optional[str]:
|
||||
if index == 0:
|
||||
return self._entity_type
|
||||
return None
|
||||
|
||||
def getEntityType(self) -> str:
|
||||
return self._entity_type
|
||||
|
||||
def getConfidenceScore(self, entityType: str) -> float:
|
||||
if entityType == self._entity_type:
|
||||
return self._confidence_score
|
||||
return 0.0
|
||||
|
||||
def getIcon(self) -> Any:
|
||||
return self._icon
|
||||
|
||||
def getLabel(self) -> str:
|
||||
return self._label
|
||||
|
||||
def getActions(self) -> List[Any]:
|
||||
return self._actions
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
def getId(self) -> Optional[str]:
|
||||
return self._id
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"TextClassification(type={self._entity_type}," \
|
||||
f" confidence={self._confidence_score:.2f})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TextLinks
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TextLinks(Stub):
|
||||
"""TextLinks — 文本链接"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._links: List[Any] = []
|
||||
self._extras: Any = None
|
||||
|
||||
def getLinks(self) -> List[Any]:
|
||||
return self._links
|
||||
|
||||
def getEntityCount(self) -> int:
|
||||
return len(self._links)
|
||||
|
||||
def getEntity(self, index: int) -> Optional[str]:
|
||||
if 0 <= index < len(self._links):
|
||||
return self._links[index]
|
||||
return None
|
||||
|
||||
def getEntityType(self) -> str:
|
||||
return TextClassifier.TYPE_LINK
|
||||
|
||||
def getConfidenceScore(self, entityType: str) -> float:
|
||||
return 0.0
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TextSelection
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TextSelection(Stub):
|
||||
"""TextSelection — 文本选择结果"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._start_index: int = 0
|
||||
self._end_index: int = 0
|
||||
self._entity_type: str = TextClassifier.TYPE_UNKNOWN
|
||||
self._text: str = ""
|
||||
self._entity_count: int = 0
|
||||
self._confidence_score: float = 0.0
|
||||
self._extras: Any = None
|
||||
self._id: Optional[str] = None
|
||||
|
||||
def getSelectionStartIndex(self) -> int:
|
||||
return self._start_index
|
||||
|
||||
def getSelectionEndIndex(self) -> int:
|
||||
return self._end_index
|
||||
|
||||
def getEntityType(self) -> str:
|
||||
return self._entity_type
|
||||
|
||||
def getText(self) -> str:
|
||||
return self._text
|
||||
|
||||
def getEntityCount(self) -> int:
|
||||
return self._entity_count
|
||||
|
||||
def getEntity(self, index: int) -> Optional[str]:
|
||||
if index == 0:
|
||||
return self._entity_type
|
||||
return None
|
||||
|
||||
def getConfidenceScore(self, entityType: str) -> float:
|
||||
if entityType == self._entity_type:
|
||||
return self._confidence_score
|
||||
return 0.0
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
def getId(self) -> Optional[str]:
|
||||
return self._id
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ConversationAction
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ConversationAction(Stub):
|
||||
"""ConversationAction — 对话操作"""
|
||||
|
||||
TYPE_CALL = "call"
|
||||
TYPE_CREATE_REMINDER = "create_reminder"
|
||||
TYPE_OPEN_URL = "open_url"
|
||||
TYPE_SEND_EMAIL = "send_email"
|
||||
TYPE_SEND_SMS = "send_sms"
|
||||
TYPE_SHARE_LOCATION = "share_location"
|
||||
TYPE_TEXT_REPLY = "text_reply"
|
||||
TYPE_VIEW_CALENDAR = "view_calendar"
|
||||
TYPE_VIEW_MAP = "view_map"
|
||||
|
||||
def __init__(self, text: str = "", confidenceScore: float = 0.0,
|
||||
action: str = "", extras: Any = None,
|
||||
icon: Any = None, label: str = ""):
|
||||
super().__init__()
|
||||
self._text = text
|
||||
self._confidence_score = confidenceScore
|
||||
self._action = action
|
||||
self._extras = extras
|
||||
self._icon = icon
|
||||
self._label = label
|
||||
|
||||
def getText(self) -> str:
|
||||
return self._text
|
||||
|
||||
def getConfidenceScore(self) -> float:
|
||||
return self._confidence_score
|
||||
|
||||
def getAction(self) -> str:
|
||||
return self._action
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
def getIcon(self) -> Any:
|
||||
return self._icon
|
||||
|
||||
def getLabel(self) -> str:
|
||||
return self._label
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TextClassificationContext
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TextClassificationContext(Stub):
|
||||
"""TextClassificationContext — 文本分类上下文"""
|
||||
|
||||
def __init__(self, packageName: str = "", widgetType: str = "",
|
||||
widgetVersion: str = ""):
|
||||
super().__init__()
|
||||
self._package_name = packageName
|
||||
self._widget_type = widgetType
|
||||
self._widget_version = widgetVersion
|
||||
|
||||
def getPackageName(self) -> str:
|
||||
return self._package_name
|
||||
|
||||
def getWidgetType(self) -> str:
|
||||
return self._widget_type
|
||||
|
||||
def getWidgetVersion(self) -> str:
|
||||
return self._widget_version
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"TextClassifier",
|
||||
"TextClassification",
|
||||
"TextLinks",
|
||||
"TextSelection",
|
||||
"ConversationAction",
|
||||
"TextClassificationContext",
|
||||
"EntityConfig",
|
||||
]
|
||||
@@ -0,0 +1,435 @@
|
||||
"""
|
||||
Android view.translation 组件 — TranslationManager, TranslationRequest, TranslationResponse 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .._core import Stub, AndroidOnlyError
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TranslationSpec
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TranslationSpec(Stub):
|
||||
"""TranslationSpec — 翻译规格"""
|
||||
|
||||
DATA_TYPE_TEXT = 0
|
||||
DATA_TYPE_UI = 1
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._locale: Optional[str] = None
|
||||
self._data_type = TranslationSpec.DATA_TYPE_TEXT
|
||||
|
||||
def setLocale(self, locale: str) -> "TranslationSpec":
|
||||
self._locale = locale
|
||||
return self
|
||||
|
||||
def setDataType(self, dataType: int) -> "TranslationSpec":
|
||||
self._data_type = dataType
|
||||
return self
|
||||
|
||||
def getLocale(self) -> Optional[str]:
|
||||
return self._locale
|
||||
|
||||
def getDataType(self) -> int:
|
||||
return self._data_type
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TranslationContext
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TranslationContext(Stub):
|
||||
"""TranslationContext — 翻译上下文"""
|
||||
|
||||
def __init__(self, sourceLocale: str = "", targetLocale: str = "",
|
||||
translationFlags: int = 0):
|
||||
super().__init__()
|
||||
self._source_locale = sourceLocale
|
||||
self._target_locale = targetLocale
|
||||
self._translation_flags = translationFlags
|
||||
|
||||
def getSourceLocale(self) -> str:
|
||||
return self._source_locale
|
||||
|
||||
def getTargetLocale(self) -> str:
|
||||
return self._target_locale
|
||||
|
||||
def getTranslationFlags(self) -> int:
|
||||
return self._translation_flags
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TranslationCapability
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TranslationCapability(Stub):
|
||||
"""TranslationCapability — 翻译能力"""
|
||||
|
||||
STATE_AVAILABLE = 0
|
||||
STATE_UNAVAILABLE = 1
|
||||
STATE_CAPABLE = 2
|
||||
STATE_NOT_CAPABLE = 3
|
||||
STATE_DOWNLOADING = 4
|
||||
STATE_NOT_ALLOWED = 5
|
||||
|
||||
def __init__(self, state: int = STATE_UNAVAILABLE,
|
||||
sourceLocale: str = "", targetLocale: str = "",
|
||||
uiTranslationEnabled: bool = False,
|
||||
textTranslationEnabled: bool = False,
|
||||
translationService: str = "desktop"):
|
||||
super().__init__()
|
||||
self._state = state
|
||||
self._source_locale = sourceLocale
|
||||
self._target_locale = targetLocale
|
||||
self._ui_translation_enabled = uiTranslationEnabled
|
||||
self._text_translation_enabled = textTranslationEnabled
|
||||
self._translation_service = translationService
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def getSourceLocale(self) -> str:
|
||||
return self._source_locale
|
||||
|
||||
def getTargetLocale(self) -> str:
|
||||
return self._target_locale
|
||||
|
||||
def isUiTranslationEnabled(self) -> bool:
|
||||
return self._ui_translation_enabled
|
||||
|
||||
def isTextTranslationEnabled(self) -> bool:
|
||||
return self._text_translation_enabled
|
||||
|
||||
def getTranslationService(self) -> str:
|
||||
return self._translation_service
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TranslationResult
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TranslationResult(Stub):
|
||||
"""TranslationResult — 翻译结果"""
|
||||
|
||||
def __init__(self, translatedText: str = "", sourceText: str = "",
|
||||
translationStatus: int = 0):
|
||||
super().__init__()
|
||||
self._translated_text = translatedText
|
||||
self._source_text = sourceText
|
||||
self._translation_status = translationStatus
|
||||
|
||||
def getTranslatedText(self) -> str:
|
||||
return self._translated_text
|
||||
|
||||
def getSourceText(self) -> str:
|
||||
return self._source_text
|
||||
|
||||
def getTranslationStatus(self) -> int:
|
||||
return self._translation_status
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TranslationRequest
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TranslationRequest(Stub):
|
||||
"""TranslationRequest — 翻译请求"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""TranslationRequest.Builder — 翻译请求构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._source_text: str = ""
|
||||
self._source_locale: str = ""
|
||||
self._target_locale: str = ""
|
||||
self._translation_flags: int = 0
|
||||
self._ui_translation_result: Any = None
|
||||
self._request_id: int = 0
|
||||
self._translation_config: Any = None
|
||||
|
||||
def setSourceText(self, text: str) -> "TranslationRequest.Builder":
|
||||
self._source_text = text
|
||||
return self
|
||||
|
||||
def setSourceLocale(self, locale: str) -> "TranslationRequest.Builder":
|
||||
self._source_locale = locale
|
||||
return self
|
||||
|
||||
def setTargetLocale(self, locale: str) -> "TranslationRequest.Builder":
|
||||
self._target_locale = locale
|
||||
return self
|
||||
|
||||
def setTranslationFlags(self, flags: int) -> "TranslationRequest.Builder":
|
||||
self._translation_flags = flags
|
||||
return self
|
||||
|
||||
def setUiTranslationResult(self, result: Any) -> "TranslationRequest.Builder":
|
||||
self._ui_translation_result = result
|
||||
return self
|
||||
|
||||
def setRequestId(self, id: int) -> "TranslationRequest.Builder":
|
||||
self._request_id = id
|
||||
return self
|
||||
|
||||
def setTranslationConfig(self, config: Any) -> "TranslationRequest.Builder":
|
||||
self._translation_config = config
|
||||
return self
|
||||
|
||||
def build(self) -> "TranslationRequest":
|
||||
req = TranslationRequest()
|
||||
req._source_text = self._source_text
|
||||
req._source_locale = self._source_locale
|
||||
req._target_locale = self._target_locale
|
||||
req._translation_flags = self._translation_flags
|
||||
req._ui_translation_result = self._ui_translation_result
|
||||
req._request_id = self._request_id
|
||||
req._translation_config = self._translation_config
|
||||
return req
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._source_text: str = ""
|
||||
self._source_locale: str = ""
|
||||
self._target_locale: str = ""
|
||||
self._request_id: int = 0
|
||||
self._translation_flags: int = 0
|
||||
self._ui_translation_result: Any = None
|
||||
self._translation_config: Any = None
|
||||
|
||||
def getSourceText(self) -> str:
|
||||
return self._source_text
|
||||
|
||||
def getSourceLocale(self) -> str:
|
||||
return self._source_locale
|
||||
|
||||
def getTargetLocale(self) -> str:
|
||||
return self._target_locale
|
||||
|
||||
def getRequestId(self) -> int:
|
||||
return self._request_id
|
||||
|
||||
def getTranslationFlags(self) -> int:
|
||||
return self._translation_flags
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TranslationResponse
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TranslationResponse(Stub):
|
||||
"""TranslationResponse — 翻译响应"""
|
||||
|
||||
class Builder(Stub):
|
||||
"""TranslationResponse.Builder — 翻译响应构建器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._translation_result: Optional[TranslationResult] = None
|
||||
self._status: int = 0
|
||||
|
||||
def setTranslationResult(self, result: TranslationResult) -> "TranslationResponse.Builder":
|
||||
self._translation_result = result
|
||||
return self
|
||||
|
||||
def setStatus(self, status: int) -> "TranslationResponse.Builder":
|
||||
self._status = status
|
||||
return self
|
||||
|
||||
def build(self) -> "TranslationResponse":
|
||||
resp = TranslationResponse()
|
||||
resp._translation_result = self._translation_result
|
||||
resp._status = self._status
|
||||
return resp
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._translation_result: Optional[TranslationResult] = None
|
||||
self._status: int = 0
|
||||
|
||||
def getTranslationResult(self) -> Optional[TranslationResult]:
|
||||
return self._translation_result
|
||||
|
||||
def getStatus(self) -> int:
|
||||
return self._status
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Translator
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Translator(Stub):
|
||||
"""Translator — 翻译器"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._supported_locales: List[str] = ["en", "zh", "ja", "ko", "fr", "de", "es"]
|
||||
|
||||
def translate(self, text: str, sourceLocale: str,
|
||||
targetLocale: str) -> str:
|
||||
# Basic stub: return original text with a prefix
|
||||
if sourceLocale == targetLocale:
|
||||
return text
|
||||
return f"[{sourceLocale}->{targetLocale}] {text}"
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def getSupportedLocales(self) -> List[str]:
|
||||
return list(self._supported_locales)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# TranslationManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TranslationManager(Stub):
|
||||
"""TranslationManager — 翻译管理器"""
|
||||
|
||||
EXTRA_CAPABILITIES_FROM_FRAMEWORK = "android.view.translation.extra.CAPABILITIES_FROM_FRAMEWORK"
|
||||
EXTRA_TRANSLATION_REQUEST_RESULT = "android.view.translation.extra.TRANSLATION_REQUEST_RESULT"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._translators: Dict[str, Translator] = {}
|
||||
|
||||
def startTranslation(self, request: TranslationRequest) -> TranslationResponse:
|
||||
builder = TranslationResponse.Builder()
|
||||
try:
|
||||
source = request.getSourceLocale()
|
||||
target = request.getTargetLocale()
|
||||
text = request.getSourceText()
|
||||
|
||||
translator = self._get_or_create_translator()
|
||||
translated = translator.translate(text, source, target)
|
||||
|
||||
result = TranslationResult(
|
||||
translatedText=translated,
|
||||
sourceText=text,
|
||||
translationStatus=0
|
||||
)
|
||||
builder.setTranslationResult(result)
|
||||
builder.setStatus(0)
|
||||
except Exception as e:
|
||||
builder.setStatus(1)
|
||||
return builder.build()
|
||||
|
||||
def createOnDeviceTranslation(self) -> Translator:
|
||||
translator = Translator()
|
||||
return translator
|
||||
|
||||
def getTranslationCapabilities(self, sourceLocale: str,
|
||||
targetLocale: str) -> List[TranslationCapability]:
|
||||
return [
|
||||
TranslationCapability(
|
||||
state=TranslationCapability.STATE_AVAILABLE,
|
||||
sourceLocale=sourceLocale,
|
||||
targetLocale=targetLocale,
|
||||
textTranslationEnabled=True
|
||||
)
|
||||
]
|
||||
|
||||
def onTranslationRequest(self, request: TranslationRequest):
|
||||
pass
|
||||
|
||||
def isTranslationSupported(self) -> bool:
|
||||
return True
|
||||
|
||||
def isOnDeviceTranslationSupported(self) -> bool:
|
||||
return True
|
||||
|
||||
def isULTRADeviceSupported(self) -> bool:
|
||||
return False
|
||||
|
||||
def _get_or_create_translator(self) -> Translator:
|
||||
return Translator()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ViewTranslation / ViewTranslationRequest / ViewTranslationResponse (stubs)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ViewTranslation(Stub):
|
||||
"""ViewTranslation — 视图翻译"""
|
||||
|
||||
def __init__(self, autofillId: Any = None, value: str = "",
|
||||
translationResult: Optional[TranslationResult] = None):
|
||||
super().__init__()
|
||||
self._autofill_id = autofillId
|
||||
self._value = value
|
||||
self._translation_result = translationResult
|
||||
|
||||
def getAutofillId(self) -> Any:
|
||||
return self._autofill_id
|
||||
|
||||
def getValue(self) -> str:
|
||||
return self._value
|
||||
|
||||
def getTranslationResult(self) -> Optional[TranslationResult]:
|
||||
return self._translation_result
|
||||
|
||||
def setTranslationResult(self, result: TranslationResult):
|
||||
self._translation_result = result
|
||||
|
||||
|
||||
class ViewTranslationRequest(Stub):
|
||||
"""ViewTranslationRequest — 视图翻译请求"""
|
||||
|
||||
def __init__(self, autofillId: Any = None, value: str = "",
|
||||
requestId: int = 0):
|
||||
super().__init__()
|
||||
self._autofill_id = autofillId
|
||||
self._value = value
|
||||
self._request_id = requestId
|
||||
|
||||
def getAutofillId(self) -> Any:
|
||||
return self._autofill_id
|
||||
|
||||
def getValue(self) -> str:
|
||||
return self._value
|
||||
|
||||
def getRequestId(self) -> int:
|
||||
return self._request_id
|
||||
|
||||
|
||||
class ViewTranslationResponse(Stub):
|
||||
"""ViewTranslationResponse — 视图翻译响应"""
|
||||
|
||||
def __init__(self, autofillId: Any = None, value: str = "",
|
||||
extras: Any = None):
|
||||
super().__init__()
|
||||
self._autofill_id = autofillId
|
||||
self._value = value
|
||||
self._extras = extras
|
||||
|
||||
def getAutofillId(self) -> Any:
|
||||
return self._autofill_id
|
||||
|
||||
def getValue(self) -> str:
|
||||
return self._value
|
||||
|
||||
def getExtras(self) -> Any:
|
||||
return self._extras
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"TranslationManager",
|
||||
"TranslationRequest",
|
||||
"TranslationResponse",
|
||||
"TranslationResult",
|
||||
"TranslationCapability",
|
||||
"Translator",
|
||||
"TranslationContext",
|
||||
"TranslationSpec",
|
||||
"ViewTranslation",
|
||||
"ViewTranslationRequest",
|
||||
"ViewTranslationResponse",
|
||||
]
|
||||
Reference in new issue
Block a user