- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等) - java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等) - javax/ 4个模块 (net/crypto/security/sql) - org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http) - dalvik/ 1个模块 (system) - ~3053个类/接口,方法签名完全对齐Android SDK - 桌面跨平台运行 (Windows/macOS/Linux) - 修复所有导入错误和Python关键字冲突
639 lines
22 KiB
Python
639 lines
22 KiB
Python
"""
|
|
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)})"
|
|
) |