""" Android HDMI API — HdmiControlManager, HdmiDeviceInfo, HdmiPortInfo, etc. 方法签名完全对齐 Android SDK """ from __future__ import annotations import os import sys import time import uuid import threading from typing import Any, Dict, List, Optional, Set, Tuple, Callable from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) # ═══════════════════════════════════════════════════════════════ # 常量 # ═══════════════════════════════════════════════════════════════ # DEVICE_* 常量 DEVICE_TYPE_TV = 0 DEVICE_TYPE_RECORDER = 1 DEVICE_TYPE_TUNER = 2 DEVICE_TYPE_PLAYBACK = 3 DEVICE_TYPE_AUDIO_SYSTEM = 4 DEVICE_TYPE_PURE_CEC_SWITCH = 5 DEVICE_TYPE_VIDEO_PROCESSOR = 6 DEVICE_TYPE_RESERVED = 7 DEVICE_TYPE_INACTIVE = 15 DEVICE_TYPE_CEC_AUTO = 16 # CONTROL_* 常量 CONTROL_STATE_CHANGED_REASON_UNKNOWN = 0 CONTROL_STATE_CHANGED_REASON_ENABLED = 1 CONTROL_STATE_CHANGED_REASON_DISABLED = 2 CONTROL_STATE_CHANGED_REASON_WAKE_UP = 3 CONTROL_STATE_CHANGED_REASON_SLEEP = 4 CONTROL_STATE_CHANGED_REASON_STANDBY = 5 CONTROL_STATE_CHANGED_REASON_HDMI_CEC_ENABLED = 6 CONTROL_STATE_CHANGED_REASON_HDMI_CEC_DISABLED = 7 CONTROL_STATE_CHANGED_REASON_HDMI_MHL_ENABLED = 8 CONTROL_STATE_CHANGED_REASON_HDMI_MHL_DISABLED = 9 CONTROL_STATE_CHANGED_REASON_INVALID = -1 # CEC_* 常量 CEC_VERSION_1_1 = 0x11 CEC_VERSION_1_2 = 0x12 CEC_VERSION_1_3 = 0x13 CEC_VERSION_1_4 = 0x14 CEC_VERSION_2_0 = 0x20 CEC_VERSION_UNKNOWN = 0x00 # POWER_STATUS 常量 POWER_STATUS_ON = 0 POWER_STATUS_STANDBY = 1 POWER_STATUS_TO_ON = 2 POWER_STATUS_TO_STANDBY = 3 POWER_STATUS_UNKNOWN = -1 # PORT_TYPE 常量 PORT_TYPE_INPUT = 0 PORT_TYPE_OUTPUT = 1 PORT_TYPE_BIDIRECTIONAL = 2 PORT_TYPE_ARC = 3 PORT_TYPE_EARC = 4 # RESULT 常量 RESULT_SUCCESS = 0 RESULT_FAILURE = -1 RESULT_TIMEOUT = -2 RESULT_NOT_SUPPORTED = -3 RESULT_BUSY = -4 RESULT_ALREADY_IN_PROGRESS = -5 # CEC_MESSAGE_TYPE 常量 CEC_MESSAGE_TYPE_FEATURE_ABORT = 0x00 CEC_MESSAGE_TYPE_ACTIVE_SOURCE = 0x82 CEC_MESSAGE_TYPE_IMAGE_VIEW_ON = 0x04 CEC_MESSAGE_TYPE_TEXT_VIEW_ON = 0x0D CEC_MESSAGE_TYPE_INACTIVE_SOURCE = 0x9D CEC_MESSAGE_TYPE_REQUEST_ACTIVE_SOURCE = 0x85 CEC_MESSAGE_TYPE_STANDBY = 0x36 CEC_MESSAGE_TYPE_SET_MENU_LANGUAGE = 0x32 CEC_MESSAGE_TYPE_USER_CONTROL_PRESSED = 0x44 CEC_MESSAGE_TYPE_USER_CONTROL_RELEASED = 0x45 CEC_MESSAGE_TYPE_GIVE_PHYSICAL_ADDRESS = 0x83 CEC_MESSAGE_TYPE_REPORT_PHYSICAL_ADDRESS = 0x84 CEC_MESSAGE_TYPE_GIVE_DEVICE_VENDOR_ID = 0x8C CEC_MESSAGE_TYPE_DEVICE_VENDOR_ID = 0x87 CEC_MESSAGE_TYPE_GIVE_OSD_NAME = 0x46 CEC_MESSAGE_TYPE_SET_OSD_NAME = 0x47 CEC_MESSAGE_TYPE_GIVE_FEATURES = 0x88 CEC_MESSAGE_TYPE_REPORT_FEATURES = 0x89 CEC_MESSAGE_TYPE_VENDOR_COMMAND = 0x89 CEC_MESSAGE_TYPE_VENDOR_REMOTE_BUTTON_DOWN = 0x8A CEC_MESSAGE_TYPE_VENDOR_REMOTE_BUTTON_UP = 0x8B # HDMI_RECORD_SOURCE 常量 RECORD_SOURCE_OWN_SOURCE = 1 RECORD_SOURCE_DIGITAL_SERVICE = 2 RECORD_SOURCE_ANALOGUE_SERVICE = 3 RECORD_SOURCE_EXTERNAL_PHYSICAL_ADDRESS = 4 RECORD_SOURCE_EXTERNAL_PLUG = 5 RECORD_SOURCE_PROGRAM_NUMBER = 6 # ═══════════════════════════════════════════════════════════════ # HdmiDeviceInfo # ═══════════════════════════════════════════════════════════════ class HdmiDeviceInfo(Stub): """HdmiDeviceInfo — HDMI 设备信息""" def __init__( self, logicalAddress: int = -1, physicalAddress: int = 0xFFFF, portId: int = -1, deviceType: int = DEVICE_TYPE_INACTIVE, vendorId: int = 0, displayName: str = "", ): super().__init__() self._logical_address = logicalAddress self._physical_address = physicalAddress self._port_id = portId self._device_type = deviceType self._vendor_id = vendorId self._display_name = displayName self._device_id = str(uuid.uuid4()) self._osd_name: Optional[str] = None self._cec_version = CEC_VERSION_2_0 self._power_status = POWER_STATUS_ON self._is_cec_device = True self._is_mhl_device = False self._is_arc_supported = False self._is_earc_supported = False self._is_audio_return_channel = False self._physical_address_str: Optional[str] = None self._adopter_id = 0 self._logical_addresses: List[int] = [] self._supports_cec = True self._vendor_name: Optional[str] = None self._menu_language: Optional[str] = None self._features: List[int] = [] self._is_active_source = False self._discovery_time = time.time() def getLogicalAddress(self) -> int: return self._logical_address def getPhysicalAddress(self) -> int: return self._physical_address def getPhysicalAddressString(self) -> str: if self._physical_address_str: return self._physical_address_str pa = self._physical_address return f"{(pa >> 12) & 0xF}.{(pa >> 8) & 0xF}.{(pa >> 4) & 0xF}.{pa & 0xF}" def getPortId(self) -> int: return self._port_id def getDeviceType(self) -> int: return self._device_type def getDeviceTypeString(self) -> str: type_names = { DEVICE_TYPE_TV: "TV", DEVICE_TYPE_RECORDER: "Recorder", DEVICE_TYPE_TUNER: "Tuner", DEVICE_TYPE_PLAYBACK: "Playback", DEVICE_TYPE_AUDIO_SYSTEM: "AudioSystem", DEVICE_TYPE_PURE_CEC_SWITCH: "Switch", DEVICE_TYPE_VIDEO_PROCESSOR: "VideoProcessor", DEVICE_TYPE_INACTIVE: "Inactive", } return type_names.get(self._device_type, "Unknown") def getVendorId(self) -> int: return self._vendor_id def getDisplayName(self) -> str: return self._display_name def getOsdName(self) -> Optional[str]: return self._osd_name def setOsdName(self, name: str) -> None: self._osd_name = name def getCecVersion(self) -> int: return self._cec_version def getPowerStatus(self) -> int: return self._power_status def setPowerStatus(self, status: int) -> None: self._power_status = status def isCecDevice(self) -> bool: return self._is_cec_device def isMhlDevice(self) -> bool: return self._is_mhl_device def isArcSupported(self) -> bool: return self._is_arc_supported def isEarcSupported(self) -> bool: return self._is_earc_supported def isActiveSource(self) -> bool: return self._is_active_source def setActiveSource(self, active: bool) -> None: self._is_active_source = active def toString(self) -> str: return ( f"HdmiDeviceInfo(logical={self._logical_address}, " f"physical={self.getPhysicalAddressString()}, " f"type={self.getDeviceTypeString()}, " f"name={self._display_name})" ) # ═══════════════════════════════════════════════════════════════ # HdmiPortInfo # ═══════════════════════════════════════════════════════════════ class HdmiPortInfo(Stub): """HdmiPortInfo — HDMI 端口信息""" def __init__(self, id: int = 0, type: int = PORT_TYPE_INPUT, address: int = 0x0000): super().__init__() self._id = id self._type = type self._physical_address = address self._is_cec_supported = True self._is_arc_supported = False self._is_earc_supported = False self._is_mhl_supported = False self._supports_4k = False self._supports_hdr = False self._supports_dolby_vision = False self._max_tmds_character_rate = 6000 self._num_edid_blocks = 0 self._edid: Optional[bytes] = None self._port_name: Optional[str] = None self._connected_device: Optional[HdmiDeviceInfo] = None self._is_connected = False def getId(self) -> int: return self._id def getType(self) -> int: return self._type def getPhysicalAddress(self) -> int: return self._physical_address def isCecSupported(self) -> bool: return self._is_cec_supported def setCecSupported(self, supported: bool) -> None: self._is_cec_supported = supported def isArcSupported(self) -> bool: return self._is_arc_supported def setArcSupported(self, supported: bool) -> None: self._is_arc_supported = supported def isEarcSupported(self) -> bool: return self._is_earc_supported def setEarcSupported(self, supported: bool) -> None: self._is_earc_supported = supported def isMhlSupported(self) -> bool: return self._is_mhl_supported def supports4k(self) -> bool: return self._supports_4k def setSupports4k(self, supported: bool) -> None: self._supports_4k = supported def supportsHdr(self) -> bool: return self._supports_hdr def supportsDolbyVision(self) -> bool: return self._supports_dolby_vision def getMaxTmdsCharacterRate(self) -> int: return self._max_tmds_character_rate def getPortName(self) -> Optional[str]: return self._port_name def setPortName(self, name: str) -> None: self._port_name = name def isConnected(self) -> bool: return self._is_connected def setConnected(self, connected: bool, device: Optional[HdmiDeviceInfo] = None) -> None: self._is_connected = connected self._connected_device = device def getConnectedDevice(self) -> Optional[HdmiDeviceInfo]: return self._connected_device def toString(self) -> str: return ( f"HdmiPortInfo(id={self._id}, type={self._type}, " f"cec={self._is_cec_supported}, arc={self._is_arc_supported}, " f"connected={self._is_connected})" ) # ═══════════════════════════════════════════════════════════════ # HdmiCecDeviceInfo # ═══════════════════════════════════════════════════════════════ class HdmiCecDeviceInfo(Stub): """HdmiCecDeviceInfo — CEC 设备信息""" def __init__(self): super().__init__() self._logical_address = -1 self._physical_address = 0xFFFF self._device_type = DEVICE_TYPE_INACTIVE self._vendor_id = 0 self._osd_name: Optional[str] = None self._cec_version = CEC_VERSION_2_0 self._language: Optional[str] = None self._features: List[int] = [] self._is_active = False def getLogicalAddress(self) -> int: return self._logical_address def getPhysicalAddress(self) -> int: return self._physical_address def getDeviceType(self) -> int: return self._device_type def getVendorId(self) -> int: return self._vendor_id def getOsdName(self) -> Optional[str]: return self._osd_name def getCecVersion(self) -> int: return self._cec_version def getLanguage(self) -> Optional[str]: return self._language def getFeatures(self) -> List[int]: return list(self._features) def isActive(self) -> bool: return self._is_active def toString(self) -> str: return ( f"HdmiCecDeviceInfo(logical={self._logical_address}, " f"type={self._device_type}, name={self._osd_name})" ) # ═══════════════════════════════════════════════════════════════ # HdmiRecordSources # ═══════════════════════════════════════════════════════════════ class HdmiRecordSources(Stub): """HdmiRecordSources — HDMI 录制源""" def __init__(self): super().__init__() @staticmethod def createDigitalSource(serviceId: int, source: int) -> Any: return {"type": "digital", "serviceId": serviceId, "source": source} @staticmethod def createAnalogueSource(serviceType: int, frequency: int, system: int) -> Any: return {"type": "analogue", "serviceType": serviceType, "frequency": frequency, "system": system} @staticmethod def createExternalPlug(plugNumber: int) -> Any: return {"type": "external_plug", "plugNumber": plugNumber} @staticmethod def createExternalPhysicalAddress(physicalAddress: int) -> Any: return {"type": "external_physical", "physicalAddress": physicalAddress} @staticmethod def createOwnSource() -> Any: return {"type": "own"} @staticmethod def createProgramNumber(programNumber: int) -> Any: return {"type": "program", "programNumber": programNumber} def toString(self) -> str: return "HdmiRecordSources" # ═══════════════════════════════════════════════════════════════ # HdmiRecordListener # ═══════════════════════════════════════════════════════════════ class HdmiRecordListener(Stub): """HdmiRecordListener — HDMI 录制监听器""" def __init__(self): super().__init__() def onRecordStarted(self, source: Any, result: int) -> None: pass def onRecordStopped(self, source: Any) -> None: pass def onRecordScheduleStarted(self, source: Any, result: int) -> None: pass def onRecordScheduleStopped(self, source: Any) -> None: pass def onClearTimerStarted(self, source: Any, result: int) -> None: pass def onClearTimerStopped(self, source: Any) -> None: pass def onOneTouchRecordStarted(self, source: Any) -> None: pass def onOneTouchRecordStopped(self, source: Any) -> None: pass def onTimerRecordStarted(self, source: Any, result: int) -> None: pass def onTimerRecordStopped(self, source: Any) -> None: pass def toString(self) -> str: return "HdmiRecordListener" # ═══════════════════════════════════════════════════════════════ # HdmiPlaybackClient # ═══════════════════════════════════════════════════════════════ class HdmiPlaybackClient(Stub): """HdmiPlaybackClient — HDMI 播放客户端""" def __init__(self, manager: "HdmiControlManager"): super().__init__() self._manager = manager self._callback: Optional[Callable] = None def sendKeyEvent(self, keyCode: int, isPressed: bool) -> None: pass def oneTouchPlay(self, callback: Optional[Callable] = None) -> None: if callback: callback(RESULT_SUCCESS) def queryDeviceType(self, callback: Optional[Callable] = None) -> None: if callback: callback(DEVICE_TYPE_PLAYBACK) def setCallback(self, callback: Callable) -> None: self._callback = callback def clearCallback(self) -> None: self._callback = None def toString(self) -> str: return "HdmiPlaybackClient" # ═══════════════════════════════════════════════════════════════ # HdmiSwitchClient # ═══════════════════════════════════════════════════════════════ class HdmiSwitchClient(Stub): """HdmiSwitchClient — HDMI 切换客户端""" def __init__(self, manager: "HdmiControlManager"): super().__init__() self._manager = manager self._callback: Optional[Callable] = None def selectDevice(self, logicalAddress: int, callback: Optional[Callable] = None) -> None: if callback: callback(RESULT_SUCCESS) def setCallback(self, callback: Callable) -> None: self._callback = callback def clearCallback(self) -> None: self._callback = None def getDeviceList(self) -> List[HdmiDeviceInfo]: return self._manager.getDeviceList() def toString(self) -> str: return "HdmiSwitchClient" # ═══════════════════════════════════════════════════════════════ # HdmiTvClient # ═══════════════════════════════════════════════════════════════ class HdmiTvClient(Stub): """HdmiTvClient — HDMI TV 客户端""" def __init__(self, manager: "HdmiControlManager"): super().__init__() self._manager = manager self._callback: Optional[Callable] = None self._record_listener: Optional[HdmiRecordListener] = None def deviceSelect(self, logicalAddress: int, callback: Optional[Callable] = None) -> None: if callback: callback(RESULT_SUCCESS) def sendStandby(self, deviceId: int, callback: Optional[Callable] = None) -> None: if callback: callback(RESULT_SUCCESS) def setSystemAudioMode(self, enabled: bool, callback: Optional[Callable] = None) -> None: if callback: callback(RESULT_SUCCESS) def setArcEnabled(self, enabled: bool) -> None: pass def setEarcEnabled(self, enabled: bool) -> None: pass def startOneTouchRecord(self, address: int, source: Any) -> None: if self._record_listener: self._record_listener.onOneTouchRecordStarted(source) def stopOneTouchRecord(self, address: int) -> None: if self._record_listener: self._record_listener.onOneTouchRecordStopped(address) def startTimerRecording(self, address: int, source: Any, duration: int) -> None: if self._record_listener: self._record_listener.onTimerRecordStarted(source, RESULT_SUCCESS) def clearTimerRecording(self, address: int, source: Any) -> None: if self._record_listener: self._record_listener.onClearTimerStarted(source, RESULT_SUCCESS) def setRecordListener(self, listener: HdmiRecordListener) -> None: self._record_listener = listener def setCallback(self, callback: Callable) -> None: self._callback = callback def clearCallback(self) -> None: self._callback = None def startPlayback(self, address: int) -> None: pass def stopPlayback(self, address: int) -> None: pass def toString(self) -> str: return "HdmiTvClient" # ═══════════════════════════════════════════════════════════════ # HdmiControlManager # ═══════════════════════════════════════════════════════════════ class HdmiControlManager(Stub): """HdmiControlManager — HDMI 控制管理器 (桌面存根)""" DEVICE_TYPE_TV = DEVICE_TYPE_TV DEVICE_TYPE_RECORDER = DEVICE_TYPE_RECORDER DEVICE_TYPE_TUNER = DEVICE_TYPE_TUNER DEVICE_TYPE_PLAYBACK = DEVICE_TYPE_PLAYBACK DEVICE_TYPE_AUDIO_SYSTEM = DEVICE_TYPE_AUDIO_SYSTEM DEVICE_TYPE_PURE_CEC_SWITCH = DEVICE_TYPE_PURE_CEC_SWITCH DEVICE_TYPE_VIDEO_PROCESSOR = DEVICE_TYPE_VIDEO_PROCESSOR DEVICE_TYPE_RESERVED = DEVICE_TYPE_RESERVED DEVICE_TYPE_INACTIVE = DEVICE_TYPE_INACTIVE CONTROL_STATE_CHANGED_REASON_UNKNOWN = CONTROL_STATE_CHANGED_REASON_UNKNOWN CONTROL_STATE_CHANGED_REASON_ENABLED = CONTROL_STATE_CHANGED_REASON_ENABLED CONTROL_STATE_CHANGED_REASON_DISABLED = CONTROL_STATE_CHANGED_REASON_DISABLED CONTROL_STATE_CHANGED_REASON_WAKE_UP = CONTROL_STATE_CHANGED_REASON_WAKE_UP CONTROL_STATE_CHANGED_REASON_SLEEP = CONTROL_STATE_CHANGED_REASON_SLEEP CONTROL_STATE_CHANGED_REASON_STANDBY = CONTROL_STATE_CHANGED_REASON_STANDBY POWER_STATUS_ON = POWER_STATUS_ON POWER_STATUS_STANDBY = POWER_STATUS_STANDBY POWER_STATUS_TO_ON = POWER_STATUS_TO_ON POWER_STATUS_TO_STANDBY = POWER_STATUS_TO_STANDBY POWER_STATUS_UNKNOWN = POWER_STATUS_UNKNOWN RESULT_SUCCESS = RESULT_SUCCESS RESULT_FAILURE = RESULT_FAILURE RESULT_TIMEOUT = RESULT_TIMEOUT RESULT_NOT_SUPPORTED = RESULT_NOT_SUPPORTED RESULT_BUSY = RESULT_BUSY CEC_VERSION_1_4 = CEC_VERSION_1_4 CEC_VERSION_2_0 = CEC_VERSION_2_0 _instance: Optional["HdmiControlManager"] = None def __init__(self, context: Any = None): super().__init__() self._context = context self._lock = threading.Lock() self._devices: Dict[int, HdmiDeviceInfo] = {} self._ports: Dict[int, HdmiPortInfo] = {} self._is_available = True self._hdmi_cec_enabled = True self._hdmi_mhl_enabled = True self._system_audio_mode = False self._arc_enabled = False self._earc_enabled = False self._active_source: Optional[HdmiDeviceInfo] = None self._playback_client: Optional[HdmiPlaybackClient] = None self._tv_client: Optional[HdmiTvClient] = None self._switch_client: Optional[HdmiSwitchClient] = None self._hotplug_listeners: List[Callable] = [] self._cec_message_listeners: List[Callable] = [] self._control_state_listeners: List[Callable] = [] def getPlaybackClient(self) -> Optional[HdmiPlaybackClient]: if self._playback_client is None: self._playback_client = HdmiPlaybackClient(self) return self._playback_client def getTvClient(self) -> Optional[HdmiTvClient]: if self._tv_client is None: self._tv_client = HdmiTvClient(self) return self._tv_client def getSwitchClient(self) -> Optional[HdmiSwitchClient]: if self._switch_client is None: self._switch_client = HdmiSwitchClient(self) return self._switch_client def getDeviceList(self) -> List[HdmiDeviceInfo]: with self._lock: return list(self._devices.values()) def getDeviceInfo(self, logicalAddress: int) -> Optional[HdmiDeviceInfo]: with self._lock: return self._devices.get(logicalAddress) def getPortInfo(self, portId: int) -> Optional[HdmiPortInfo]: with self._lock: return self._ports.get(portId) def getPorts(self) -> List[HdmiPortInfo]: with self._lock: return list(self._ports.values()) def addDevice(self, device: HdmiDeviceInfo) -> None: with self._lock: self._devices[device.getLogicalAddress()] = device if device.isActiveSource(): self._active_source = device def removeDevice(self, logicalAddress: int) -> Optional[HdmiDeviceInfo]: with self._lock: device = self._devices.pop(logicalAddress, None) if device and self._active_source is device: self._active_source = None return device def clearDevices(self) -> None: with self._lock: self._devices.clear() self._active_source = None def isCecEnabled(self) -> bool: return self._hdmi_cec_enabled def setCecEnabled(self, enabled: bool) -> None: self._hdmi_cec_enabled = enabled def isMhlEnabled(self) -> bool: return self._hdmi_mhl_enabled def setMhlEnabled(self, enabled: bool) -> None: self._hdmi_mhl_enabled = enabled def isSystemAudioMode(self) -> bool: return self._system_audio_mode def setSystemAudioMode(self, mode: bool) -> None: self._system_audio_mode = mode def isArcEnabled(self) -> bool: return self._arc_enabled def setArcEnabled(self, enabled: bool) -> None: self._arc_enabled = enabled def isEarcEnabled(self) -> bool: return self._earc_enabled def setEarcEnabled(self, enabled: bool) -> None: self._earc_enabled = enabled def getActiveSource(self) -> Optional[HdmiDeviceInfo]: with self._lock: return self._active_source def setActiveSource(self, device: HdmiDeviceInfo) -> None: with self._lock: self._active_source = device def addHotplugEventListener(self, listener: Callable) -> None: if listener not in self._hotplug_listeners: self._hotplug_listeners.append(listener) def removeHotplugEventListener(self, listener: Callable) -> None: if listener in self._hotplug_listeners: self._hotplug_listeners.remove(listener) def addCecMessageListener(self, listener: Callable) -> None: if listener not in self._cec_message_listeners: self._cec_message_listeners.append(listener) def removeCecMessageListener(self, listener: Callable) -> None: if listener in self._cec_message_listeners: self._cec_message_listeners.remove(listener) def addControlStateChangeListener(self, listener: Callable) -> None: if listener not in self._control_state_listeners: self._control_state_listeners.append(listener) def removeControlStateChangeListener(self, listener: Callable) -> None: if listener in self._control_state_listeners: self._control_state_listeners.remove(listener) def isAvailable(self) -> bool: return self._is_available def sendCecMessage(self, message: Any) -> int: return RESULT_SUCCESS def toString(self) -> str: with self._lock: return ( f"HdmiControlManager(devices={len(self._devices)}, " f"ports={len(self._ports)}, " f"cec={self._hdmi_cec_enabled})" )