From 7298d0d62a46733f3b7455fd36b5db49d93e6770 Mon Sep 17 00:00:00 2001 From: Simon Busch Date: Mon, 23 Jul 2012 17:53:17 +0200 Subject: fsogsmd: lib: introduce a new call handler implementation The new call handler tries to put as much logic related to voice calls together and just delegates single actions (e.g. initiating a call) to a call driver which implements then the protocol related logic. --- fsogsmd/src/lib/Makefile.am | 3 +- fsogsmd/src/lib/at/atcall.vala | 351 ----------------------------- fsogsmd/src/lib/call.vala | 249 --------------------- fsogsmd/src/lib/calldriver.vala | 100 +++++++++ fsogsmd/src/lib/callhandler.vala | 460 +++++++++++++++++++++++++++++++++++++++ fsogsmd/src/lib/modem.vala | 12 +- 6 files changed, 568 insertions(+), 607 deletions(-) delete mode 100644 fsogsmd/src/lib/at/atcall.vala create mode 100644 fsogsmd/src/lib/calldriver.vala create mode 100644 fsogsmd/src/lib/callhandler.vala (limited to 'fsogsmd/src/lib') diff --git a/fsogsmd/src/lib/Makefile.am b/fsogsmd/src/lib/Makefile.am index 84d9f9db..d675d728 100644 --- a/fsogsmd/src/lib/Makefile.am +++ b/fsogsmd/src/lib/Makefile.am @@ -10,7 +10,6 @@ AM_VALAFLAGS = modlibexecdir = $(libdir)/cornucopia/modules/fsogsm modlibexec_LTLIBRARIES = libfsogsm.la libfsogsm_la_SOURCES = \ - at/atcall.vala \ at/atchannel.vala \ at/atcommand.vala \ at/atcommands.vala \ @@ -48,6 +47,8 @@ libfsogsm_la_SOURCES = \ watchdog.vala \ serviceprovider.vala \ mbpi.vala \ + callhandler.vala \ + calldriver.vala \ \ $(top_srcdir)/src/3rdparty/conversions.c \ $(top_srcdir)/src/3rdparty/smsutil.c \ diff --git a/fsogsmd/src/lib/at/atcall.vala b/fsogsmd/src/lib/at/atcall.vala deleted file mode 100644 index 4c48cee3..00000000 --- a/fsogsmd/src/lib/at/atcall.vala +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (C) 2009-2012 Michael 'Mickey' Lauer - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -using Gee; -using FsoGsm.Constants; - -internal const int CALL_STATUS_REFRESH_TIMEOUT = 3; // in seconds - -/** - * @class FsoGsm.GenericAtCallHandler - */ -public class FsoGsm.GenericAtCallHandler : FsoGsm.AbstractCallHandler -{ - public override string repr() - { - return "<>"; - } - - // - // protected API - // - - protected override async void cancelOutgoingWithId( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - assert( logger.debug( @"Cancelling outgoing call with ID $id" ) ); - var cmd = modem.data().atCommandCancelOutgoing; - if ( cmd != null ) - { - var c1 = new CustomAtCommand(); - var r1 = yield modem.processAtCommandAsync( c1, cmd ); - checkResponseOk( c1, r1 ); - } - else - { - var c2 = modem.createAtCommand( "H" ); - var r2 = yield modem.processAtCommandAsync( c2, c2.execute() ); - checkResponseOk( c2, r2 ); - } - } - - protected override async void rejectIncomingWithId( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - assert( logger.debug( @"Rejecting incoming call with ID $id" ) ); - var cmd = modem.data().atCommandRejectIncoming; - if ( cmd != null ) - { - var c1 = new CustomAtCommand(); - var r1 = yield modem.processAtCommandAsync( c1, cmd ); - checkResponseOk( c1, r1 ); - } - else - { - var c2 = modem.createAtCommand( "H" ); - var r2 = yield modem.processAtCommandAsync( c2, c2.execute() ); - checkResponseOk( c2, r2 ); - } - } - - public override void addSupplementaryInformation( string direction, string info ) - { - supplementary = new FsoFramework.Pair( direction, info ); - } - - protected override async void syncCallStatus() - { - inSyncCallStatus = true; - - try - { - assert( logger.debug( "Synchronizing call status" ) ); - var m = modem.createMediator(); - yield m.run(); - - // workaround for https://bugzilla.gnome.org/show_bug.cgi?id=585847 - var length = 0; - foreach ( var c in m.calls ) - { - length++; - } - // - - assert( logger.debug( @"$(length) calls known in the system" ) ); - - // stop timer if there are no more calls - if ( length == 0 ) - { - assert( logger.debug( "call status idle -> stopping updater" ) ); - Source.remove( timeout ); - timeout = 0; - } - - if ( supplementary != null ) - { - // add supplementary information to incoming or outgoing - foreach ( var ca in m.calls ) - { - var direction = ca.properties.lookup( "direction" ).get_string(); - if ( direction == supplementary.first ) - { - ca.properties.insert( "service", supplementary.second ); - } - } - supplementary = null; - } - - // visit all busy (incoming,outgoing,held,active) calls to send updates... - var visited = new bool[Constants.CALL_INDEX_MAX+1]; - foreach ( var call in m.calls ) - { - calls[call.id].update( call ); - visited[call.id] = true; - } - - // ...and synthesize updates for (now) released calls - for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) - { - if ( ! visited[i] && calls[i].detail.status != FreeSmartphone.GSM.CallStatus.RELEASE ) - { - var detail = FreeSmartphone.GSM.CallDetail( - i, - FreeSmartphone.GSM.CallStatus.RELEASE, - new GLib.HashTable( str_hash, str_equal ) - ); - - var ceer = modem.createAtCommand( "+CEER" ); - var result = yield modem.processAtCommandAsync( ceer, ceer.execute() ); - if ( ceer.validate( result ) == Constants.AtResponse.VALID ) - { - detail.properties.insert( "cause", ceer.reason ); - } - - calls[i].update( detail ); - } - } - } - catch ( GLib.Error e ) - { - logger.error( @"Can't synchronize call status: $(e.message)" ); - } - - inSyncCallStatus = false; - } - - // - // public API - // - - public GenericAtCallHandler( FsoGsm.Modem modem ) - { - base( modem ); - } - - // - // DBus methods, delegated from the Call mediators - // - - public override async void activate( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - validateCallId( id ); - - if ( calls[id].detail.status != FreeSmartphone.GSM.CallStatus.INCOMING && - calls[id].detail.status != FreeSmartphone.GSM.CallStatus.HELD ) - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No suitable call to activate found" ); - } - - if ( numberOfBusyCalls() == 0 ) // simple case - { - var cmd = modem.createAtCommand( "A" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.execute() ); - checkResponseOk( cmd, response ); - } - else - { - // call is present and incoming or held - var cmd2 = modem.createAtCommand( "+CHLD" ); - var response2 = yield modem.processAtCommandAsync( cmd2, cmd2.issue( PlusCHLD.Action.HOLD_ALL_AND_ACCEPT_WAITING_OR_HELD ) ); - checkResponseOk( cmd2, response2 ); - } - } - - public override async int initiate( string number, string ctype ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - var num = lowestOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.RELEASE ); - if ( num == 0 ) - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "System busy" ); - } - - var cmd = modem.createAtCommand( "D" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.issue( number, ctype == "voice" ) ); - checkResponseOk( cmd, response ); - - startTimeoutIfNecessary(); - - return num; - } - - public override async void hold() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.ACTIVE ) == 0 ) - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No active call present" ); - } - if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.INCOMING ) > 0 ) - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "Call incoming. Can't hold active calls without activating" ); - } - var cmd = modem.createAtCommand( "+CHLD" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.issue( PlusCHLD.Action.HOLD_ALL_AND_ACCEPT_WAITING_OR_HELD ) ); - checkResponseOk( cmd, response ); - } - - public override async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - validateCallId( id ); - - if ( calls[id].detail.status == FreeSmartphone.GSM.CallStatus.RELEASE ) - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No suitable call to release found" ); - } - if ( calls[id].detail.status == FreeSmartphone.GSM.CallStatus.OUTGOING ) - { - yield cancelOutgoingWithId( id ); - return; - } - if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.INCOMING ) == 1 && calls[id].detail.status == FreeSmartphone.GSM.CallStatus.INCOMING ) - { - yield rejectIncomingWithId( id ); - return; - } - else - { - var cmd = modem.createAtCommand( "+CHLD" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.issue( PlusCHLD.Action.DROP_SPECIFIC_AND_ACCEPT_WAITING_OR_HELD, id ) ); - checkResponseOk( cmd, response ); - } - } - - public override async void releaseAll() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - if ( numberOfCallsWithSpecificStatus( new FreeSmartphone.GSM.CallStatus[] { - FreeSmartphone.GSM.CallStatus.INCOMING, FreeSmartphone.GSM.CallStatus.OUTGOING, - FreeSmartphone.GSM.CallStatus.HELD, FreeSmartphone.GSM.CallStatus.ACTIVE } ) == 0 ) - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No call to release available" ); - } - - var cmd = modem.createAtCommand( "H" ); - yield modem.processAtCommandAsync( cmd, cmd.execute() ); - // no checkResponseOk, this call will always succeed - } - - public override async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.ACTIVE ) == 0 && - // According to 22.091 section 5.8 it's possible that our network supports - // transfering incoming calls too - numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.INCOMING ) == 0 ) - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No active call present" ); - - if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.HELD ) == 0 ) - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No held call present" ); - - var cmd = modem.createAtCommand( "+CHLD" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.issue( PlusCHLD.Action.DROP_SELF_AND_CONNECT_ACTIVE ) ); - checkResponseOk( cmd, response ); - - // FIXME do we really need to call this here or can we skip it as call state - // polling is always active as long as we have an active call? - startTimeoutIfNecessary(); - } - - public override async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.INCOMING ) == 0 && - numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.HELD ) == 0 ) - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No active or held call present" ); - - validatePhoneNumber( number ); - - var cmd = modem.createAtCommand( "+CTFR" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.issue( number, determinePhoneNumberType( number ) ) ); - checkResponseOk( cmd, response ); - - // FIXME do we really need to call this here or can we skip it as call state - // polling is always active as long as we have an active call? - startTimeoutIfNecessary(); - } - - public override async void conference( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.ACTIVE ) != 0 ) - { - if ( calls[id].detail.status == FreeSmartphone.GSM.CallStatus.HELD || - calls[id].detail.status == FreeSmartphone.GSM.CallStatus.INCOMING ) - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "Specified call is not in held or incoming status" ); - } - } - else - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "Without an active call we can't create a conference call" ); - } - - // If we deal with an incoming call we have to activate it first and hold our - // current active call. If we deal with an active and an already held call we can - // step through and add both to the conference. - if ( calls[id].detail.status == FreeSmartphone.GSM.CallStatus.INCOMING ) - { - var cmd = modem.createAtCommand( "+CHLD" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.issue( (PlusCHLD.Action) 2 ) ); - checkResponseOk( cmd, response ); - } - - var cmd = modem.createAtCommand( "+CHLD" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.issue( (PlusCHLD.Action) 3 ) ); - checkResponseOk( cmd, response ); - } - - public override async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.ACTIVE ) != 0 && - numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.HELD ) != 0 ) - { - throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No active or hold calls to join" ); - } - - var cmd = modem.createAtCommand( "+CHLD" ); - var response = yield modem.processAtCommandAsync( cmd, cmd.issue( (PlusCHLD.Action) 4 )); - checkResponseOk( cmd, response ); - } -} - -// vim:ts=4:sw=4:expandtab diff --git a/fsogsmd/src/lib/call.vala b/fsogsmd/src/lib/call.vala index 7d3a2543..b0595dd2 100644 --- a/fsogsmd/src/lib/call.vala +++ b/fsogsmd/src/lib/call.vala @@ -119,253 +119,4 @@ public class FsoGsm.CallInfo : GLib.Object public GLib.HashTable cinfo; } -/** - * @interface FsoGsm.CallHandler - **/ -public abstract interface FsoGsm.CallHandler : FsoFramework.AbstractObject -{ - /** - * Call this, when the network has indicated an incoming call. - **/ - public abstract void handleIncomingCall( FsoGsm.CallInfo call_info ); - - /** - * Call this, when the network has indicated an connecting call - **/ - public abstract void handleConnectingCall( FsoGsm.CallInfo call_info ); - - /** - * Call this, when the network has indicated an ending call - **/ - public abstract void handleEndingCall( FsoGsm.CallInfo call_info ); - - /** - * Call this, when the network has indicated a supplementary service indication. - **/ - public abstract void addSupplementaryInformation( string direction, string info ); - - /** - * Call Actions - **/ - public abstract async void activate( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async int initiate( string number, string ctype ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void hold() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void releaseAll() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void conference( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; -} - -/** - * @class FsoGsm.NullCallHandler - **/ -public class FsoGsm.NullCallHandler : FsoGsm.CallHandler, FsoFramework.AbstractObject -{ - public void handleIncomingCall( FsoGsm.CallInfo call_info ) - { - } - - public void handleConnectingCall( FsoGsm.CallInfo call_info ) - { - } - - public void handleEndingCall( FsoGsm.CallInfo call_info ) - { - } - - public void addSupplementaryInformation( string direction, string info ) - { - } - - public async void activate( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - } - - public async int initiate( string number, string ctype ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - return 0; - } - - public async void hold() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - } - - public async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - } - - public async void releaseAll() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - } - - public async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - } - - public async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - } - - public async void conference( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - } - - public async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error - { - } - - public override string repr() - { - return @"<>"; - } -} - -/** - * @class FsoGsm.AbstractCallHandler - **/ -public abstract class FsoGsm.AbstractCallHandler : FsoGsm.CallHandler, FsoFramework.AbstractObject -{ - protected bool inSyncCallStatus; - protected uint timeout; - protected FsoGsm.Call[] calls; - - protected FsoFramework.Pair supplementary; - - protected FsoGsm.Modem modem { get; private set; } - - construct - { - calls = new FsoGsm.Call[Constants.CALL_INDEX_MAX+1] {}; - for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) - { - calls[i] = new Call.newFromId( i ); - calls[i].status_changed.connect( ( id, status, properties ) => { - var obj = modem.theDevice(); - obj.call_status( id, status, properties ); - } ); - } - } - - // - // protected - // - - protected void validateCallId( int id ) throws FreeSmartphone.Error - { - if ( id < 1 || id > Constants.CALL_INDEX_MAX ) - throw new FreeSmartphone.Error.INVALID_PARAMETER( "Call index needs to be within [ 1, %d ]".printf( (int)Constants.CALL_INDEX_MAX) ); - } - - protected int numberOfBusyCalls() - { - var num = 0; - for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) - { - if ( calls[i].detail.status != FreeSmartphone.GSM.CallStatus.RELEASE && calls[i].detail.status != FreeSmartphone.GSM.CallStatus.INCOMING ) - { - num++; - } - } - return num; - } - - protected int numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus status ) - { - var num = 0; - for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) - { - if ( calls[i].detail.status == status ) - { - num++; - } - } - return num; - } - - protected int numberOfCallsWithSpecificStatus( FreeSmartphone.GSM.CallStatus[] status ) - { - var num = 0; - for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) - { - if ( calls[i].detail.status in status ) - num++; - } - return num; - } - - protected int lowestOfCallsWithStatus( FreeSmartphone.GSM.CallStatus status ) - { - for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) - { - if ( calls[i].detail.status == status ) - { - return i; - } - } - return 0; - } - - protected void startTimeoutIfNecessary() - { - onTimeout(); - if ( timeout == 0 ) - { - timeout = GLib.Timeout.add_seconds( CALL_STATUS_REFRESH_TIMEOUT, onTimeout ); - } - } - - protected bool onTimeout() - { - if ( inSyncCallStatus ) - { - assert( logger.debug( "Synchronizing call status not done yet... ignoring" ) ); - } - else - { - syncCallStatus.begin(); - } - return true; - } - - protected abstract async void syncCallStatus(); - protected abstract async void cancelOutgoingWithId( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - protected abstract async void rejectIncomingWithId( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - - // - // public API - // - - public AbstractCallHandler( FsoGsm.Modem modem ) - { - this.modem = modem; - } - - public virtual void handleIncomingCall( FsoGsm.CallInfo call_info ) - { - startTimeoutIfNecessary(); - } - - public virtual void handleConnectingCall( FsoGsm.CallInfo call_info ) - { - } - - public virtual void handleEndingCall( FsoGsm.CallInfo call_info ) - { - } - - public abstract void addSupplementaryInformation( string direction, string info ); - - public abstract async void activate( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async int initiate( string number, string ctype ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void hold() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void releaseAll() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void conference( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; - public abstract async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; -} - // vim:ts=4:sw=4:expandtab diff --git a/fsogsmd/src/lib/calldriver.vala b/fsogsmd/src/lib/calldriver.vala new file mode 100644 index 00000000..96364d07 --- /dev/null +++ b/fsogsmd/src/lib/calldriver.vala @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2009-2012 Michael 'Mickey' Lauer + * 2012 Simon Busch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +public interface FsoGsm.ICallDriver : GLib.Object +{ + public abstract async void dial( string number, string type ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void activate() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void hangup_active() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void hangup_all() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void hold_all_active() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void release_all_held() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void release_all_active() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void create_conference() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + + public abstract async void cancel_outgoing_with_id( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void reject_incoming_with_id( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; +} + +public class NullCallDriver : FsoGsm.ICallDriver, GLib.Object +{ + public async void dial( string number, string type ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void activate() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void hangup_active() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void hangup_all() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void hold_all_active() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void release_all_held() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void release_all_active() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void create_conference() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + + public async void cancel_outgoing_with_id( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void reject_incoming_with_id( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } +} + +// vim:ts=4:sw=4:expandtab diff --git a/fsogsmd/src/lib/callhandler.vala b/fsogsmd/src/lib/callhandler.vala new file mode 100644 index 00000000..7c68d6cd --- /dev/null +++ b/fsogsmd/src/lib/callhandler.vala @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2009-2012 Michael 'Mickey' Lauer + * 2012 Simon Busch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +internal const int CALL_STATUS_REFRESH_TIMEOUT = 3; // in seconds + +public abstract interface FsoGsm.ICallHandler : FsoFramework.AbstractObject +{ + /** + * Call this, when the network has indicated an incoming call. + **/ + public abstract void handleIncomingCall( FsoGsm.CallInfo call_info ); + + /** + * Call this, when the network has indicated an connecting call + **/ + public abstract void handleConnectingCall( FsoGsm.CallInfo call_info ); + + /** + * Call this, when the network has indicated an ending call + **/ + public abstract void handleEndingCall( FsoGsm.CallInfo call_info ); + + /** + * Call this, when the network has indicated a supplementary service indication. + **/ + public abstract void addSupplementaryInformation( string direction, string info ); + + /** + * Call Actions + **/ + public abstract async void activate( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async int initiate( string number, string ctype ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void hold() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void releaseAll() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void conference( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; + public abstract async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error; +} + +public class FsoGsm.NullCallHandler : FsoGsm.ICallHandler, FsoFramework.AbstractObject +{ + public void handleIncomingCall( FsoGsm.CallInfo call_info ) + { + } + + public void handleConnectingCall( FsoGsm.CallInfo call_info ) + { + } + + public void handleEndingCall( FsoGsm.CallInfo call_info ) + { + } + + public void addSupplementaryInformation( string direction, string info ) + { + } + + public async void activate( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async int initiate( string number, string ctype ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + return 0; + } + + public async void hold() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void releaseAll() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void conference( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + } + + public override string repr() + { + return @"<>"; + } +} + +public class FsoGsm.CallHandler : FsoGsm.ICallHandler, FsoFramework.AbstractObject +{ + private ICallDriver driver; + private bool inSyncCallStatus; + private uint timeout; + private FsoGsm.Call[] calls; + private FsoFramework.Pair supplementary; + private FsoGsm.Modem modem { get; private set; } + + construct + { + calls = new FsoGsm.Call[Constants.CALL_INDEX_MAX+1] {}; + for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) + { + calls[i] = new Call.newFromId( i ); + calls[i].status_changed.connect( ( id, status, properties ) => { + var obj = modem.theDevice(); + obj.call_status( id, status, properties ); + } ); + } + } + + // + // private + // + + private void validateCallId( int id ) throws FreeSmartphone.Error + { + if ( id < 1 || id > Constants.CALL_INDEX_MAX ) + throw new FreeSmartphone.Error.INVALID_PARAMETER( "Call index needs to be within [ 1, %d ]".printf( (int)Constants.CALL_INDEX_MAX) ); + } + + private int numberOfBusyCalls() + { + var num = 0; + for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) + { + if ( calls[i].detail.status != FreeSmartphone.GSM.CallStatus.RELEASE && calls[i].detail.status != FreeSmartphone.GSM.CallStatus.INCOMING ) + { + num++; + } + } + return num; + } + + private int numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus status ) + { + var num = 0; + for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) + { + if ( calls[i].detail.status == status ) + num++; + } + return num; + } + + private int numberOfCallsWithSpecificStatus( FreeSmartphone.GSM.CallStatus[] status ) + { + var num = 0; + for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) + { + if ( calls[i].detail.status in status ) + num++; + } + return num; + } + + private int lowestOfCallsWithStatus( FreeSmartphone.GSM.CallStatus status ) + { + for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) + { + if ( calls[i].detail.status == status ) + return i; + } + return 0; + } + + private void startTimeoutIfNecessary() + { + onTimeout(); + if ( timeout == 0 ) + timeout = GLib.Timeout.add_seconds( CALL_STATUS_REFRESH_TIMEOUT, onTimeout ); + } + + private bool onTimeout() + { + if ( inSyncCallStatus ) + { + assert( logger.debug( "Synchronizing call status not done yet... ignoring" ) ); + } + else + { + syncCallStatus.begin(); + } + + return true; + } + + private async void syncCallStatus() + { + inSyncCallStatus = true; + + try + { + assert( logger.debug( "Synchronizing call status" ) ); + var m = modem.createMediator(); + yield m.run(); + + // workaround for https://bugzilla.gnome.org/show_bug.cgi?id=585847 + var length = 0; + foreach ( var c in m.calls ) + { + length++; + } + // + + assert( logger.debug( @"$(length) calls known in the system" ) ); + + // stop timer if there are no more calls + if ( length == 0 ) + { + assert( logger.debug( "call status idle -> stopping updater" ) ); + Source.remove( timeout ); + timeout = 0; + } + + if ( supplementary != null ) + { + // add supplementary information to incoming or outgoing + foreach ( var ca in m.calls ) + { + var direction = ca.properties.lookup( "direction" ).get_string(); + if ( direction == supplementary.first ) + { + ca.properties.insert( "service", supplementary.second ); + } + } + supplementary = null; + } + + // visit all busy (incoming,outgoing,held,active) calls to send updates... + var visited = new bool[Constants.CALL_INDEX_MAX+1]; + foreach ( var call in m.calls ) + { + calls[call.id].update( call ); + visited[call.id] = true; + } + + // ...and synthesize updates for (now) released calls + for ( int i = Constants.CALL_INDEX_MIN; i != Constants.CALL_INDEX_MAX; ++i ) + { + if ( ! visited[i] && calls[i].detail.status != FreeSmartphone.GSM.CallStatus.RELEASE ) + { + var detail = FreeSmartphone.GSM.CallDetail( + i, + FreeSmartphone.GSM.CallStatus.RELEASE, + new GLib.HashTable( str_hash, str_equal ) + ); + + /* + var ceer = modem.createAtCommand( "+CEER" ); + var result = yield modem.processAtCommandAsync( ceer, ceer.execute() ); + if ( ceer.validate( result ) == Constants.AtResponse.VALID ) + { + detail.properties.insert( "cause", ceer.reason ); + } + */ + + calls[i].update( detail ); + } + } + } + catch ( GLib.Error e ) + { + logger.error( @"Can't synchronize call status: $(e.message)" ); + } + + inSyncCallStatus = false; + } + + // + // public API + // + + public CallHandler( ICallDriver driver ) + { + this.driver = driver; + } + + public async void activate( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + validateCallId( id ); + + if ( calls[id].detail.status != FreeSmartphone.GSM.CallStatus.INCOMING && + calls[id].detail.status != FreeSmartphone.GSM.CallStatus.HELD ) + { + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No suitable call to activate found" ); + } + + if ( numberOfBusyCalls() != 0 ) + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "System busy" ); + + yield driver.activate(); + } + + public async int initiate( string number, string ctype ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + var num = lowestOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.RELEASE ); + if ( num == 0 ) + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "System busy" ); + + yield driver.dial( number, ctype ); + + startTimeoutIfNecessary(); + + return num; + } + + public async void hold() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.ACTIVE ) == 0 ) + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No active call present" ); + + if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.INCOMING ) > 0 ) + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "Call incoming. Can't hold active calls without activating" ); + } + + public async void release( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + validateCallId( id ); + + if ( calls[id].detail.status == FreeSmartphone.GSM.CallStatus.RELEASE ) + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No suitable call to release found" ); + + if ( calls[id].detail.status == FreeSmartphone.GSM.CallStatus.OUTGOING ) + { + yield driver.cancel_outgoing_with_id( id ); + return; + } + + if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.INCOMING ) == 1 && calls[id].detail.status == FreeSmartphone.GSM.CallStatus.INCOMING ) + { + yield driver.reject_incoming_with_id( id ); + return; + } + else + { + yield driver.release( id ); + } + } + + public async void releaseAll() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + if ( numberOfCallsWithSpecificStatus( new FreeSmartphone.GSM.CallStatus[] { + FreeSmartphone.GSM.CallStatus.INCOMING, FreeSmartphone.GSM.CallStatus.OUTGOING, + FreeSmartphone.GSM.CallStatus.HELD, FreeSmartphone.GSM.CallStatus.ACTIVE } ) == 0 ) + { + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No call to release available" ); + } + + yield driver.hangup_all(); + } + + public async void transfer() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.ACTIVE ) == 0 && + // According to 22.091 section 5.8 it's possible that our network supports + // transfering incoming calls too + numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.INCOMING ) == 0 ) + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No active call present" ); + + if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.HELD ) == 0 ) + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No held call present" ); + + yield driver.transfer(); + } + + public async void deflect( string number ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.INCOMING ) == 0 && + numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.HELD ) == 0 ) + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No active or held call present" ); + + validatePhoneNumber( number ); + + yield driver.deflect( number ); + } + + public async void conference( int id ) throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.ACTIVE ) != 0 ) + { + if ( calls[id].detail.status == FreeSmartphone.GSM.CallStatus.HELD || + calls[id].detail.status == FreeSmartphone.GSM.CallStatus.INCOMING ) + { + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "Specified call is not in held or incoming status" ); + } + } + else + { + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "Without an active call we can't create a conference call" ); + } + + yield driver.create_conference(); + } + + public async void join() throws FreeSmartphone.GSM.Error, FreeSmartphone.Error + { + if ( numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.ACTIVE ) != 0 && + numberOfCallsWithStatus( FreeSmartphone.GSM.CallStatus.HELD ) != 0 ) + { + throw new FreeSmartphone.GSM.Error.CALL_NOT_FOUND( "No active or hold calls to join" ); + } + + yield driver.join(); + } + + public void addSupplementaryInformation( string direction, string info ) + { + } + + public void handleIncomingCall( FsoGsm.CallInfo call_info ) + { + startTimeoutIfNecessary(); + } + + public void handleConnectingCall( FsoGsm.CallInfo call_info ) + { + } + + public void handleEndingCall( FsoGsm.CallInfo call_info ) + { + } + + public override string repr() + { + return @"<>"; + } +} + +// vim:ts=4:sw=4:expandtab diff --git a/fsogsmd/src/lib/modem.vala b/fsogsmd/src/lib/modem.vala index 201eeca3..4a712747 100644 --- a/fsogsmd/src/lib/modem.vala +++ b/fsogsmd/src/lib/modem.vala @@ -230,7 +230,7 @@ public abstract interface FsoGsm.Modem : FsoFramework.AbstractObject public abstract T createAtCommand( string command ); public abstract T theDevice(); public abstract IServiceProvider parent { get; set; } // the DBus object - public abstract CallHandler callhandler { get; set; } // the Call handler + public abstract ICallHandler callhandler { get; set; } // the Call handler public abstract SmsHandler smshandler { get; set; } // the Sms handler public abstract PhonebookHandler pbhandler { get; set; } // the Phonebook handler public abstract WatchDog watchdog { get; set; } // the WatchDog @@ -282,7 +282,7 @@ public abstract class FsoGsm.AbstractModem : FsoGsm.Modem, FsoFramework.Abstract protected UnsolicitedResponseHandler urc; public IServiceProvider parent { get; set; } // the DBus object - public CallHandler callhandler { get; set; } // the Call handler + public ICallHandler callhandler { get; set; } // the Call handler public SmsHandler smshandler { get; set; } // the SMS handler public PhonebookHandler pbhandler { get; set; } // the Phonebook handler public WatchDog watchdog { get; set; } // the WatchDog @@ -517,7 +517,7 @@ public abstract class FsoGsm.AbstractModem : FsoGsm.Modem, FsoFramework.Abstract private void registerHandlers() { urc = createUnsolicitedHandler(); - callhandler = createCallHandler(); + callhandler = new CallHandler( createCallDriver() ); smshandler = createSmsHandler(); pbhandler = createPhonebookHandler(); watchdog = createWatchDog(); @@ -629,11 +629,11 @@ public abstract class FsoGsm.AbstractModem : FsoGsm.Modem, FsoFramework.Abstract } /** - * Override this to return a custom type of Call handler to be used for this modem. + * Override this to return a custom type of Call driver to be used for this modem. **/ - protected virtual CallHandler createCallHandler() + protected virtual ICallDriver createCallDriver() { - return new GenericAtCallHandler( this ); + return new NullCallDriver(); } /** -- cgit v1.2.3