/usr/lib/python2.7/dist-packages/framework/subsystems/ogsmd/modems/abstract/calling.py is in fso-frameworkd 0.10.1-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 | #!/usr/bin/env python
"""
freesmartphone.org ogsmd - Python Implementation
(C) 2008-2009 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
(C) 2008-2009 Openmoko, Inc.
GPLv2 or later
Package: ogsmd.modems.abstract
Module: calling
New style abstract call handling
"""
__version__ = "0.9.1.4"
MODULE_NAME = "ogsmd.callhandler"
import mediator
from ogsmd import error
from ogsmd.gsm import const
from framework.patterns.processguard import ProcessGuard
import logging
logger = logging.getLogger( MODULE_NAME )
#=========================================================================#
class CallHandler( object ):
#=========================================================================#
_instance = None
@classmethod
def getInstance( klass, dbus_object=None ):
if klass._instance is None and dbus_object is not None:
klass._instance = CallHandler( dbus_object )
return klass._instance
def __init__( self, dbus_object ):
self._object = dbus_object
self._calls = {}
self._calls[1] = { "status": "release" }
self._calls[2] = { "status": "release" }
# we can have at least 2 calls, more will be added when coming in
self.unsetHook()
def setHook( self, hook ):
self._hook = hook
def unsetHook( self ):
self._hook = lambda *args, **kwargs: None
def isBusy( self ):
return self._calls[1]["status"] != "release" or self._calls[2]["status"] != "release"
def status( self ):
return self._calls[1]["status"], self._calls[2]["status"]
#
# additional support for data call handling with a customizable data call handler
#
def onActivateResult( self, request, response ):
"""
Called after ATA
"""
if response[0].startswith( "CONNECT" ): # data call succeeded
self._onDataCallEstablished()
def onInitiateResult( self, request, response ):
"""
Called after ATDxyz
"""
if response[0].startswith( "CONNECT" ): # data call succeeded
self._onDataCallEstablished()
def _onDataCallEstablished( self ):
logger.debug( "data call established" )
# if this is a data call, add the port where communication happens
self.csdid = callId = 1 if self._calls[1]["status"] == "active" else 2
self.csdchan = channel = self._object.modem.channel( "CallMediator" )
self.statusChangeFromNetwork( callId, { "status": "connect", "port": channel.port() } )
# check whether we have a data call handler registered
dataCallHandler = self._object.modem.data( "data-call-handler" )
if dataCallHandler is not None:
self.csdchan.freeze()
csd_commandline = dataCallHandler.split()
if not dataCallHandler.startswith( "/bin/sleep" ): # for debugging
csd_commandline += [ channel.port(), self._calls[callId]["direction"] ]
self.csdproc = ProcessGuard( csd_commandline )
logger.info( "launching csd handler as commandline %s" % csd_commandline )
self.csdproc.execute( onExit=self._spawnedProcessDone )
else:
logger.info( "no csd handler registered" )
def _spawnedProcessDone( self, pid, exitcode, exitsignal ):
"""
Called after CSD Handler exit.
"""
logger.info( "csd handler exited with code %d, signal %d" % ( exitcode, exitsignal ) )
# unfreeze
self.csdchan.thaw()
# release call and resume normal operation
# self.release( self.csdid, self._object.modem.channel( "MiscMediator" ) )
self.releaseAll( self._object.modem.channel( "MiscMediator" ) )
#
# called from mediators
#
def initiate( self, dialstring, commchannel ):
result = self.feedUserInput( "initiate", dialstring, commchannel )
self._hook( "initiate", result )
return result
def activate( self, index, commchannel ):
result = self.feedUserInput( "activate", index=index, channel=commchannel )
self._hook( "activate", result )
return result
def activateConference( self, index, commchannel ):
result = self.feedUserInput( "conference", index=index, channel=commchannel )
self._hook( "conference", result )
return result
def release( self, index, commchannel ):
result = self.feedUserInput( "release", index=index, channel=commchannel )
self._hook( "release", result )
return result
def releaseAll( self, commchannel ):
result = self.feedUserInput( "dropall", channel=commchannel )
self._hook( "dropall", result )
return result
def hold( self, commchannel ):
result = self.feedUserInput( "hold", channel=commchannel )
self._hook( "hold", result )
return result
#
# called from unsolicited response delegates
#
def ring( self ):
for callId, info in self._calls.items():
if info["status"] == "incoming":
self._updateStatus( callId )
break # can't be more than one call incoming at once (GSM limitation)
# FIXME is the above comment really true?
def statusChangeFromNetwork( self, callId, info ):
if not self._calls.has_key(callId):
self._calls[callId] = { "status": "release" }
lastStatus = self._calls[callId].copy()
self._calls[callId].update( info )
if self._calls[callId]["status"] == "release":
# release signal always without properties
self._calls[callId] = { "status": "release" }
if self._calls[callId]["status"] != "incoming":
# suppress sending the same signal twice
if lastStatus != self._calls[callId]:
self._updateStatus( callId )
else:
self._updateStatus( callId )
def statusChangeFromNetworkByStatus( self, status, info ):
calls = [call for call in self._calls.items() if call[1]["status"] == status]
if not len(calls) == 1:
raise error.InternalException( "non-unique call state '%'" % status )
self.statusChangeFromNetwork( calls[0][0], info )
#
# internal
#
def _updateStatus( self, callId ):
"""send dbus signal indicating call status for a callId"""
self._object.CallStatus( callId, self._calls[callId]["status"], self._calls[callId] )
def feedUserInput( self, action, *args, **kwargs ):
# simple actions
# FIXME might rather want to consider using the state machine, since that would be more clear
if action == "dropall":
kwargs["channel"].enqueue( 'H' )
return True
try:
state = "state_%s_%s" % ( self._calls[1]["status"], self._calls[2]["status"] )
method = getattr( self, state )
except AttributeError:
logger.exception( "unhandled state '%s' in state machine. calls are %s" % ( state, repr(self._calls) ) )
raise error.InternalException( "unhandled state '%s' in state machine. calls are %s" % ( state, repr(self._calls) ) )
else:
return method( action, *args, **kwargs )
#
# deal with responses from call control commands
#
def responseFromChannel( self, request, response ):
logger.debug( "response from channel to %s = %s", request, response )
def errorFromChannel( self, request, response ):
logger.error( "error from channel to %s = %s", request, response )
#
# synchronize status
#
def syncStatus( self, request, response ):
mediator.CallListCalls( self._object, self.syncStatus_ok, self.syncStatus_err )
def syncStatus_ok( self, calls ):
if len( calls ) > 1:
logger.warning( "unhandled case" )
logger.warning( "calls is %s", calls)
#return
# synthesize status change from network
for call in calls:
callid, status, properties = call
self.statusChangeFromNetwork( callid, {"status": status} )
def syncStatus_err( self, request, error ):
logger.error( "error from channel to %s = %s", request, error )
#
# state machine actions following. micro states:
#
# release: line idle, call has been released
# incoming: remote party is calling, network is alerting us
# outgoing: local party is calling, network is alerting remote party
# active: local and remote party talking
# held: remote party held
# An important command here is +CHLD=<n>
# <n> Description
# -----------------
# 0 Release all held calls or set the busy state for the waiting call.
# 1 Release all active calls.
# 1x Release only call x.
# 2 Put active calls on hold (and activate the waiting or held call).
# 2x Put active calls on hold and activate call x.
# 3 Add the held calls to the active conversation.
# 4 Add the held calls to the active conversation, and then detach the local subscriber from the conversation.
#
# action with 1st call, 2nd call released
#
def state_release_release( self, action, *args, **kwargs ):
if action == "initiate":
dialstring, commchannel = args
commchannel.enqueue( "D%s" % dialstring, self.onInitiateResult, self.errorFromChannel )
return 1
def state_incoming_release( self, action, *args, **kwargs ):
if action == "release" and kwargs["index"] == 1:
kwargs["channel"].enqueue( 'H' )
return True
elif action == "activate" and kwargs["index"] == 1:
# FIXME handle data calls here
kwargs["channel"].enqueue( 'A', self.onActivateResult )
return True
def state_outgoing_release( self, action, *args, **kwargs ):
if action == "release" and kwargs["index"] == 1:
command = self._object.modem.data( "cancel-outgoing-call" )
kwargs["channel"].enqueue( command )
return True
def state_active_release( self, action, *args, **kwargs ):
if action == "release" and kwargs["index"] == 1:
kwargs["channel"].enqueue( 'H' )
return True
elif action == "hold":
# put active call on hold without accepting any waiting or held
# this is not supported by all modems / networks
# thus we must call syncStatus to check
self.channel = kwargs["channel"]
kwargs["channel"].enqueue( "+CHLD=2", self.syncStatus )
return True
# FIXME add state_release_active
def state_held_release( self, action, *args, **kwargs ):
# state not supported by all modems
if action == "release" and kwargs["index"] == 1:
kwargs["channel"].enqueue( 'H' )
return True
elif action == "activate" and kwargs["index"] == 1:
# activate held call
self.channel = kwargs["channel"]
kwargs["channel"].enqueue( "+CHLD=2", self.syncStatus )
return True
elif action == "initiate":
dialstring, commchannel = args
commchannel.enqueue( "D%s" % dialstring, self.onInitiateResult, self.errorFromChannel )
return 2
#
# 1st call active, 2nd call call incoming or on hold
#
def state_active_incoming( self, action, *args, **kwargs ):
if action == "release":
if kwargs["index"] == 1:
# release active call, waiting call becomes active
kwargs["channel"].enqueue( "+CHLD=1" )
return True
elif kwargs["index"] == 2:
# reject waiting call, sending busy signal
kwargs["channel"].enqueue( "+CHLD=0" )
return True
elif action == "activate":
if kwargs["index"] == 2:
# put active call on hold, take waiting call
kwargs["channel"].enqueue( "+CHLD=2" )
return True
elif action == "conference":
# put active call on hold, take waiting call, add held call to conversation
kwargs["channel"].enqueue( "+CHLD=2;+CHLD=3" )
return True
def state_active_held( self, action, *args, **kwargs ):
if action == "release":
if kwargs["index"] == 1:
# release active call, (auto)activate the held call
kwargs["channel"].enqueue( "+CHLD=11" )
return True
elif kwargs["index"] == 2:
# release held call
kwargs["channel"].enqueue( "+CHLD=12" )
return True
else:
# Fixme: we can have a 3rd call incoming that cannot be accepted, however, but still rejected
# TI Calypso indicates the 3rd call, but refuses the index on commanding???
logger.warning("FIXME: callid >2 (%s), don't know what to do", kwargs["index"])
elif action == "activate":
# put active call on hold, activate held call
kwargs["channel"].enqueue( "+CHLD=2" )
return True
elif action == "conference":
kwargs["channel"].enqueue( "+CHLD=3" )
return True
elif action == "connect":
kwargs["channel"].enqueue( "+CHLD=4" )
return True
def state_held_active( self, action, *args, **kwargs ):
# should be the same as the reversed state
return self.state_active_held( action, *args, **kwargs )
# both calls active
def state_active_active( self, action, *args, **kwargs ):
if action == "release":
if kwargs["index"] == 1:
# release only call 1
kwargs["channel"].enqueue( "+CHLD=11" )
return True
elif kwargs["index"] == 2:
kwargs["channel"].enqueue( "+CHLD=12" )
return True
elif action == "activate":
if kwargs["index"] == 1:
# put 2nd call on hold
kwargs["channel"].enqueue( "+CHLD=21" )
return True
elif kwargs["index"] == 2:
# put 1st call on hold
kwargs["channel"].enqueue( "+CHLD=22" )
return True
elif action == "connect":
kwargs["channel"].enqueue( "+CHLD=4" )
return True
|