diff --git a/.gitignore b/.gitignore index 8393c66..049f50e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,11 @@ build unit/coverage/*.gcov unit/coverage/report +debian/.debhelper +debian/files +debian/*.debhelper.log +debian/*.install +debian/*.substvars +debian/libofonobinderpluginext-dev +debian/libofonobinderpluginext +debian/tmp diff --git a/lib/Makefile b/lib/Makefile index e233ed7..d578db6 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -46,6 +46,7 @@ LIB = $(LIB_NAME).a # SRC = \ + binder_ext_call.c \ binder_ext_plugin.c \ binder_ext_slot.c \ binder_ext_sms.c diff --git a/lib/include/binder_ext_call.h b/lib/include/binder_ext_call.h new file mode 100644 index 0000000..d1c5126 --- /dev/null +++ b/lib/include/binder_ext_call.h @@ -0,0 +1,309 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + */ + +#ifndef BINDER_EXT_CALL_H +#define BINDER_EXT_CALL_H + +#include "binder_ext_types.h" + +#include + +G_BEGIN_DECLS + +typedef enum binder_ext_call_interface_flags { + BINDER_EXT_CALL_INTERFACE_NO_FLAGS = 0, + BINDER_EXT_CALL_INTERFACE_FLAG_IMS_SUPPORT = 0x01, + BINDER_EXT_CALL_INTERFACE_FLAG_IMS_REQUIRED = 0x02 +} BINDER_EXT_CALL_INTERFACE_FLAGS; + +typedef enum binder_ext_call_type { + BINDER_EXT_CALL_TYPE_UNKNOWN, + BINDER_EXT_CALL_TYPE_VOICE +} BINDER_EXT_CALL_TYPE; + +typedef enum binder_ext_call_state { + BINDER_EXT_CALL_STATE_INVALID = -1, + BINDER_EXT_CALL_STATE_ACTIVE, + BINDER_EXT_CALL_STATE_HOLDING, + BINDER_EXT_CALL_STATE_DIALING, + BINDER_EXT_CALL_STATE_ALERTING, + BINDER_EXT_CALL_STATE_INCOMING, + BINDER_EXT_CALL_STATE_WAITING +} BINDER_EXT_CALL_STATE; + +typedef enum binder_ext_call_flags { + BINDER_EXT_CALL_FLAGS_NONE = 0, + BINDER_EXT_CALL_FLAG_INCOMING = 0x0001, + BINDER_EXT_CALL_FLAG_EMERGENCY = 0x0002, + BINDER_EXT_CALL_FLAG_ENCRYPTED = 0x0004, + BINDER_EXT_CALL_FLAG_MPTY = 0x0008, + BINDER_EXT_CALL_FLAG_RTT = 0x0010, + BINDER_EXT_CALL_FLAG_IMS = 0x0020 +} BINDER_EXT_CALL_FLAGS; + +typedef enum binder_ext_call_direction { + BINDER_EXT_CALL_DIRECTION_NONE, + BINDER_EXT_CALL_DIRECTION_RECEIVE, + BINDER_EXT_CALL_DIRECTION_SEND, + BINDER_EXT_CALL_DIRECTION_SEND_RECEIVE +} BINDER_EXT_CALL_DIRECTION; + +typedef enum binder_ext_call_clir { + BINDER_EXT_CALL_CLIR_DEFAULT, + BINDER_EXT_CALL_CLIR_INVOCATION, /* Restrict CLI presentation */ + BINDER_EXT_CALL_CLIR_SUPPRESSION /* Allow CLI presentation */ +} BINDER_EXT_CALL_CLIR; + +typedef enum binder_ext_call_hangup_reason { + BINDER_EXT_CALL_HANGUP_TERMINATE, + BINDER_EXT_CALL_HANGUP_IGNORE, + BINDER_EXT_CALL_HANGUP_REJECT +} BINDER_EXT_CALL_HANGUP_REASON; + +typedef enum binder_ext_call_disconnect_reason { + BINDER_EXT_CALL_DISCONNECT_UNKNOWN, + BINDER_EXT_CALL_DISCONNECT_LOCAL, + BINDER_EXT_CALL_DISCONNECT_REMOTE, + BINDER_EXT_CALL_DISCONNECT_ERROR +} BINDER_EXT_CALL_DISCONNECT_REASON; + +typedef enum binder_ext_call_result { + BINDER_EXT_CALL_RESULT_OK = 0, + BINDER_EXT_CALL_RESULT_ERROR +} BINDER_EXT_CALL_RESULT; + +typedef enum binder_ext_call_dial_flags { + BINDER_EXT_CALL_DIAL_FLAGS_NONE = 0 +} BINDER_EXT_CALL_DIAL_FLAGS; + +typedef enum binder_ext_call_deflect_flags { + BINDER_EXT_CALL_DEFLECT_FLAGS_NONE = 0 +} BINDER_EXT_CALL_DEFLECT_FLAGS; + +typedef enum binder_ext_call_answer_flags { + BINDER_EXT_CALL_ANSWER_NO_FLAGS = 0, + BINDER_EXT_CALL_ANSWER_FLAG_RTT = 0x01 /* Enable Real-Time Text */ +} BINDER_EXT_CALL_ANSWER_FLAGS; + +typedef enum binder_ext_call_transfer_flags { + BINDER_EXT_CALL_TRANSFER_FLAGS_NONE = 0 +} BINDER_EXT_CALL_TRANSFER_FLAGS; + +typedef enum binder_ext_call_conference_flags { + BINDER_EXT_CALL_CONFERENCE_FLAGS_NONE = 0 +} BINDER_EXT_CALL_CONFERENCE_FLAGS; + +typedef enum binder_ext_call_hangup_flags { + BINDER_EXT_CALL_HANGUP_NO_FLAGS = 0 +} BINDER_EXT_CALL_HANGUP_FLAGS; + +typedef enum binder_ext_call_swap_flags { + BINDER_EXT_CALL_SWAP_NO_FLAGS = 0, + BINDER_EXT_CALL_SWAP_FLAG_HANGUP = 0x01 /* Hangup active call */ +} BINDER_EXT_CALL_SWAP_FLAGS; + +typedef struct binder_ext_call_supp_svc_notify { + gboolean mt; + int code; + int index; + BINDER_EXT_TOA type; /* MT only */ + const char* number; /* MT only */ +} BinderExtCallSuppSvcNotify; + +typedef struct binder_ext_call_info { + guint call_id; + BINDER_EXT_CALL_TYPE type; + BINDER_EXT_CALL_STATE state; + BINDER_EXT_CALL_FLAGS flags; + BINDER_EXT_TOA toa; + const char* number; + const char* name; +} BinderExtCallInfo; + +typedef +void +(*BinderExtCallResultFunc)( + BinderExtCall* ext, + BINDER_EXT_CALL_RESULT result, + void* user_data); + +typedef +void +(*BinderExtCallFunc)( + BinderExtCall* ext, + void* user_data); + +typedef +void +(*BinderExtCallSuppSvcNotifyFunc)( + BinderExtCall* ext, + const BinderExtCallSuppSvcNotify* ssn, + void* user_data); + +typedef +void +(*BinderExtCallDisconnectFunc)( + BinderExtCall* ext, + guint call_id, + BINDER_EXT_CALL_DISCONNECT_REASON reason, + void* user_data); + +GType binder_ext_call_get_type(void); +#define BINDER_EXT_TYPE_CALL (binder_ext_call_get_type()) +#define BINDER_EXT_CALL(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + BINDER_EXT_TYPE_CALL, BinderExtCall)) + +BinderExtCall* +binder_ext_call_ref( + BinderExtCall* ext); + +void +binder_ext_call_unref( + BinderExtCall* ext); + +BINDER_EXT_CALL_INTERFACE_FLAGS +binder_ext_call_get_interface_flags( + BinderExtCall* ext); + +const BinderExtCallInfo* const* +binder_ext_call_get_calls( + BinderExtCall* ext); + +guint +binder_ext_call_dial( + BinderExtCall* ext, + const char* number, + BINDER_EXT_TOA toa, + BINDER_EXT_CALL_CLIR clir, + BINDER_EXT_CALL_DIAL_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data); + +guint +binder_ext_call_deflect( + BinderExtCall* ext, + const char* number, + BINDER_EXT_TOA toa, + BINDER_EXT_CALL_DEFLECT_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data); + +guint +binder_ext_call_answer( + BinderExtCall* ext, + BINDER_EXT_CALL_ANSWER_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data); + +guint +binder_ext_call_swap( + BinderExtCall* ext, + BINDER_EXT_CALL_SWAP_FLAGS swap_flags, + BINDER_EXT_CALL_ANSWER_FLAGS answer_flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data); + +guint +binder_ext_call_transfer( + BinderExtCall* ext, + BINDER_EXT_CALL_TRANSFER_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data); + +guint +binder_ext_call_conference( + BinderExtCall* ext, + BINDER_EXT_CALL_CONFERENCE_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data); + +guint +binder_ext_call_send_dtmf( + BinderExtCall* ext, + const char* tones, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data); + +guint +binder_ext_call_hangup( + BinderExtCall* ext, + guint call_id, + BINDER_EXT_CALL_HANGUP_REASON reason, + BINDER_EXT_CALL_HANGUP_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data); + +void +binder_ext_call_cancel( + BinderExtCall* ext, + guint id); + +gulong +binder_ext_call_add_calls_changed_handler( + BinderExtCall* ext, + BinderExtCallFunc handler, + void* user_data); + +gulong +binder_ext_call_add_disconnect_handler( + BinderExtCall* ext, + BinderExtCallDisconnectFunc handler, + void* user_data); + +gulong +binder_ext_call_add_ring_handler( + BinderExtCall* ext, + BinderExtCallFunc handler, + void* user_data); + +gulong +binder_ext_call_add_ssn_handler( + BinderExtCall* ext, + BinderExtCallSuppSvcNotifyFunc handler, + void* user_data); + +void +binder_ext_call_remove_handler( + BinderExtCall* ext, + gulong id); + +void +binder_ext_call_remove_handlers( + BinderExtCall* ext, + gulong* ids, + guint count); + +#define binder_ext_call_remove_all_handlers(ext, ids) \ + binder_ext_call_remove_handlers(ext, ids, G_N_ELEMENTS(ids)) + +G_END_DECLS + +#endif /* BINDER_EXT_CALL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/lib/include/binder_ext_call_impl.h b/lib/include/binder_ext_call_impl.h new file mode 100644 index 0000000..a06f601 --- /dev/null +++ b/lib/include/binder_ext_call_impl.h @@ -0,0 +1,110 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + */ + +#ifndef BINDER_EXT_CALL_IMPL_H +#define BINDER_EXT_CALL_IMPL_H + +#include "binder_ext_call.h" + +/* Internal API for use by BinderExtCall implemenations */ + +G_BEGIN_DECLS + +#define BINDER_EXT_CALL_INTERFACE_VERSION 1 + +/* + * Implementation sets field to BINDER_EXT_CALL_INTERFACE_VERSION. + * + * It allows more callbacks to be added to BinderExtCallInterface + * in the future. If the vtable gets extended, the version field will be + * used to figure out the size of the (possibly smaller) vtable which + * the implementation was compiled against. + * + * But let's hope that we will never run out of the reserved fields. + */ +typedef struct binder_ext_call_interface { + GTypeInterface parent; + BINDER_EXT_CALL_INTERFACE_FLAGS flags; + int version; + + const BinderExtCallInfo* const* (*get_calls)(BinderExtCall* ext); + guint (*dial)(BinderExtCall* ext, const char* number, BINDER_EXT_TOA toa, + BINDER_EXT_CALL_CLIR clir, BINDER_EXT_CALL_DIAL_FLAGS flags, + BinderExtCallResultFunc complete, GDestroyNotify destroy, + void* user_data); + guint (*deflect)(BinderExtCall* ext, const char* number, BINDER_EXT_TOA toa, + BINDER_EXT_CALL_DEFLECT_FLAGS flags, + BinderExtCallResultFunc complete, GDestroyNotify destroy, + void* user_data); + guint (*answer)(BinderExtCall* ext, BINDER_EXT_CALL_ANSWER_FLAGS flags, + BinderExtCallResultFunc complete, GDestroyNotify destroy, + void* user_data); + guint (*swap)(BinderExtCall* ext, BINDER_EXT_CALL_SWAP_FLAGS swap_flags, + BINDER_EXT_CALL_ANSWER_FLAGS answer_flags, + BinderExtCallResultFunc complete, GDestroyNotify destroy, + void* user_data); + guint (*transfer)(BinderExtCall* ext, BINDER_EXT_CALL_TRANSFER_FLAGS flags, + BinderExtCallResultFunc complete, GDestroyNotify destroy, + void* user_data); + guint (*conference)(BinderExtCall* ext, + BINDER_EXT_CALL_CONFERENCE_FLAGS flags, + BinderExtCallResultFunc complete, GDestroyNotify destroy, + void* user_data); + guint (*send_dtmf)(BinderExtCall* ext, const char* tones, + BinderExtCallResultFunc complete, GDestroyNotify destroy, + void* user_data); + guint (*hangup)(BinderExtCall* ext, guint call_id, + BINDER_EXT_CALL_HANGUP_REASON reason, + BINDER_EXT_CALL_HANGUP_FLAGS flags, + BinderExtCallResultFunc complete, GDestroyNotify destroy, + void* user_data); + void (*cancel)(BinderExtCall* ext, guint id); + gulong (*add_calls_changed_handler)(BinderExtCall* ext, + BinderExtCallFunc handler, void* user_data); + gulong (*add_disconnect_handler)(BinderExtCall* ext, + BinderExtCallDisconnectFunc handler, void* user_data); + gulong (*add_ring_handler)(BinderExtCall* ext, + BinderExtCallFunc handler, void* user_data); + gulong (*add_ssn_handler)(BinderExtCall* ext, + BinderExtCallSuppSvcNotifyFunc handler, void* user_data); + void (*remove_handler)(BinderExtCall* ext, gulong id); + + /* Padding for future expansion */ + void (*_reserved1)(void); + void (*_reserved2)(void); + void (*_reserved3)(void); + void (*_reserved4)(void); + void (*_reserved5)(void); + void (*_reserved6)(void); + void (*_reserved7)(void); + void (*_reserved8)(void); + void (*_reserved9)(void); + void (*_reserved10)(void); +} BinderExtCallInterface; + +#define BINDER_EXT_CALL_GET_IFACE(obj) G_TYPE_INSTANCE_GET_INTERFACE(obj,\ + BINDER_EXT_TYPE_CALL, BinderExtCallInterface) + +G_END_DECLS + +#endif /* BINDER_EXT_CALL_IMPL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/lib/include/binder_ext_types.h b/lib/include/binder_ext_types.h index 2e27b9e..5b6f6e1 100644 --- a/lib/include/binder_ext_types.h +++ b/lib/include/binder_ext_types.h @@ -20,9 +20,17 @@ G_BEGIN_DECLS -typedef struct binder_ext_sms BinderExtSms; +typedef struct binder_ext_call BinderExtCall; typedef struct binder_ext_plugin BinderExtPlugin; typedef struct binder_ext_slot BinderExtSlot; +typedef struct binder_ext_sms BinderExtSms; + +/* Type of address, 3GPP TS 24.008 subclause 10.5.4.7 */ +typedef enum binder_ext_toa { + BINDER_EXT_TOA_LOCAL = 129, + BINDER_EXT_TOA_INTERNATIONAL = 145, + BINDER_EXT_TOA_UNKNOWN = 0x7fffffff +} BINDER_EXT_TOA; G_END_DECLS diff --git a/lib/src/binder_ext_call.c b/lib/src/binder_ext_call.c new file mode 100644 index 0000000..32d8937 --- /dev/null +++ b/lib/src/binder_ext_call.c @@ -0,0 +1,380 @@ +/* + * oFono - Open Source Telephony - binder based adaptation + * + * Copyright (C) 2022 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + */ + +#include "binder_ext_call_impl.h" + +G_DEFINE_INTERFACE(BinderExtCall, binder_ext_call, G_TYPE_OBJECT) +#define GET_IFACE(obj) BINDER_EXT_CALL_GET_IFACE(obj) + +/*==========================================================================* + * API + *==========================================================================*/ + +BinderExtCall* +binder_ext_call_ref( + BinderExtCall* self) +{ + if (G_LIKELY(self)) { + g_object_ref(self); + } + return self; +} + +void +binder_ext_call_unref( + BinderExtCall* self) +{ + if (G_LIKELY(self)) { + g_object_unref(self); + } +} + +BINDER_EXT_CALL_INTERFACE_FLAGS +binder_ext_call_get_interface_flags( + BinderExtCall* self) +{ + return G_LIKELY(self) ? GET_IFACE(self)->flags : + BINDER_EXT_CALL_INTERFACE_NO_FLAGS; +} + +const BinderExtCallInfo* const* +binder_ext_call_get_calls( + BinderExtCall* self) +{ + static const BinderExtCallInfo* none = NULL; + + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->get_calls) { + const BinderExtCallInfo* const* calls = iface->get_calls(self); + + if (calls) { + return calls; + } + } + } + + /* This function never returns NULL */ + return &none; +} + +guint +binder_ext_call_dial( + BinderExtCall* self, + const char* number, + BINDER_EXT_TOA toa, + BINDER_EXT_CALL_CLIR clir, + BINDER_EXT_CALL_DIAL_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(self) && G_LIKELY(number)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->dial) { + return iface->dial(self, number, toa, clir, flags, complete, + destroy, user_data); + } + } + return 0; +} + +guint +binder_ext_call_deflect( + BinderExtCall* self, + const char* number, + BINDER_EXT_TOA toa, + BINDER_EXT_CALL_DEFLECT_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(self) && G_LIKELY(number)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->deflect) { + return iface->deflect(self, number, toa, flags, complete, + destroy, user_data); + } + } + return 0; +} + +guint +binder_ext_call_answer( + BinderExtCall* self, + BINDER_EXT_CALL_ANSWER_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->answer) { + return iface->answer(self, flags, complete, destroy, data); + } + } + return 0; +} + +guint +binder_ext_call_swap( + BinderExtCall* self, + BINDER_EXT_CALL_SWAP_FLAGS swap_flags, + BINDER_EXT_CALL_ANSWER_FLAGS answer_flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->swap) { + return iface->swap(self, swap_flags, answer_flags, complete, + destroy, data); + } + } + return 0; +} + +guint +binder_ext_call_transfer( + BinderExtCall* self, + BINDER_EXT_CALL_TRANSFER_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->transfer) { + return iface->transfer(self, flags, complete, destroy, user_data); + } + } + return 0; +} + +guint +binder_ext_call_conference( + BinderExtCall* self, + BINDER_EXT_CALL_CONFERENCE_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->conference) { + return iface->conference(self, flags, complete, destroy, user_data); + } + } + return 0; +} + +guint +binder_ext_call_send_dtmf( + BinderExtCall* self, + const char* tones, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(self) && G_LIKELY(tones)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->send_dtmf) { + return iface->send_dtmf(self, tones, complete, destroy, user_data); + } + } + return 0; +} + +guint +binder_ext_call_hangup( + BinderExtCall* self, + guint call_id, + BINDER_EXT_CALL_HANGUP_REASON reason, + BINDER_EXT_CALL_HANGUP_FLAGS flags, + BinderExtCallResultFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->hangup) { + return iface->hangup(self, call_id, reason, flags, complete, + destroy, user_data); + } + } + return 0; +} + +void +binder_ext_call_cancel( + BinderExtCall* self, + guint id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->cancel) { + iface->cancel(self, id); + } + } +} + +gulong +binder_ext_call_add_calls_changed_handler( + BinderExtCall* self, + BinderExtCallFunc handler, + void* user_data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->add_calls_changed_handler) { + iface->add_calls_changed_handler(self, handler, user_data); + } + } + return 0; +} + +gulong +binder_ext_call_add_disconnect_handler( + BinderExtCall* self, + BinderExtCallDisconnectFunc handler, + void* user_data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->add_disconnect_handler) { + iface->add_disconnect_handler(self, handler, user_data); + } + } + return 0; +} + +gulong +binder_ext_call_add_ring_handler( + BinderExtCall* self, + BinderExtCallFunc handler, + void* user_data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->add_ring_handler) { + iface->add_ring_handler(self, handler, user_data); + } + } + return 0; +} + +gulong +binder_ext_call_add_ssn_handler( + BinderExtCall* self, + BinderExtCallSuppSvcNotifyFunc handler, + void* user_data) +{ + if (G_LIKELY(self)) { + BinderExtCallInterface* iface = GET_IFACE(self); + + if (iface->add_ssn_handler) { + iface->add_ssn_handler(self, handler, user_data); + } + } + return 0; +} + +void +binder_ext_call_remove_handler( + BinderExtCall* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + /* + * Since we provide the default callback, we can safely assume + * that remove_handler is always there. + */ + GET_IFACE(self)->remove_handler(self, id); + } +} + +void +binder_ext_call_remove_handlers( + BinderExtCall* self, + gulong* ids, + guint count) +{ + if (G_LIKELY(self) && G_LIKELY(ids) && G_LIKELY(count)) { + BinderExtCallInterface* iface = GET_IFACE(self); + int i; + + /* + * Since we provide the default callback, we can safely assume + * that remove_handler is always there. + */ + for (i = 0; i < count; i++) { + if (ids[i]) { + iface->remove_handler(self, ids[i]); + ids[i] = 0; + } + } + } +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +binder_ext_call_default_remove_handler( + BinderExtCall* self, + gulong id) +{ + if (id) { + g_signal_handler_disconnect(self, id); + } +} + +static +void +binder_ext_call_default_init( + BinderExtCallInterface* iface) +{ + /* + * Assume the smallest interface version. Implementation must overwrite + * iface->version with BINDER_EXT_CALL_INTERFACE_VERSION known to it at + * the compile time. + */ + iface->version = 1; + iface->remove_handler = binder_ext_call_default_remove_handler; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/binder_ims.c b/src/binder_ims.c index 220a5e6..3c8772b 100644 --- a/src/binder_ims.c +++ b/src/binder_ims.c @@ -19,6 +19,7 @@ #include "binder_modem.h" #include "binder_util.h" +#include "binder_ext_call.h" #include "binder_ext_slot.h" #include "binder_ext_sms.h" @@ -114,10 +115,17 @@ binder_ims_probe( if (binder_ext_sms_get_interface_flags (binder_ext_slot_get_interface(modem->ext, BINDER_EXT_TYPE_SMS)) & BINDER_EXT_SMS_INTERFACE_FLAG_IMS_SUPPORT) { - DBG_(self, "ims sms seems to be supported"); + DBG_(self, "ims sms support is detected"); self->caps |= OFONO_IMS_SMS_CAPABLE; } + if (binder_ext_call_get_interface_flags + (binder_ext_slot_get_interface(modem->ext, BINDER_EXT_TYPE_CALL)) & + BINDER_EXT_CALL_INTERFACE_FLAG_IMS_SUPPORT) { + DBG_(self, "ims call support is detected"); + self->caps |= OFONO_IMS_VOICE_CAPABLE; + } + self->start_id = g_idle_add(binder_ims_start, self); ofono_ims_set_data(ims, self); return 0; diff --git a/src/binder_voicecall.c b/src/binder_voicecall.c index 57af4ec..c35caa0 100644 --- a/src/binder_voicecall.c +++ b/src/binder_voicecall.c @@ -15,9 +15,13 @@ #include "binder_log.h" #include "binder_modem.h" +#include "binder_ims_reg.h" #include "binder_util.h" #include "binder_voicecall.h" +#include "binder_ext_slot.h" +#include "binder_ext_call.h" + #include #include #include @@ -25,6 +29,7 @@ #include #include #include +#include #include #include @@ -45,10 +50,19 @@ enum binder_voicecall_events { VOICECALL_EVENT_COUNT }; +enum binder_ext_events { + VOICECALL_EXT_CALL_STATE_CHANGED, + VOICECALL_EXT_CALL_DISCONNECTED, + VOICECALL_EXT_CALL_SUPP_SVC_NOTIFICATION, + VOICECALL_EXT_EVENT_COUNT +}; + typedef struct binder_voicecall { struct ofono_voicecall* vc; char* log_prefix; GSList* calls; + BinderExtCall* ext; + BinderImsReg* ims_reg; RadioRequestGroup* g; ofono_voicecall_cb_t cb; void* data; @@ -59,16 +73,18 @@ typedef struct binder_voicecall { GUtilInts* remote_hangup_reasons; RadioRequest* send_dtmf_req; RadioRequest* clcc_poll_req; - gulong event_id[VOICECALL_EVENT_COUNT]; + guint ext_req_id; + gulong ext_event[VOICECALL_EXT_EVENT_COUNT]; + gulong radio_event[VOICECALL_EVENT_COUNT]; gulong supp_svc_notification_id; gulong ringback_tone_event_id; } BinderVoiceCall; -typedef struct binder_voicecall_request_data { +typedef struct binder_voicecall_cb_data { int ref_count; int pending_call_count; int success; - struct ofono_voicecall* vc; + BinderVoiceCall* self; ofono_voicecall_cb_t cb; gpointer data; } BinderVoiceCallCbData; @@ -78,6 +94,13 @@ typedef struct binder_voicecall_lastcause_data { guint cid; } BinderVoiceCallLastCauseData; +typedef struct binder_voicecall_info { + struct ofono_call oc; + BinderExtCall* ext; /* Not a ref */ +} BinderVoiceCallInfo; + +#define ANSWER_FLAGS BINDER_EXT_CALL_ANSWER_NO_FLAGS + #define DBG_(self,fmt,args...) DBG("%s" fmt, (self)->log_prefix, ##args) #define DBG__(vc,fmt,args...) \ DBG("%s" fmt, binder_voicecall_get_data(vc)->log_prefix, ##args) @@ -98,15 +121,15 @@ binder_voicecall_get_data(struct ofono_voicecall* vc) static BinderVoiceCallCbData* -binder_voicecall_request_data_new( - struct ofono_voicecall* vc, +binder_voicecall_cbd_new( + BinderVoiceCall* self, ofono_voicecall_cb_t cb, void* data) { BinderVoiceCallCbData* req = g_slice_new0(BinderVoiceCallCbData); req->ref_count = 1; - req->vc = vc; + req->self = self; req->cb = cb; req->data = data; return req; @@ -114,22 +137,14 @@ binder_voicecall_request_data_new( static void -binder_voicecall_request_data_unref( - BinderVoiceCallCbData* req) +binder_voicecall_cbd_unref( + BinderVoiceCallCbData* cbd) { - if (!--req->ref_count) { - gutil_slice_free(req); + if (!--cbd->ref_count) { + gutil_slice_free(cbd); } } -static -void -binder_voicecall_request_data_free( - gpointer data) -{ - binder_voicecall_request_data_unref(data); -} - static void binder_voicecall_clear_dtmf_queue( @@ -140,72 +155,243 @@ binder_voicecall_clear_dtmf_queue( self->send_dtmf_req = NULL; } +static +enum ofono_call_mode +binder_voicecall_ext_call_state_to_ofono( + BINDER_EXT_CALL_STATE state) +{ + switch (state) { + case BINDER_EXT_CALL_STATE_INVALID: + break; + case BINDER_EXT_CALL_STATE_ACTIVE: + return OFONO_CALL_STATUS_ACTIVE; + case BINDER_EXT_CALL_STATE_HOLDING: + return OFONO_CALL_STATUS_HELD; + case BINDER_EXT_CALL_STATE_DIALING: + return OFONO_CALL_STATUS_DIALING; + case BINDER_EXT_CALL_STATE_ALERTING: + return OFONO_CALL_STATUS_ALERTING; + case BINDER_EXT_CALL_STATE_INCOMING: + return OFONO_CALL_STATUS_INCOMING; + case BINDER_EXT_CALL_STATE_WAITING: + return OFONO_CALL_STATUS_WAITING; + } + DBG("unexpected call state %d", state); + return OFONO_CALL_STATUS_DISCONNECTED; +} + +static +gboolean +binder_voicecall_ofono_call_equal( + const struct ofono_call* c1, + const struct ofono_call* c2) +{ + return c1->id == c2->id && + c1->type == c2->type && + c1->direction == c2->direction && + c1->status == c2->status && + c1->phone_number.type == c2->phone_number.type && + c1->called_number.type == c2->called_number.type && + c1->clip_validity == c2->clip_validity && + c1->cnap_validity == c2->cnap_validity && + !strcmp(c1->phone_number.number, c1->phone_number.number) && + !strcmp(c1->called_number.number, c1->called_number.number); +} + static gint -binder_voicecall_compare( +binder_voicecall_info_compare( gconstpointer a, gconstpointer b) { - const struct ofono_call* ca = a; - const struct ofono_call* cb = b; + const guint ca = ((const BinderVoiceCallInfo*)a)->oc.id; + const guint cb = ((const BinderVoiceCallInfo*)b)->oc.id; - return (ca->id < cb->id) ? -1 : - (ca->id > cb->id) ? 1 : 0; + return (ca < cb) ? -1 : (ca > cb) ? 1 : 0; } static void -binder_voicecall_ofono_call_free( +binder_voicecall_info_free( gpointer data) { - g_slice_free(struct ofono_call, data); + g_slice_free(BinderVoiceCallInfo, data); } static -struct ofono_call* -binder_voicecall_ofono_call_new( +BinderVoiceCallInfo* +binder_voicecall_info_new( const RadioCall* rc) { - struct ofono_call* call = g_slice_new0(struct ofono_call); + BinderVoiceCallInfo* call = g_slice_new0(BinderVoiceCallInfo); + struct ofono_call* oc = &call->oc; - ofono_call_init(call); + ofono_call_init(oc); - call->status = rc->state; - call->id = rc->index; - call->direction = rc->isMT ? + oc->status = rc->state; + oc->id = rc->index; + oc->direction = rc->isMT ? OFONO_CALL_DIRECTION_MOBILE_TERMINATED : OFONO_CALL_DIRECTION_MOBILE_ORIGINATED; - call->type = rc->isVoice ? + oc->type = rc->isVoice ? OFONO_CALL_MODE_VOICE : OFONO_CALL_MODE_UNKNOWN; if (rc->name.len) { - g_strlcpy(call->name, rc->name.data.str, OFONO_MAX_CALLER_NAME_LENGTH); + g_strlcpy(oc->name, rc->name.data.str, OFONO_MAX_CALLER_NAME_LENGTH); } - call->phone_number.type = rc->toa; + oc->phone_number.type = rc->toa; if (rc->number.len) { - call->clip_validity = OFONO_CLIP_VALIDITY_VALID; - g_strlcpy(call->phone_number.number, rc->number.data.str, - OFONO_MAX_PHONE_NUMBER_LENGTH); + oc->clip_validity = OFONO_CLIP_VALIDITY_VALID; + g_strlcpy(oc->phone_number.number, rc->number.data.str, + OFONO_MAX_PHONE_NUMBER_LENGTH); } else { - call->clip_validity = OFONO_CLIP_VALIDITY_NOT_AVAILABLE; + oc->clip_validity = OFONO_CLIP_VALIDITY_NOT_AVAILABLE; } - DBG("[id=%d,status=%d,type=%d,number=%s,name=%s]", call->id, - call->status, call->type, call->phone_number.number, call->name); + DBG("[id=%d,status=%d,type=%d,number=%s,name=%s]", oc->id, + oc->status, oc->type, oc->phone_number.number, oc->name); return call; } +static +BinderVoiceCallInfo* +binder_voicecall_info_ext_new( + const BinderExtCallInfo* ci, + BinderExtCall* ext) +{ + BinderVoiceCallInfo* call = g_slice_new0(BinderVoiceCallInfo); + struct ofono_call* oc = &call->oc; + + ofono_call_init(oc); + + oc->status = binder_voicecall_ext_call_state_to_ofono(ci->state); + oc->id = ci->call_id; + oc->direction = (ci->flags & BINDER_EXT_CALL_FLAG_INCOMING) ? + OFONO_CALL_DIRECTION_MOBILE_TERMINATED : + OFONO_CALL_DIRECTION_MOBILE_ORIGINATED; + oc->type = (ci->type == BINDER_EXT_CALL_TYPE_VOICE) ? + OFONO_CALL_MODE_VOICE : + OFONO_CALL_MODE_UNKNOWN; + if (ci->name) { + g_strlcpy(oc->name, ci->name, OFONO_MAX_CALLER_NAME_LENGTH); + } + oc->phone_number.type = ci->toa; + if (ci->number && ci->number[0]) { + oc->clip_validity = OFONO_CLIP_VALIDITY_VALID; + g_strlcpy(oc->phone_number.number, ci->number, + OFONO_MAX_PHONE_NUMBER_LENGTH); + } else { + oc->clip_validity = OFONO_CLIP_VALIDITY_NOT_AVAILABLE; + } + + DBG("[id=%d,status=%d,type=%d,number=%s,name=%s]", oc->id, + oc->status, oc->type, oc->phone_number.number, oc->name); + + call->ext = ext; + return call; +} + +static +gboolean +binder_voicecall_have_ext_call( + BinderVoiceCall* self) +{ + if (self->ext) { + GSList* l; + + for (l = self->calls; l; l = l->next) { + const BinderVoiceCallInfo* call = l->data; + + if (call->ext) { + return TRUE; + } + } + } + return FALSE; +} + +static +const BinderVoiceCallInfo* +binder_voicecall_find_call_with_status( + BinderVoiceCall* self, + enum ofono_call_status status) +{ + GSList* l; + + /* + * Normally, the list is either empty or very short, there's + * nothing to optimize. + */ + for (l = self->calls; l; l = l->next) { + const BinderVoiceCallInfo* call = l->data; + + if (call->oc.status == status) { + return call; + } + } + return NULL; +} + +static +GSList* +binder_voicecall_find_call_link_with_id( + BinderVoiceCall* self, + guint call_id) +{ + GSList* l; + + /* + * Normally, the list is either empty or very short, there's + * nothing to optimize. + */ + for (l = self->calls; l; l = l->next) { + const BinderVoiceCallInfo* call = l->data; + + if (call->oc.id == call_id) { + return l; + } + } + return NULL; +} + +static +const BinderVoiceCallInfo* +binder_voicecall_find_call_with_id( + BinderVoiceCall* self, + unsigned int call_id) +{ + GSList* l = binder_voicecall_find_call_link_with_id(self, call_id); + + return l ? l->data : NULL; +} + static enum ofono_call_status binder_voicecall_status_with_id( - struct ofono_voicecall *vc, - unsigned int id) + BinderVoiceCall* self, + unsigned int call_id) { - struct ofono_call *call = ofono_voicecall_find_call(vc, id); + const BinderVoiceCallInfo* call = + binder_voicecall_find_call_with_id(self, call_id); /* Valid call statuses have value >= 0 */ - return call ? call->status : -1; + return call ? call->oc.status : -1; +} + +static +void +binder_voicecall_remove_call_id( + BinderVoiceCall* self, + guint call_id) +{ + GSList* l = binder_voicecall_find_call_link_with_id(self, call_id); + + if (l) { + DBG_(self, "removed call %u", call_id); + binder_voicecall_info_free(l->data); + self->calls = g_slist_delete_link(self->calls, l); + } } static @@ -219,7 +405,7 @@ binder_voicecall_map_cause( DBG_(self, "hangup cause %d => remote hangup", last_cause); return OFONO_DISCONNECT_REASON_REMOTE_HANGUP; } else if (gutil_ints_contains(self->local_hangup_reasons, last_cause)) { - DBG("hangup cause %d => local hangup", last_cause); + DBG_(self, "hangup cause %d => local hangup", last_cause); return OFONO_DISCONNECT_REASON_LOCAL_HANGUP; } else { enum ofono_call_status call_status; @@ -242,7 +428,7 @@ binder_voicecall_map_cause( return OFONO_DISCONNECT_REASON_REMOTE_HANGUP; case RADIO_LAST_CALL_FAIL_NORMAL_UNSPECIFIED: - call_status = binder_voicecall_status_with_id(self->vc, cid); + call_status = binder_voicecall_status_with_id(self, cid); if (call_status == OFONO_CALL_STATUS_ACTIVE || call_status == OFONO_CALL_STATUS_HELD || call_status == OFONO_CALL_STATUS_DIALING || @@ -254,7 +440,7 @@ binder_voicecall_map_cause( break; case RADIO_LAST_CALL_FAIL_ERROR_UNSPECIFIED: - call_status = binder_voicecall_status_with_id(self->vc, cid); + call_status = binder_voicecall_status_with_id(self, cid); if (call_status == OFONO_CALL_STATUS_DIALING || call_status == OFONO_CALL_STATUS_ALERTING || call_status == OFONO_CALL_STATUS_INCOMING) { @@ -319,6 +505,77 @@ binder_voicecall_lastcause_cb( ofono_voicecall_disconnected(vc, cid, OFONO_DISCONNECT_REASON_ERROR, NULL); } +static +void +binder_voicecall_set_calls( + BinderVoiceCall* self, + GSList* list) +{ + struct ofono_voicecall* vc = self->vc; + GSList* n = list; + GSList* o = self->calls; + + /* Note: the lists are sorted by id */ + while (n || o) { + const BinderVoiceCallInfo* nc = n ? n->data : NULL; + const BinderVoiceCallInfo* oc = o ? o->data : NULL; + + if (oc && (!nc || (nc->oc.id > oc->oc.id))) { + const guint id = oc->oc.id; + + /* old call is gone */ + if (gutil_int_array_remove_all_fast(self->local_release_ids, id)) { + ofono_voicecall_disconnected(vc, id, + OFONO_DISCONNECT_REASON_LOCAL_HANGUP, NULL); + } else { + /* Get disconnect cause before informing oFono core */ + BinderVoiceCallLastCauseData* reqdata = + g_new0(BinderVoiceCallLastCauseData, 1); + /* getLastCallFailCause(int32 serial); */ + RadioRequest* req2 = radio_request_new2(self->g, + RADIO_REQ_GET_LAST_CALL_FAIL_CAUSE, NULL, + binder_voicecall_lastcause_cb, g_free, reqdata); + + reqdata->self = self; + reqdata->cid = id; + radio_request_submit(req2); + radio_request_unref(req2); + } + + binder_voicecall_clear_dtmf_queue(self); + o = o->next; + + } else if (nc && (!oc || (nc->oc.id < oc->oc.id))) { + /* new call, signal it */ + if (nc->oc.type == OFONO_CALL_MODE_VOICE) { + ofono_voicecall_notify(vc, &nc->oc); + if (self->cb) { + ofono_voicecall_cb_t cb = self->cb; + void* cbdata = self->data; + struct ofono_error err; + + self->cb = NULL; + self->data = NULL; + cb(binder_error_ok(&err), cbdata); + } + } + + n = n->next; + + } else { + /* Both old and new call exist */ + if (!binder_voicecall_ofono_call_equal(&nc->oc, &oc->oc)) { + ofono_voicecall_notify(vc, &nc->oc); + } + n = n->next; + o = o->next; + } + } + + g_slist_free_full(self->calls, binder_voicecall_info_free); + self->calls = list; +} + static void binder_voicecall_clcc_poll_cb( @@ -330,11 +587,7 @@ binder_voicecall_clcc_poll_cb( gpointer user_data) { BinderVoiceCall* self = user_data; - struct ofono_voicecall* vc = self->vc; - struct ofono_error err; GSList* list = NULL; - GSList* n; - GSList* o; GASSERT(self->clcc_poll_req == req); radio_request_unref(self->clcc_poll_req); @@ -356,8 +609,8 @@ binder_voicecall_clcc_poll_cb( /* Build sorted list */ for (i = 0; i < count; i++) { list = g_slist_insert_sorted(list, - binder_voicecall_ofono_call_new(calls + i), - binder_voicecall_compare); + binder_voicecall_info_new(calls + i), + binder_voicecall_info_compare); } } } else if (resp == RADIO_RESP_GET_CURRENT_CALLS_1_2) { @@ -369,8 +622,8 @@ binder_voicecall_clcc_poll_cb( /* Build sorted list */ for (i = 0; i < count; i++) { list = g_slist_insert_sorted(list, - binder_voicecall_ofono_call_new(&calls[i].base), - binder_voicecall_compare); + binder_voicecall_info_new(&calls[i].base), + binder_voicecall_info_compare); } } } else { @@ -386,67 +639,9 @@ binder_voicecall_clcc_poll_cb( } } - /* Note: the lists are sorted by id */ +#pragma message("TODO: Merge call lists?") - n = list; - o = self->calls; - - while (n || o) { - struct ofono_call* nc = n ? n->data : NULL; - struct ofono_call* oc = o ? o->data : NULL; - - if (oc && (!nc || (nc->id > oc->id))) { - /* old call is gone */ - if (gutil_int_array_remove_all_fast(self->local_release_ids, - oc->id)) { - ofono_voicecall_disconnected(vc, oc->id, - OFONO_DISCONNECT_REASON_LOCAL_HANGUP, NULL); - } else { - /* Get disconnect cause before informing oFono core */ - BinderVoiceCallLastCauseData* reqdata = - g_new0(BinderVoiceCallLastCauseData, 1); - /* getLastCallFailCause(int32 serial); */ - RadioRequest* req2 = radio_request_new2(self->g, - RADIO_REQ_GET_LAST_CALL_FAIL_CAUSE, NULL, - binder_voicecall_lastcause_cb, g_free, reqdata); - - reqdata->self = self; - reqdata->cid = oc->id; - radio_request_submit(req2); - radio_request_unref(req2); - } - - binder_voicecall_clear_dtmf_queue(self); - o = o->next; - - } else if (nc && (!oc || (nc->id < oc->id))) { - /* new call, signal it */ - if (nc->type == OFONO_CALL_MODE_VOICE) { - ofono_voicecall_notify(vc, nc); - if (self->cb) { - ofono_voicecall_cb_t cb = self->cb; - void* cbdata = self->data; - - self->cb = NULL; - self->data = NULL; - cb(binder_error_ok(&err), cbdata); - } - } - - n = n->next; - - } else { - /* Both old and new call exist */ - if (memcmp(nc, oc, sizeof(*nc))) { - ofono_voicecall_notify(vc, nc); - } - n = n->next; - o = o->next; - } - } - - g_slist_free_full(self->calls, binder_voicecall_ofono_call_free); - self->calls = list; + binder_voicecall_set_calls(self, list); } static @@ -478,7 +673,7 @@ binder_voicecall_clcc_poll( { if (!self->clcc_poll_req) { /* getCurrentCalls(int32 serial); */ - RadioRequest* req = radio_request_new(self->g->client, + RadioRequest* req = radio_request_new2(self->g, RADIO_REQ_GET_CURRENT_CALLS, NULL, binder_voicecall_clcc_poll_cb, NULL, self); @@ -494,7 +689,56 @@ binder_voicecall_clcc_poll( static void -binder_voicecall_request_cb( +binder_voicecall_cbd_destroy( + gpointer cbd) +{ + binder_voicecall_cbd_unref((BinderVoiceCallCbData*) cbd); +} + +static +void +binder_voicecall_request_submitted( + BinderVoiceCallCbData* cbd) +{ + cbd->ref_count++; + cbd->pending_call_count++; +} + +static +void +binder_voicecall_request_completed( + BinderVoiceCallCbData* cbd, + gboolean ok) +{ + /* + * The ofono API call is considered successful if at least one + * associated request succeeds. + */ + if (ok) { + cbd->success++; + } + + /* + * Only invoke the callback if this is the last request associated + * with this ofono api call (pending call count becomes zero). + */ + GASSERT(cbd->pending_call_count > 0); + if (!--cbd->pending_call_count && cbd->cb) { + struct ofono_error err; + + if (cbd->success) { + binder_error_init_ok(&err); + } else { + binder_error_init_failure(&err); + } + + cbd->cb(&err, cbd->data); + } +} + +static +void +binder_voicecall_cbd_complete( RadioRequest* req, RADIO_TX_STATUS status, RADIO_RESP resp, @@ -503,54 +747,40 @@ binder_voicecall_request_cb( gpointer user_data) { BinderVoiceCallCbData* data = user_data; - BinderVoiceCall* self = binder_voicecall_get_data(data->vc); - binder_voicecall_clcc_poll(self); - - /* - * The ofono API call is considered successful if at least one - * associated request succeeds. - */ - if (status == RADIO_TX_STATUS_OK && error == RADIO_ERROR_NONE) { - data->success++; - } - - /* - * Only invoke the callback if this is the last request associated - * with this ofono api call (pending call count becomes zero). - */ - GASSERT(data->pending_call_count > 0); - if (!--data->pending_call_count && data->cb) { - struct ofono_error error; - - if (data->success) { - binder_error_init_ok(&error); - } else { - binder_error_init_failure(&error); - } - - data->cb(&error, data->data); - } + binder_voicecall_clcc_poll(data->self); + binder_voicecall_request_completed(data, status == RADIO_TX_STATUS_OK && + error == RADIO_ERROR_NONE); } static void -binder_voicecall_request( - struct ofono_voicecall* vc, - RADIO_REQ code, - ofono_voicecall_cb_t cb, - void* data) +binder_voicecall_cbd_ext_complete( + BinderExtCall* ext, + BINDER_EXT_CALL_RESULT result, + void* user_data) { - BinderVoiceCall* self = binder_voicecall_get_data(vc); - BinderVoiceCallCbData* cbd = - binder_voicecall_request_data_new(vc, cb, data); - RadioRequest* req = radio_request_new2(self->g, code, NULL, - binder_voicecall_request_cb, binder_voicecall_request_data_free, cbd); + BinderVoiceCallCbData* data = user_data; + data->self->ext_req_id = 0; + binder_voicecall_request_completed(data, + result == BINDER_EXT_CALL_RESULT_OK); +} + +static +void +binder_voicecall_request_submit( + BinderVoiceCall* self, + RADIO_REQ code, + BinderVoiceCallCbData* cbd) +{ + RadioRequest* req = radio_request_new2(self->g, code, NULL, + binder_voicecall_cbd_complete, + binder_voicecall_cbd_destroy, cbd); + + /* Request data will be unref'ed when the request is done */ if (radio_request_submit(req)) { - cbd->pending_call_count++; - } else { - binder_voicecall_request_data_unref(cbd); + binder_voicecall_request_submitted(cbd); } radio_request_unref(req); } @@ -602,6 +832,57 @@ binder_voicecall_dial_cb( } } +static +void +binder_voicecall_ext_dial_cb( + BinderExtCall* ext, + BINDER_EXT_CALL_RESULT result, + void* user_data) +{ + BinderVoiceCall* self = user_data; + + self->ext_req_id = 0; + if (self->cb) { + struct ofono_error err; + ofono_voicecall_cb_t cb = self->cb; + void* cbdata = self->data; + + self->cb = NULL; + self->data = NULL; + if (result == BINDER_EXT_CALL_RESULT_OK) { + cb(binder_error_failure(&err), cbdata); + } else { + cb(binder_error_ok(&err), cbdata); + } + } +} + +static +gboolean +binder_voicecall_can_ext_dial( + BinderVoiceCall* self) +{ + return self->ext && (!(binder_ext_call_get_interface_flags + (self->ext) & BINDER_EXT_CALL_INTERFACE_FLAG_IMS_REQUIRED) || + (self->ims_reg && self->ims_reg->registered)); +} + +static +BINDER_EXT_CALL_CLIR +binder_voicecall_ext_clir( + enum ofono_clir_option clir) +{ + switch (clir) { + case OFONO_CLIR_OPTION_INVOCATION: + return BINDER_EXT_CALL_CLIR_INVOCATION; + case OFONO_CLIR_OPTION_SUPPRESSION: + return BINDER_EXT_CALL_CLIR_SUPPRESSION; + case OFONO_CLIR_OPTION_DEFAULT: + break; + } + return BINDER_EXT_CALL_CLIR_DEFAULT; +} + static void binder_voicecall_dial( @@ -616,17 +897,29 @@ binder_voicecall_dial( const char* phstr = ofono_phone_number_to_string(ph, phbuf); GBinderParent parent; RadioDial* dialInfo; - - /* dial(int32 serial, Dial dialInfo) */ GBinderWriter writer; - RadioRequest* req = radio_request_new2(self->g, RADIO_REQ_DIAL, &writer, - binder_voicecall_dial_cb, NULL, self); + RadioRequest* req; ofono_info("dialing \"%s\"", phstr); DBG_(self, "%s,%d,0", phstr, clir); - GASSERT(!self->cb); - self->cb = cb; - self->data = data; + + binder_ext_call_cancel(self->ext, self->ext_req_id); + if (binder_voicecall_can_ext_dial(self)) { + self->ext_req_id = binder_ext_call_dial(self->ext, phstr, ph->type, + binder_voicecall_ext_clir(clir), BINDER_EXT_CALL_DIAL_FLAGS_NONE, + binder_voicecall_ext_dial_cb, NULL, self); + if (self->ext_req_id) { + self->cb = cb; + self->data = data; + return; + } + } else { + self->ext_req_id = 0; + } + + /* dial(int32 serial, Dial dialInfo) */ + req = radio_request_new2(self->g, RADIO_REQ_DIAL, &writer, + binder_voicecall_dial_cb, NULL, self); /* Prepare the Dial structure */ dialInfo = gbinder_writer_new0(&writer, RadioDial); @@ -645,7 +938,14 @@ binder_voicecall_dial( gbinder_writer_append_buffer_object_with_parent(&writer, NULL, 0, &parent); /* Submit the request */ - radio_request_submit(req); + if (radio_request_submit(req)) { + self->cb = cb; + self->data = data; + } else { + struct ofono_error err; + + cb(binder_error_failure(&err), data); + } radio_request_unref(req); } @@ -661,7 +961,7 @@ binder_voicecall_submit_hangup_req( /* hangup(int32 serial, int32 index) */ GBinderWriter writer; RadioRequest* req = radio_request_new2(self->g, RADIO_REQ_HANGUP, &writer, - binder_voicecall_request_cb, binder_voicecall_request_data_free, cbd); + binder_voicecall_cbd_complete, binder_voicecall_cbd_destroy, cbd); gbinder_writer_append_int32(&writer, cid); @@ -669,65 +969,38 @@ binder_voicecall_submit_hangup_req( GASSERT(!gutil_int_array_contains(self->local_release_ids, cid)); gutil_int_array_append(self->local_release_ids, cid); - /* binder_voicecall_request_data_free will unref the request data */ + /* Request data will be unref'ed when the request is done */ if (radio_request_submit(req)) { - cbd->ref_count++; - cbd->pending_call_count++; + binder_voicecall_request_submitted(cbd); } radio_request_unref(req); } static -void -binder_voicecall_hangup( - struct ofono_voicecall* vc, - gboolean (*filter)(struct ofono_call* call), - ofono_voicecall_cb_t cb, - void* data) +BINDER_EXT_CALL_DISCONNECT_REASON +binder_voicecall_ext_disconnect_reason( + const BinderVoiceCallInfo* call) { - BinderVoiceCall* self = binder_voicecall_get_data(vc); - BinderVoiceCallCbData* cbd = NULL; - GSList *l; - - /* - * Here the idea is that we submit (potentially) multiple - * hangup requests to BINDER and invoke the callback after - * the last request has completed (pending call count - * becomes zero). - */ - for (l = self->calls; l; l = l->next) { - struct ofono_call* call = l->data; - - if (!filter || filter(call)) { - if (!cbd) { - cbd = binder_voicecall_request_data_new(vc, cb, data); - } - - /* Send request to BINDER */ - DBG("Hanging up call with id %d", call->id); - binder_voicecall_submit_hangup_req(vc, call->id, cbd); - } else { - DBG("Skipping call with id %d", call->id); - } - } - - if (cbd) { - /* Release our reference (if any) */ - binder_voicecall_request_data_unref(cbd); - } else { - /* No requests were submitted */ - struct ofono_error err; - - cb(binder_error_ok(&err), data); + switch (call->oc.status) { + case OFONO_CALL_STATUS_INCOMING: + case OFONO_CALL_STATUS_WAITING: + return BINDER_EXT_CALL_HANGUP_REJECT; + case OFONO_CALL_STATUS_ACTIVE: + case OFONO_CALL_STATUS_HELD: + case OFONO_CALL_STATUS_DIALING: + case OFONO_CALL_STATUS_ALERTING: + case OFONO_CALL_STATUS_DISCONNECTED: + break; } + return BINDER_EXT_CALL_HANGUP_TERMINATE; } static gboolean -binder_voicecall_hangup_active_filter( - struct ofono_call *call) +binder_voicecall_hangup_filter_active( + const BinderVoiceCallInfo* call) { - switch (call->status) { + switch (call->oc.status) { case OFONO_CALL_STATUS_ACTIVE: case OFONO_CALL_STATUS_DIALING: case OFONO_CALL_STATUS_ALERTING: @@ -741,6 +1014,100 @@ binder_voicecall_hangup_active_filter( return FALSE; } +static +gboolean +binder_voicecall_hangup_filter_incoming_waiting( + const BinderVoiceCallInfo* call) +{ + switch (call->oc.status) { + case OFONO_CALL_STATUS_INCOMING: + case OFONO_CALL_STATUS_WAITING: + return TRUE; + case OFONO_CALL_STATUS_ACTIVE: + case OFONO_CALL_STATUS_DIALING: + case OFONO_CALL_STATUS_ALERTING: + case OFONO_CALL_STATUS_HELD: + case OFONO_CALL_STATUS_DISCONNECTED: + break; + } + return FALSE; +} + +static +gboolean +binder_voicecall_hangup_filter_held( + const BinderVoiceCallInfo* call) +{ + switch (call->oc.status) { + case OFONO_CALL_STATUS_WAITING: + case OFONO_CALL_STATUS_HELD: + return TRUE; + case OFONO_CALL_STATUS_INCOMING: + case OFONO_CALL_STATUS_ACTIVE: + case OFONO_CALL_STATUS_DIALING: + case OFONO_CALL_STATUS_ALERTING: + case OFONO_CALL_STATUS_DISCONNECTED: + break; + } + return FALSE; +} + +static +void +binder_voicecall_hangup( + struct ofono_voicecall* vc, + gboolean (*filter)(const BinderVoiceCallInfo* call), + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = NULL; + GSList* l; + + /* + * The idea is that we submit (potentially) multiple hangup + * requests and invoke the callback after the last request + * has completed (pending call count becomes zero). + */ + for (l = self->calls; l; l = l->next) { + const BinderVoiceCallInfo* call = l->data; + const guint id = call->oc.id; + + if (!filter || filter(call)) { + if (!cbd) { + cbd = binder_voicecall_cbd_new(self, cb, data); + } + + /* Send request to the modem */ + if (call->ext) { + DBG_(self, "hanging up ext call id %u", id); + if (binder_ext_call_hangup(call->ext, id, + BINDER_EXT_CALL_HANGUP_NO_FLAGS, + binder_voicecall_ext_disconnect_reason(call), + binder_voicecall_cbd_ext_complete, + binder_voicecall_cbd_destroy, cbd)) { + binder_voicecall_request_submitted(cbd); + continue; + } + } + DBG_(self, "hanging up call with id %u", id); + binder_voicecall_submit_hangup_req(vc, id, cbd); + } else { + DBG_(self, "Skipping call with id %u", id); + } + } + + if (cbd) { + /* Release our reference (if any) */ + binder_voicecall_cbd_unref(cbd); + } else { + /* No requests were submitted */ + struct ofono_error err; + + cb(binder_error_ok(&err), data); + } +} + static void binder_voicecall_hangup_active( @@ -749,7 +1116,7 @@ binder_voicecall_hangup_active( void* data) { binder_voicecall_hangup(vc, - binder_voicecall_hangup_active_filter, cb, data); + binder_voicecall_hangup_filter_active, cb, data); } static @@ -770,12 +1137,34 @@ binder_voicecall_release_specific( ofono_voicecall_cb_t cb, void* data) { - BinderVoiceCallCbData* req = - binder_voicecall_request_data_new(vc, cb, data); + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = binder_voicecall_cbd_new(self, cb, data); + const BinderVoiceCallInfo* call = + binder_voicecall_find_call_with_id(self, id); - DBG("Hanging up call with id %d", id); - binder_voicecall_submit_hangup_req(vc, id, req); - binder_voicecall_request_data_unref(req); + if (call) { + if (call->ext) { + DBG_(self, "hanging up ext call with id %u", id); + if (binder_ext_call_hangup(call->ext, id, + BINDER_EXT_CALL_HANGUP_NO_FLAGS, + binder_voicecall_hangup_filter_incoming_waiting(call) ? + BINDER_EXT_CALL_HANGUP_REJECT : + BINDER_EXT_CALL_HANGUP_TERMINATE, + binder_voicecall_cbd_ext_complete, + binder_voicecall_cbd_destroy, cbd)) { + binder_voicecall_request_submitted(cbd); + return; + } + } + DBG_(self, "hanging up call with id %d", id); + binder_voicecall_submit_hangup_req(vc, id, cbd); + binder_voicecall_cbd_unref(cbd); + } else if (cb) { + struct ofono_error err; + + DBG_(self, "call id %d not found", id); + cb(binder_error_failure(&err), data); + } } static @@ -796,51 +1185,119 @@ binder_voicecall_call_state_changed_event( static void -binder_voicecall_supp_svc_notification_event( +binder_voicecall_ext_supp_svc_notification( + BinderExtCall* ext, + const BinderExtCallSuppSvcNotify* ssn, + void* user_data) +{ + BinderVoiceCall* self = user_data; + + if (ssn->mt) { + struct ofono_phone_number ph; + + /* MT unsolicited result code */ + DBG_(self, "MT code: %d, index: %d type: %d number: %s", ssn->code, + ssn->index, ssn->type, ssn->number); + + if (ssn->number && ssn->number[0]) { + ph.type = ssn->type; + g_strlcpy(ph.number, ssn->number, sizeof(ph.number)); + } else { + ph.type = OFONO_NUMBER_TYPE_UNKNOWN; + ph.number[0] = 0; + } + ofono_voicecall_ssn_mt_notify(self->vc, 0, ssn->code, ssn->index, &ph); + } else { + /* MO intermediate result code */ + DBG_(self, "MO code: %d, index: %d", ssn->code, ssn->index); + ofono_voicecall_ssn_mo_notify(self->vc, 0, ssn->code, ssn->index); + } +} + +static +void +binder_voicecall_supp_svc_notification( RadioClient* client, RADIO_IND code, const GBinderReader* args, gpointer user_data) { BinderVoiceCall* self = user_data; - const RadioSuppSvcNotification* ntf; + const RadioSuppSvcNotification* ssn; GBinderReader reader; gbinder_reader_copy(&reader, args); - ntf = gbinder_reader_read_hidl_struct(&reader, RadioSuppSvcNotification); - if (ntf) { - DBG_(self, "MT/MO: %d, code: %d, index: %d", ntf->isMT, - ntf->code, ntf->index); - - if (ntf->isMT) { - struct ofono_phone_number phone; - - if (ntf->number.data.str) { - g_strlcpy(phone.number, ntf->number.data.str, - sizeof(phone.number)); - } else { - phone.number[0] = 0; - } + ssn = gbinder_reader_read_hidl_struct(&reader, RadioSuppSvcNotification); + if (ssn) { + if (ssn->isMT) { + struct ofono_phone_number ph; /* MT unsolicited result code */ - ofono_voicecall_ssn_mt_notify(self->vc, 0, ntf->code, ntf->index, - &phone); + DBG_(self, "MT code: %d, index: %d type: %d number: %s", ssn->code, + ssn->index, ssn->type, ssn->number.data.str); + if (ssn->number.data.str && ssn->number.len) { + ph.type = ssn->type; + g_strlcpy(ph.number, ssn->number.data.str, sizeof(ph.number)); + } else { + ph.type = OFONO_NUMBER_TYPE_UNKNOWN; + ph.number[0] = 0; + } + + ofono_voicecall_ssn_mt_notify(self->vc, 0, ssn->code, + ssn->index, &ph); } else { /* MO intermediate result code */ - ofono_voicecall_ssn_mo_notify(self->vc, 0, ntf->code, ntf->index); + ofono_voicecall_ssn_mo_notify(self->vc, 0, ssn->code, ssn->index); } } } +static +gboolean +binder_voicecall_ext_answer( + BinderVoiceCall* self, + BinderVoiceCallCbData* cbd) +{ + if (self->ext) { + binder_ext_call_cancel(self->ext, self->ext_req_id); + self->ext_req_id = binder_ext_call_answer(self->ext, ANSWER_FLAGS, + binder_voicecall_cbd_ext_complete, + binder_voicecall_cbd_destroy, cbd); + if (self->ext_req_id) { + binder_voicecall_request_submitted(cbd); + return TRUE; + } + } + return FALSE; +} + static void binder_voicecall_answer( - struct ofono_voicecall *vc, + struct ofono_voicecall* vc, ofono_voicecall_cb_t cb, void* data) { - DBG__(vc, "Answering current call"); - binder_voicecall_request(vc, RADIO_REQ_ACCEPT_CALL, cb, data); + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = binder_voicecall_cbd_new(self, cb, data); + const BinderVoiceCallInfo* call = + binder_voicecall_find_call_with_status(self, + OFONO_CALL_STATUS_INCOMING); + + if (call && call->ext) { + DBG_(self, "answering ext call"); + if (!binder_voicecall_ext_answer(self, cbd)) { + struct ofono_error err; + + DBG_(self, "failed to answer ext call"); + cb(binder_error_failure(&err), data); + } + } else { + /* Default action */ + DBG_(self, "answering current call"); + binder_voicecall_request_submit(self, RADIO_REQ_ACCEPT_CALL, cbd); + } + binder_voicecall_cbd_unref(cbd); } static @@ -884,7 +1341,7 @@ binder_voicecall_send_one_dtmf( if (!self->send_dtmf_req && gutil_ring_size(self->dtmf_queue) > 0) { /* sendDtmf(int32 serial, string s) */ GBinderWriter writer; - RadioRequest* req = radio_request_new(self->g->client, + RadioRequest* req = radio_request_new2(self->g, RADIO_REQ_SEND_DTMF, &writer, binder_voicecall_send_dtmf_cb, NULL, self); char dtmf_str[2]; @@ -918,7 +1375,7 @@ binder_voicecall_send_dtmf( * Queue any incoming DTMF, send them to BINDER one-by-one, * immediately call back core with no error */ - DBG("Queue '%s'", dtmf); + DBG_(self, "queue '%s'", dtmf); while (*dtmf) { gutil_ring_put(self->dtmf_queue, GUINT_TO_POINTER(*dtmf)); dtmf++; @@ -928,6 +1385,26 @@ binder_voicecall_send_dtmf( cb(binder_error_ok(&err), data); } +static +gboolean +binder_voicecall_ext_conference( + BinderVoiceCall* self, + BinderVoiceCallCbData* cbd) +{ + if (self->ext) { + binder_ext_call_cancel(self->ext, self->ext_req_id); + self->ext_req_id = binder_ext_call_conference(self->ext, + BINDER_EXT_CALL_CONFERENCE_FLAGS_NONE, + binder_voicecall_cbd_ext_complete, + binder_voicecall_cbd_destroy, cbd); + if (self->ext_req_id) { + binder_voicecall_request_submitted(cbd); + return TRUE; + } + } + return FALSE; +} + static void binder_voicecall_create_multiparty( @@ -935,8 +1412,41 @@ binder_voicecall_create_multiparty( ofono_voicecall_cb_t cb, void* data) { - DBG__(vc, ""); - binder_voicecall_request(vc, RADIO_REQ_CONFERENCE, cb, data); + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = binder_voicecall_cbd_new(self, cb, data); + const RADIO_REQ req = RADIO_REQ_CONFERENCE; + + if (binder_voicecall_have_ext_call(self)) { + if (!binder_voicecall_ext_conference(self, cbd)) { + DBG_(self, "(fallback)"); + binder_voicecall_request_submit(self, req, cbd); + } + } else { + /* Default action */ + DBG_(self, ""); + binder_voicecall_request_submit(self, req, cbd); + } + binder_voicecall_cbd_unref(cbd); +} + +static +gboolean +binder_voicecall_ext_transfer( + BinderVoiceCall* self, + BinderVoiceCallCbData* cbd) +{ + if (self->ext) { + binder_ext_call_cancel(self->ext, self->ext_req_id); + self->ext_req_id = binder_ext_call_transfer(self->ext, + BINDER_EXT_CALL_TRANSFER_FLAGS_NONE, + binder_voicecall_cbd_ext_complete, + binder_voicecall_cbd_destroy, cbd); + if (self->ext_req_id) { + binder_voicecall_request_submitted(cbd); + return TRUE; + } + } + return FALSE; } static @@ -946,8 +1456,21 @@ binder_voicecall_transfer( ofono_voicecall_cb_t cb, void* data) { - DBG__(vc, ""); - binder_voicecall_request(vc, RADIO_REQ_EXPLICIT_CALL_TRANSFER, cb, data); + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = binder_voicecall_cbd_new(self, cb, data); + const RADIO_REQ req = RADIO_REQ_EXPLICIT_CALL_TRANSFER; + + if (binder_voicecall_have_ext_call(self)) { + if (!binder_voicecall_ext_transfer(self, cbd)) { + DBG_(self, "(fallback)"); + binder_voicecall_request_submit(self, req, cbd); + } + } else { + /* Default action */ + DBG_(self, ""); + binder_voicecall_request_submit(self, req, cbd); + } + binder_voicecall_cbd_unref(cbd); } static @@ -959,24 +1482,67 @@ binder_voicecall_private_chat( void* data) { BinderVoiceCall* self = binder_voicecall_get_data(vc); - BinderVoiceCallCbData* cbd = - binder_voicecall_request_data_new(vc, cb, data); + BinderVoiceCallCbData* cbd = binder_voicecall_cbd_new(self, cb, data); /* separateConnection(int32 serial, int32 index) */ GBinderWriter writer; RadioRequest* req = radio_request_new2(self->g, RADIO_REQ_SEPARATE_CONNECTION, &writer, - binder_voicecall_request_cb, binder_voicecall_request_data_free, cbd); + binder_voicecall_cbd_complete, binder_voicecall_cbd_destroy, cbd); DBG_(self, "Private chat with id %d", cid); gbinder_writer_append_int32(&writer, cid); if (radio_request_submit(req)) { - cbd->ref_count++; - cbd->pending_call_count++; + binder_voicecall_request_submitted(cbd); } radio_request_unref(req); } +static +gboolean +binder_voicecall_ext_swap( + BinderVoiceCall* self, + BinderVoiceCallCbData* cbd, + BINDER_EXT_CALL_SWAP_FLAGS swap_flags) +{ + if (self->ext) { + binder_ext_call_cancel(self->ext, self->ext_req_id); + self->ext_req_id = binder_ext_call_swap(self->ext, swap_flags, + ANSWER_FLAGS, binder_voicecall_cbd_ext_complete, + binder_voicecall_cbd_destroy, cbd); + if (self->ext_req_id) { + binder_voicecall_request_submitted(cbd); + return TRUE; + } + } + return FALSE; +} + +static +void +binder_voicecall_swap_with_fallback( + struct ofono_voicecall* vc, + BINDER_EXT_CALL_SWAP_FLAGS flags, + RADIO_REQ fallback, + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = binder_voicecall_cbd_new(self, cb, data); + + if (binder_voicecall_have_ext_call(self)) { + if (!binder_voicecall_ext_swap(self, cbd, flags)) { + DBG_(self, "%s (fallback)", radio_req_name(fallback)); + binder_voicecall_request_submit(self, fallback, cbd); + } + } else { + /* Default action */ + DBG_(self, "%s", radio_req_name(fallback)); + binder_voicecall_request_submit(self, fallback, cbd); + } + binder_voicecall_cbd_unref(cbd); +} + static void binder_voicecall_swap_without_accept( @@ -984,11 +1550,89 @@ binder_voicecall_swap_without_accept( ofono_voicecall_cb_t cb, void* data) { - DBG__(vc, ""); - binder_voicecall_request(vc, + binder_voicecall_swap_with_fallback(vc, BINDER_EXT_CALL_SWAP_NO_FLAGS, RADIO_REQ_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, cb, data); } +static +void +binder_voicecall_hold_all_active( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + binder_voicecall_swap_with_fallback(vc, BINDER_EXT_CALL_SWAP_NO_FLAGS, + RADIO_REQ_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, cb, data); +} + +static +void +binder_voicecall_release_all_active( + struct ofono_voicecall* vc, + ofono_voicecall_cb_t cb, + void* data) +{ + binder_voicecall_swap_with_fallback(vc, BINDER_EXT_CALL_SWAP_FLAG_HANGUP, + RADIO_REQ_HANGUP_FOREGROUND_RESUME_BACKGROUND, cb, data); +} + +static +void +binder_voicecall_hangup_with_fallback( + struct ofono_voicecall* vc, + gboolean (*filter)(const BinderVoiceCallInfo* call), + BINDER_EXT_CALL_HANGUP_REASON reason, + RADIO_REQ fallback, + ofono_voicecall_cb_t cb, + void* data) +{ + BinderVoiceCall* self = binder_voicecall_get_data(vc); + BinderVoiceCallCbData* cbd = binder_voicecall_cbd_new(self, cb, data); + + /* + * The idea is that we submit (potentially) multiple hangup + * requests and invoke the callback after the last request + * has completed (pending call count becomes zero). + */ + if (self->ext) { + gboolean use_fallback = FALSE; + GSList* l; + + for (l = self->calls; l; l = l->next) { + const BinderVoiceCallInfo* call = l->data; + + if (filter(call)) { + const guint id = call->oc.id; + + if (call->ext) { + DBG_(self, "hanging up ext call id %u", id); + if (binder_ext_call_hangup(call->ext, id, reason, + BINDER_EXT_CALL_HANGUP_NO_FLAGS, + binder_voicecall_cbd_ext_complete, + binder_voicecall_cbd_destroy, cbd)) { + binder_voicecall_request_submitted(cbd); + continue; + } + } else { + DBG_(self, "%s %u", radio_req_name(fallback), id); + } + + /* Request wasn't submitted - will use the fallback */ + use_fallback = TRUE; + } + } + + if (use_fallback || !cbd->pending_call_count) { + DBG_(self, "%s", radio_req_name(fallback)); + binder_voicecall_request_submit(self, fallback, cbd); + } + } else { + DBG_(self, "%s", radio_req_name(fallback)); + binder_voicecall_request_submit(self, fallback, cbd); + } + binder_voicecall_cbd_unref(cbd); +} + static void binder_voicecall_release_all_held( @@ -997,22 +1641,12 @@ binder_voicecall_release_all_held( void* data) { DBG__(vc, ""); - binder_voicecall_request(vc, + binder_voicecall_hangup_with_fallback(vc, + binder_voicecall_hangup_filter_held, + BINDER_EXT_CALL_HANGUP_REJECT, RADIO_REQ_HANGUP_WAITING_OR_BACKGROUND, cb, data); } -static -void -binder_voicecall_release_all_active( - struct ofono_voicecall* vc, - ofono_voicecall_cb_t cb, - void* data) -{ - DBG__(vc, ""); - binder_voicecall_request(vc, - RADIO_REQ_HANGUP_FOREGROUND_RESUME_BACKGROUND, cb, data); -} - static void binder_voicecall_set_udub( @@ -1021,7 +1655,10 @@ binder_voicecall_set_udub( void* data) { DBG__(vc, ""); - binder_voicecall_request(vc, RADIO_REQ_REJECT_CALL, cb, data); + binder_voicecall_hangup_with_fallback(vc, + binder_voicecall_hangup_filter_incoming_waiting, + BINDER_EXT_CALL_HANGUP_REJECT, + RADIO_REQ_REJECT_CALL, cb, data); } static @@ -1057,11 +1694,65 @@ binder_voicecall_ringback_tone_event( /* indicateRingbackTone(RadioIndicationType, bool start) */ gbinder_reader_copy(&reader, args); if (gbinder_reader_read_bool(&reader, &start)) { - DBG("play ringback tone: %d", start); + DBG_(self, "play ringback tone: %d", start); ofono_voicecall_ringback_tone_notify(self->vc, start); } } +static +void +binder_voicecall_ext_calls_changed( + BinderExtCall* ext, + void* user_data) +{ + BinderVoiceCall* self = user_data; + const BinderExtCallInfo* const* calls = binder_ext_call_get_calls(ext); + const BinderExtCallInfo* const* ptr = calls; + GSList* list = NULL; + + /* binder_ext_call_get_calls never returns NULL */ + while (*ptr) { + const BinderExtCallInfo* call = *ptr++; + + /* We don't report multiparty calls to the core */ + if (!(call->flags & BINDER_EXT_CALL_FLAG_MPTY)) { + list = g_slist_insert_sorted(list, + binder_voicecall_info_ext_new(call, ext), + binder_voicecall_info_compare); + } + } + +#pragma message("TODO: Merge call lists?") + + binder_voicecall_set_calls(self, list); +} + +static +void +binder_voicecall_ext_call_disconnected( + BinderExtCall* ext, + guint call_id, + BINDER_EXT_CALL_DISCONNECT_REASON reason, + void* user_data) +{ + BinderVoiceCall* self = user_data; + + if (binder_voicecall_find_call_link_with_id(self, call_id)) { + DBG_(self, "ext call %u disconnected", call_id); + binder_voicecall_remove_call_id(self, call_id); + binder_voicecall_clear_dtmf_queue(self); + ofono_voicecall_disconnected(self->vc, call_id, + gutil_int_array_remove_all_fast(self->local_release_ids, call_id) ? + OFONO_DISCONNECT_REASON_LOCAL_HANGUP : + (reason == BINDER_EXT_CALL_DISCONNECT_ERROR) ? + OFONO_DISCONNECT_REASON_ERROR : + OFONO_DISCONNECT_REASON_REMOTE_HANGUP, NULL); + } else { + /* We don't report multipart calls to the core */ + DBG_(self, "ignoring ext call %u hangup (mpty?)", call_id); + } +} + static void binder_voicecall_register( @@ -1079,23 +1770,34 @@ binder_voicecall_register( binder_voicecall_enable_supp_svc(self); /* Unsol when call state changes */ - self->event_id[VOICECALL_EVENT_CALL_STATE_CHANGED] = + self->radio_event[VOICECALL_EVENT_CALL_STATE_CHANGED] = radio_client_add_indication_handler(client, RADIO_IND_CALL_STATE_CHANGED, binder_voicecall_call_state_changed_event, self); /* Unsol when call set in hold */ - self->event_id[VOICECALL_EVENT_SUPP_SVC_NOTIFICATION] = + self->radio_event[VOICECALL_EVENT_SUPP_SVC_NOTIFICATION] = radio_client_add_indication_handler(client, RADIO_IND_SUPP_SVC_NOTIFY, - binder_voicecall_supp_svc_notification_event, self); + binder_voicecall_supp_svc_notification, self); /* Register for ringback tone notifications */ - self->event_id[VOICECALL_EVENT_RINGBACK_TONE] = + self->radio_event[VOICECALL_EVENT_RINGBACK_TONE] = radio_client_add_indication_handler(client, RADIO_IND_INDICATE_RINGBACK_TONE, binder_voicecall_ringback_tone_event, self); + if (self->ext) { + self->ext_event[VOICECALL_EXT_CALL_STATE_CHANGED] = + binder_ext_call_add_calls_changed_handler(self->ext, + binder_voicecall_ext_calls_changed, self); + self->ext_event[VOICECALL_EXT_CALL_DISCONNECTED] = + binder_ext_call_add_disconnect_handler(self->ext, + binder_voicecall_ext_call_disconnected, self); + self->ext_event[VOICECALL_EXT_CALL_SUPP_SVC_NOTIFICATION] = + binder_ext_call_add_ssn_handler(self->ext, + binder_voicecall_ext_supp_svc_notification, self); + } #pragma message("TODO: Set up ECC list watcher") } @@ -1113,13 +1815,21 @@ binder_voicecall_probe( self->log_prefix = binder_dup_prefix(modem->log_prefix); DBG_(self, ""); + self->vc = vc; self->dtmf_queue = gutil_ring_new(); self->g = radio_request_group_new(modem->client); /* Keeps ref to client */ self->local_hangup_reasons = gutil_ints_ref(cfg->local_hangup_reasons); self->remote_hangup_reasons = gutil_ints_ref(cfg->remote_hangup_reasons); self->local_release_ids = gutil_int_array_new(); self->idleq = gutil_idle_queue_new(); - self->vc = vc; + self->ims_reg = binder_ims_reg_ref(modem->ims); + + if (modem->ext && (self->ext = + binder_ext_slot_get_interface(modem->ext, + BINDER_EXT_TYPE_CALL)) != NULL) { + DBG_(self, "using call extension"); + binder_ext_call_ref(self->ext); + } binder_voicecall_clear_dtmf_queue(self); gutil_idle_queue_add(self->idleq, binder_voicecall_register, self); @@ -1134,12 +1844,12 @@ binder_voicecall_remove( { BinderVoiceCall* self = binder_voicecall_get_data(vc); - DBG(""); - g_slist_free_full(self->calls, binder_voicecall_ofono_call_free); + DBG_(self, ""); + g_slist_free_full(self->calls, binder_voicecall_info_free); radio_request_drop(self->send_dtmf_req); radio_request_drop(self->clcc_poll_req); - radio_client_remove_all_handlers(self->g->client, self->event_id); + radio_client_remove_all_handlers(self->g->client, self->radio_event); radio_request_group_cancel(self->g); radio_request_group_unref(self->g); @@ -1149,6 +1859,13 @@ binder_voicecall_remove( gutil_int_array_free(self->local_release_ids, TRUE); gutil_idle_queue_free(self->idleq); + if (self->ext) { + binder_ext_call_remove_all_handlers(self->ext, self->ext_event); + binder_ext_call_cancel(self->ext, self->ext_req_id); + binder_ext_call_unref(self->ext); + } + + binder_ims_reg_unref(self->ims_reg); g_free(self->log_prefix); g_free(self); @@ -1159,6 +1876,8 @@ binder_voicecall_remove( * API *==========================================================================*/ +#pragma message("TODO: Implement deflect callback?") + static const struct ofono_voicecall_driver binder_voicecall_driver = { .name = BINDER_DRIVER, .probe = binder_voicecall_probe, @@ -1167,15 +1886,16 @@ static const struct ofono_voicecall_driver binder_voicecall_driver = { .answer = binder_voicecall_answer, .hangup_active = binder_voicecall_hangup_active, .hangup_all = binder_voicecall_hangup_all, - .release_specific = binder_voicecall_release_specific, - .send_tones = binder_voicecall_send_dtmf, - .create_multiparty = binder_voicecall_create_multiparty, - .transfer = binder_voicecall_transfer, - .private_chat = binder_voicecall_private_chat, - .swap_without_accept = binder_voicecall_swap_without_accept, + .hold_all_active = binder_voicecall_hold_all_active, .release_all_held = binder_voicecall_release_all_held, .set_udub = binder_voicecall_set_udub, - .release_all_active = binder_voicecall_release_all_active + .release_all_active = binder_voicecall_release_all_active, + .release_specific = binder_voicecall_release_specific, + .private_chat = binder_voicecall_private_chat, + .create_multiparty = binder_voicecall_create_multiparty, + .transfer = binder_voicecall_transfer, + .swap_without_accept = binder_voicecall_swap_without_accept, + .send_tones = binder_voicecall_send_dtmf }; void