""" Android SIP API — SipManager, SipProfile, SipSession, SipAudioCall, SipErrorCode, SipSessionAdapter 方法签名完全对齐 Android SDK """ from __future__ import annotations import os import sys import time import uuid import socket 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,), {}) # ═══════════════════════════════════════════════════════════════ # SipErrorCode # ═══════════════════════════════════════════════════════════════ class SipErrorCode(Stub): """SipErrorCode — SIP 错误码""" NO_ERROR = 0 SOCKET_ERROR = -1 SERVER_ERROR = -2 TRANSACTION_ISSUE = -3 TRANSACTION_TERMINATED = -4 INVALID_REMOTE_URI = -5 PEER_NOT_REACHABLE = -6 CLIENT_ERROR = -7 INVALID_CREDENTIALS = -8 IN_PROGRESS = -9 NOT_REACHABLE = -10 TIME_OUT = -11 DATA_USAGE_LIMIT = -12 CROSS_DOMAIN_AUTHENTICATION = -13 SERVER_UNREACHABLE = -14 INTERNAL_SERVER_ERROR = -15 def __init__(self): super().__init__() # ═══════════════════════════════════════════════════════════════ # SipProfile # ═══════════════════════════════════════════════════════════════ class SipProfile(Stub): """SipProfile — SIP 用户档案""" class Builder(Stub): """SipProfile.Builder — 构建器""" def __init__(self, uriString: str): super().__init__() self._profile = SipProfile() self._profile._uri_string = uriString def __init__(self, settings: "SipProfile"): super().__init__() self._profile = SipProfile() self._profile._uri_string = settings.getUriString() self._profile._display_name = settings.getDisplayName() self._profile._auth_user = settings.getAuthUserName() self._profile._password = settings.getPassword() self._profile._server = settings.getServer() self._profile._port = settings.getPort() self._profile._protocol = settings.getProtocol() self._profile._send_keepalive = settings.getSendKeepAlive() self._profile._auto_registration = settings.isAutoRegistrationEnabled() 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 = authUserName return self def setPassword(self, password: str) -> "SipProfile.Builder": self._profile._password = password return self def setServer(self, server: str) -> "SipProfile.Builder": self._profile._server = server 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, enable: bool) -> "SipProfile.Builder": self._profile._send_keepalive = enable return self def setAutoRegistration(self, enable: bool) -> "SipProfile.Builder": self._profile._auto_registration = enable return self def setProfilePicture(self, icon: Any) -> "SipProfile.Builder": self._profile._profile_picture = icon return self def build(self) -> "SipProfile": return self._profile def __init__(self): super().__init__() self._uri_string: Optional[str] = None self._display_name: Optional[str] = None self._auth_user: Optional[str] = None self._password: Optional[str] = None self._server: Optional[str] = None self._port: int = 5060 self._protocol: str = "UDP" self._send_keepalive = False self._auto_registration = True self._profile_picture: Optional[Any] = None self._profile_id = str(uuid.uuid4()) self._local_ip: Optional[str] = None self._registration_time = 3600 self._is_registered = False self._created_at = time.time() def getUriString(self) -> Optional[str]: return self._uri_string def getDisplayName(self) -> Optional[str]: return self._display_name def getAuthUserName(self) -> Optional[str]: return self._auth_user def getPassword(self) -> Optional[str]: return self._password def getServer(self) -> Optional[str]: return self._server def getPort(self) -> int: return self._port def getProtocol(self) -> str: return self._protocol def getSendKeepAlive(self) -> bool: return self._send_keepalive def isAutoRegistrationEnabled(self) -> bool: return self._auto_registration def getProfilePicture(self) -> Optional[Any]: return self._profile_picture def getProfileId(self) -> str: return self._profile_id def getLocalIp(self) -> Optional[str]: return self._local_ip def setLocalIp(self, ip: str) -> None: self._local_ip = ip def isRegistered(self) -> bool: return self._is_registered def getRegistrationTime(self) -> int: return self._registration_time def setRegistrationTime(self, seconds: int) -> None: self._registration_time = seconds def getCreatedAt(self) -> float: return self._created_at def toString(self) -> str: return f"SipProfile(uri={self._uri_string}, display={self._display_name}, server={self._server})" # ═══════════════════════════════════════════════════════════════ # SipSessionAdapter # ═══════════════════════════════════════════════════════════════ class SipSessionAdapter(Stub): """SipSessionAdapter — SIP 会话适配器""" def __init__(self): super().__init__() def onCalling(self, session: "SipSession") -> None: pass def onRinging(self, session: "SipSession", caller: "SipProfile", options: Any) -> None: pass def onRingingBack(self, session: "SipSession") -> None: pass def onCallEstablished(self, session: "SipSession", sessionDescription: str) -> None: pass def onCallEnded(self, session: "SipSession") -> None: pass def onCallBusy(self, session: "SipSession") -> None: pass def onCallChangeFailed(self, session: "SipSession", errorCode: int, errorMessage: str) -> None: pass def onError(self, session: "SipSession", errorCode: int, errorMessage: str) -> None: pass def onRegistering(self, session: "SipSession") -> None: pass def onRegistrationDone(self, session: "SipSession", duration: int) -> None: pass def onRegistrationFailed( self, session: "SipSession", errorCode: int, errorMessage: str ) -> None: pass def onRegistrationTimeout(self, session: "SipSession") -> None: pass def toString(self) -> str: return "SipSessionAdapter" # ═══════════════════════════════════════════════════════════════ # SipSession # ═══════════════════════════════════════════════════════════════ class SipSession(Stub): """SipSession — SIP 会话""" # 会话状态 STATE_READY_TO_CALL = 0 STATE_INCOMING_CALL = 1 STATE_INCOMING_CALL_ANSWERING = 2 STATE_OUTGOING_CALL = 3 STATE_OUTGOING_CALL_RINGING = 4 STATE_OUTGOING_CALL_CANCELING = 5 STATE_IN_CALL = 6 STATE_ENDED = 7 STATE_DEREGISTER = 8 STATE_REGISTERING = 9 STATE_REGISTERED = 10 STATE_UNREGISTERED = 11 STATE_PENDING = 12 STATE_INVALID = -1 def __init__(self): super().__init__() self._session_id = str(uuid.uuid4()) self._local_profile: Optional[SipProfile] = None self._peer_profile: Optional[SipProfile] = None self._state = SipSession.STATE_READY_TO_CALL self._listener: Optional[SipSessionAdapter] = None self._call_id: Optional[str] = None self._is_in_call = False self._call_duration = 0 self._start_time: Optional[float] = None self._peer_session_description: Optional[str] = None self._local_session_description: Optional[str] = None self._credentials: Optional[Any] = None self._error_code = SipErrorCode.NO_ERROR self._error_message: Optional[str] = None self._lock = threading.Lock() def getSessionId(self) -> str: return self._session_id def getLocalProfile(self) -> Optional[SipProfile]: return self._local_profile def getPeerProfile(self) -> Optional[SipProfile]: return self._peer_profile def setListener(self, listener: SipSessionAdapter) -> None: self._listener = listener def getState(self) -> int: return self._state def setState(self, state: int) -> None: self._state = state def isInCall(self) -> bool: return self._is_in_call def getCallId(self) -> Optional[str]: return self._call_id def getCallDuration(self) -> int: return self._call_duration def getLocalSessionDescription(self) -> Optional[str]: return self._local_session_description def setLocalSessionDescription(self, description: str) -> None: self._local_session_description = description def getPeerSessionDescription(self) -> Optional[str]: return self._peer_session_description def setPeerSessionDescription(self, description: str) -> None: self._peer_session_description = description def getErrorCode(self) -> int: return self._error_code def getErrorMessage(self) -> Optional[str]: return self._error_message def makeCall(self, profile: SipProfile, sessionDescription: str, timeout: int = 30) -> None: self._peer_profile = profile self._state = SipSession.STATE_OUTGOING_CALL self._start_time = time.time() self._is_in_call = True if self._listener: self._listener.onCalling(self) self._listener.onRingingBack(self) self._listener.onCallEstablished(self, sessionDescription) def answerCall(self, sessionDescription: str, timeout: int = 30) -> None: self._state = SipSession.STATE_IN_CALL self._start_time = time.time() self._is_in_call = True if self._listener: self._listener.onCallEstablished(self, sessionDescription) def endCall(self) -> None: self._state = SipSession.STATE_ENDED self._is_in_call = False if self._start_time: self._call_duration = int(time.time() - self._start_time) if self._listener: self._listener.onCallEnded(self) def changeCall(self, sessionDescription: str, timeout: int = 30) -> None: if self._listener: self._listener.onCallChangeFailed(self, SipErrorCode.IN_PROGRESS, "changeCall not supported") def register(self, duration: int = 3600) -> None: self._state = SipSession.STATE_REGISTERING if self._listener: self._listener.onRegistering(self) self._state = SipSession.STATE_REGISTERED self._listener.onRegistrationDone(self, duration) def unregister(self) -> None: self._state = SipSession.STATE_UNREGISTERED def isRegistered(self) -> bool: return self._state == SipSession.STATE_REGISTERED def toString(self) -> str: return f"SipSession(state={self._state}, in_call={self._is_in_call})" # ═══════════════════════════════════════════════════════════════ # SipAudioCall # ═══════════════════════════════════════════════════════════════ class SipAudioCall(Stub): """SipAudioCall — SIP 音频通话""" class Listener(Stub): """SipAudioCall.Listener — 音频通话监听器""" def __init__(self): super().__init__() def onReady(self, call: "SipAudioCall") -> None: pass def onRinging(self, call: "SipAudioCall", caller: "SipProfile") -> None: pass def onRingingBack(self, call: "SipAudioCall") -> None: pass def onCallEstablished(self, call: "SipAudioCall") -> None: pass def onCallEnded(self, call: "SipAudioCall") -> None: pass def onCallBusy(self, call: "SipAudioCall") -> None: pass def onCallHeld(self, call: "SipAudioCall") -> None: pass def onError(self, call: "SipAudioCall", errorCode: int, errorMessage: str) -> None: pass def onChanged(self, call: "SipAudioCall") -> None: pass def toString(self) -> str: return "SipAudioCall.Listener" def __init__(self, context: Any = None, profile: Optional[SipProfile] = None): super().__init__() self._context = context self._profile = profile self._session: Optional[SipSession] = None self._listener: Optional["SipAudioCall.Listener"] = None self._local_holder: Optional[Any] = None self._is_muted = False self._is_speaker_on = False self._is_in_call = False self._start_time: Optional[float] = None self._call_duration = 0 self._audio_state = 0 def setListener(self, listener: "SipAudioCall.Listener") -> None: self._listener = listener def makeCall(self, profile: SipProfile, sessionDescription: str, timeout: int = 30) -> "SipSession": session = SipSession() session._local_profile = self._profile session.makeCall(profile, sessionDescription, timeout) self._session = session self._is_in_call = True self._start_time = time.time() if self._listener: self._listener.onCallEstablished(self) return session def answerCall(self, sessionDescription: str, timeout: int = 30) -> None: if self._session: self._session.answerCall(sessionDescription, timeout) self._is_in_call = True self._start_time = time.time() if self._listener: self._listener.onCallEstablished(self) def endCall(self) -> None: if self._session: self._session.endCall() self._is_in_call = False if self._start_time: self._call_duration = int(time.time() - self._start_time) if self._listener: self._listener.onCallEnded(self) def holdCall(self, timeout: int = 30) -> None: if self._listener: self._listener.onCallHeld(self) def continueCall(self, timeout: int = 30) -> None: pass def toggleMute(self) -> None: self._is_muted = not self._is_muted def isMuted(self) -> bool: return self._is_muted def setSpeakerMode(self, speakerMode: bool) -> None: self._is_speaker_on = speakerMode def isSpeakerOn(self) -> bool: return self._is_speaker_on def isInCall(self) -> bool: return self._is_in_call def getCallDuration(self) -> int: return self._call_duration def getLocalProfile(self) -> Optional[SipProfile]: return self._profile def getPeerProfile(self) -> Optional[SipProfile]: if self._session: return self._session.getPeerProfile() return None def getSession(self) -> Optional[SipSession]: return self._session def setLocalHolder(self, holder: Any) -> None: self._local_holder = holder def toString(self) -> str: return f"SipAudioCall(in_call={self._is_in_call}, muted={self._is_muted})" # ═══════════════════════════════════════════════════════════════ # SipManager # ═══════════════════════════════════════════════════════════════ class SipManager(Stub): """SipManager — SIP 管理器 (桌面存根)""" EXTRA_CALL_ID = "android.sip.extra.CALL_ID" EXTRA_OFFER_SD = "android.sip.extra.OFFER_SD" EXTRA_PROFILE = "android.sip.extra.PROFILE" EXTRA_RESULT = "android.sip.extra.RESULT" EXTRA_ERROR_CODE = "android.sip.extra.ERROR_CODE" EXTRA_ERROR_MESSAGE = "android.sip.extra.ERROR_MESSAGE" INCOMING_CALL_INTENT_ACTION = "android.SipDemo.INCOMING_CALL" _instance: Optional["SipManager"] = None def __init__(self, context: Any = None): super().__init__() self._context = context self._profiles: Dict[str, SipProfile] = {} self._sessions: Dict[str, SipSession] = {} self._audio_calls: Dict[str, SipAudioCall] = {} self._lock = threading.Lock() self._is_available = True self._is_api_supported = True self._sip_network_type = 0 @staticmethod def isApiSupported(context: Any) -> bool: return True @staticmethod def isVoipSupported(context: Any) -> bool: return True @staticmethod def isSipWifiOnly(context: Any) -> bool: return False @staticmethod def get(context: Any) -> "SipManager": if SipManager._instance is None: SipManager._instance = SipManager(context) return SipManager._instance @staticmethod def getSipNetworkType(context: Any) -> int: return 0 def open(self, profile: SipProfile) -> None: with self._lock: key = profile.getUriString() or profile.getProfileId() self._profiles[key] = profile def close(self, profile: SipProfile) -> None: with self._lock: key = profile.getUriString() or profile.getProfileId() self._profiles.pop(key, None) def isOpened(self, uri: str) -> bool: with self._lock: return uri in self._profiles def register(self, profile: SipProfile, expiryTime: int, listener: SipSessionAdapter) -> None: with self._lock: key = profile.getUriString() or profile.getProfileId() self._profiles[key] = profile profile._is_registered = True session = SipSession() session._local_profile = profile session._listener = listener self._sessions[key] = session listener.onRegistrationDone(session, expiryTime) def unregister(self, profile: SipProfile, listener: SipSessionAdapter) -> None: with self._lock: key = profile.getUriString() or profile.getProfileId() self._profiles.pop(key, None) session = self._sessions.pop(key, None) if session: session.setState(SipSession.STATE_UNREGISTERED) listener.onRegistrationTimeout(session) def isRegistered(self, uri: str) -> bool: with self._lock: profile = self._profiles.get(uri) return profile is not None and profile.isRegistered() def makeAudioCall( self, localProfile: SipProfile, peerProfile: SipProfile, listener: SipAudioCall.Listener, timeout: int = 30 ) -> SipAudioCall: call = SipAudioCall(self._context, localProfile) call.setListener(listener) call.makeCall(peerProfile, "", timeout) with self._lock: key = peerProfile.getUriString() or str(uuid.uuid4()) self._audio_calls[key] = call return call def makeAudioCall( self, localUri: str, peerUri: str, listener: SipAudioCall.Listener, timeout: int = 30 ) -> SipAudioCall: local = SipProfile.Builder(localUri).build() peer = SipProfile.Builder(peerUri).build() return self.makeAudioCall(local, peer, listener, timeout) def takeAudioCall(self, incomingCallIntent: Any, listener: SipAudioCall.Listener) -> SipAudioCall: call = SipAudioCall(self._context) call.setListener(listener) return call def getSessionFor(self, intent: Any) -> Optional[SipSession]: return None def getProfiles(self) -> List[SipProfile]: with self._lock: return list(self._profiles.values()) def getActiveSessions(self) -> List[SipSession]: with self._lock: return list(self._sessions.values()) def getActiveCalls(self) -> List[SipAudioCall]: with self._lock: return list(self._audio_calls.values()) def toString(self) -> str: return ( f"SipManager(profiles={len(self._profiles)}, " f"sessions={len(self._sessions)}, " f"calls={len(self._audio_calls)})" )