sig_analog: Add Call Waiting Deluxe support.

Adds support for Call Waiting Deluxe options to enhance
the current call waiting feature.

As part of this change, a mechanism is also added that
allows a channel driver to queue an audio file for Dial()
to play, which is necessary for the announcement function.

ASTERISK-30373 #close

Resolves: #271
This commit is contained in:
Naveen Albert 2023-08-24 14:07:06 +00:00
parent f5e704b9d1
commit 4356f16234
10 changed files with 294 additions and 0 deletions

View File

@ -1686,6 +1686,15 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
case AST_CONTROL_PVT_CAUSE_CODE:
ast_indicate_data(in, AST_CONTROL_PVT_CAUSE_CODE, f->data.ptr, f->datalen);
break;
case AST_CONTROL_PLAYBACK_BEGIN:
if (!f->data.ptr) {
ast_log(LOG_WARNING, "Got playback begin directive without filename on %s\n", ast_channel_name(c));
} else {
const char *filename = f->data.ptr;
ast_verb(3, "Playing audio file %s on %s\n", filename, ast_channel_name(in));
ast_streamfile(in, filename, ast_channel_language(in));
}
break;
case -1:
if (single && !caller_entertained) {
ast_verb(3, "%s stopped sounds\n", ast_channel_name(c));

View File

@ -9191,6 +9191,26 @@ static int dahdi_write(struct ast_channel *ast, struct ast_frame *frame)
return -1;
}
if (p->sig == SIG_FXOLS || p->sig == SIG_FXOKS || p->sig == SIG_FXOGS) {
struct analog_pvt *analog_p = p->sig_pvt;
if (analog_p->callwaitdeluxepending) {
unsigned int mssinceflash = ast_tvdiff_ms(ast_tvnow(), analog_p->flashtime);
if (mssinceflash >= 1000) {
/* Timer expired: the user hasn't yet selected an option. Take the default action and get on with it. */
/* Note: If in the future Advanced Call Waiting Deluxe (*76) is supported, then as part of the
* dialing code, we'll need to automatically invoke the preselected behavior about 2-3 seconds after
* the call waiting begins (this allows for the SAS, CAS, and CWCID spill to be sent first).
*/
analog_p->callwaitdeluxepending = 0;
analog_callwaiting_deluxe(analog_p, 0);
}
ast_mutex_unlock(&p->lock);
/* The user shouldn't hear anything after hook flashing, until a decision is made, by the user or when the timer expires. */
ast_debug(5, "Dropping frame since Call Waiting Deluxe pending on %s\n", ast_channel_name(ast));
return 0;
}
}
if (p->dialing) {
ast_mutex_unlock(&p->lock);
ast_debug(5, "Dropping frame since I'm still dialing on %s...\n",
@ -12947,6 +12967,7 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
tmp->usedistinctiveringdetection = usedistinctiveringdetection;
tmp->callwaitingcallerid = conf->chan.callwaitingcallerid;
tmp->callwaitingdeluxe = conf->chan.callwaitingdeluxe; /* Not used in DAHDI pvt, only analog pvt */
tmp->threewaycalling = conf->chan.threewaycalling;
tmp->threewaysilenthold = conf->chan.threewaysilenthold;
tmp->calledsubscriberheld = conf->chan.calledsubscriberheld; /* Not used in chan_dahdi.c, just analog pvt, but must exist on the DAHDI pvt anyways */
@ -13285,6 +13306,7 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
break;
}
analog_p->callwaitingcallerid = conf->chan.callwaitingcallerid;
analog_p->callwaitingdeluxe = conf->chan.callwaitingdeluxe;
analog_p->ringt = conf->chan.ringt;
analog_p->ringt_base = ringt_base;
analog_p->onhooktime = time(NULL);
@ -18413,6 +18435,8 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
confp->chan.callwaiting = ast_true(v->value);
} else if (!strcasecmp(v->name, "callwaitingcallerid")) {
confp->chan.callwaitingcallerid = ast_true(v->value);
} else if (!strcasecmp(v->name, "callwaitingdeluxe")) {
confp->chan.callwaitingdeluxe = ast_true(v->value);
} else if (!strcasecmp(v->name, "context")) {
ast_copy_string(confp->chan.context, v->value, sizeof(confp->chan.context));
} else if (!strcasecmp(v->name, "language")) {

View File

@ -229,6 +229,10 @@ struct dahdi_pvt {
* \note Set from the "callwaitingcallerid" value read in from chan_dahdi.conf
*/
unsigned int callwaitingcallerid:1;
/*!
* \brief TRUE if Call Waiting Deluxe options should be available
*/
unsigned int callwaitingdeluxe:1;
/*!
* \brief TRUE if support for call forwarding enabled.
* Dial *72 to enable call forwarding.

View File

@ -1447,6 +1447,7 @@ static int iax2_is_control_frame_allowed(int subtype)
case AST_CONTROL_STREAM_RESTART:
case AST_CONTROL_STREAM_REVERSE:
case AST_CONTROL_STREAM_FORWARD:
case AST_CONTROL_PLAYBACK_BEGIN: /* Only supported by app_dial currently */
/* None of these playback stream control frames should go across the link. */
case AST_CONTROL_RECORD_CANCEL:
case AST_CONTROL_RECORD_STOP:

View File

@ -1590,6 +1590,161 @@ static int analog_handles_digit(struct ast_frame *f)
}
}
enum callwaiting_deluxe_option {
CWD_CONFERENCE = '3',
CWD_HOLD = '6',
CWD_DROP = '7',
CWD_ANNOUNCEMENT = '8',
CWD_FORWARD = '9',
};
static const char *callwaiting_deluxe_optname(int option)
{
switch (option) {
case CWD_CONFERENCE:
return "CONFERENCE";
case CWD_HOLD:
return "HOLD";
case CWD_DROP:
return "DROP";
case CWD_ANNOUNCEMENT:
return "ANNOUNCEMENT";
case CWD_FORWARD:
return "FORWARD";
default:
return "DEFAULT";
}
}
int analog_callwaiting_deluxe(struct analog_pvt *p, int option)
{
const char *announce_var;
char announcement[PATH_MAX];
ast_debug(1, "Handling Call Waiting on channel %d with option %c: treatment %s\n", p->channel, option, callwaiting_deluxe_optname(option));
if (!p->subs[ANALOG_SUB_CALLWAIT].owner) {
/* This can happen if the caller hook flashes and the call waiting hangs up before the CWD timer expires (1 second) */
ast_debug(1, "Call waiting call disappeared before it could be handled?\n");
return -1;
}
analog_lock_sub_owner(p, ANALOG_SUB_CALLWAIT);
if (!p->subs[ANALOG_SUB_CALLWAIT].owner) {
ast_log(LOG_WARNING, "Whoa, the call-waiting call disappeared.\n");
return -1;
}
/* Note that when p->callwaitdeluxepending, dahdi_write will drop incoming frames to the channel,
* since the user shouldn't hear anything after flashing until either a DTMF has been received
* or it's been a second and the decision is made automatically. */
switch (option) {
case CWD_CONFERENCE:
/* We should never have a call waiting if we have a 3-way anyways, but check just in case,
* there better be no existing SUB_THREEWAY since we're going to make one (and then swap the call wait to it) */
if (p->subs[ANALOG_SUB_THREEWAY].owner) {
ast_channel_unlock(p->subs[ANALOG_SUB_CALLWAIT].owner);
ast_log(LOG_ERROR, "Already have a 3-way call on channel %d, can't conference!\n", p->channel);
return -1;
}
/* To conference the incoming call, swap it from SUB_CALLWAIT to SUB_THREEWAY,
* and then the existing 3-way logic will ensure that flashing again will drop the call waiting */
analog_alloc_sub(p, ANALOG_SUB_THREEWAY);
analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_CALLWAIT);
analog_unalloc_sub(p, ANALOG_SUB_CALLWAIT);
ast_verb(3, "Building conference call with %s and %s\n", ast_channel_name(p->subs[ANALOG_SUB_THREEWAY].owner), ast_channel_name(p->subs[ANALOG_SUB_REAL].owner));
analog_set_inthreeway(p, ANALOG_SUB_THREEWAY, 1);
analog_set_inthreeway(p, ANALOG_SUB_REAL, 1);
if (ast_channel_state(p->subs[ANALOG_SUB_THREEWAY].owner) == AST_STATE_RINGING) {
ast_setstate(p->subs[ANALOG_SUB_THREEWAY].owner, AST_STATE_UP);
ast_queue_control(p->subs[ANALOG_SUB_THREEWAY].owner, AST_CONTROL_ANSWER);
/* Stop the ringing on the call wait channel (yeah, apparently this is how it's done) */
ast_queue_hold(p->subs[ANALOG_SUB_THREEWAY].owner, p->mohsuggest);
ast_queue_unhold(p->subs[ANALOG_SUB_THREEWAY].owner);
}
analog_stop_callwait(p);
ast_channel_unlock(p->subs[ANALOG_SUB_THREEWAY].owner); /* Unlock what was originally SUB_CALLWAIT */
break;
case CWD_HOLD: /* The CI-7112 Visual Director sends "HOLD" for "Play hold message" rather than "ANNOUNCEMENT". For default behavior, nothing is actually sent. */
case CWD_ANNOUNCEMENT:
/* We can't just call ast_streamfile here, this thread isn't responsible for media on the call waiting channel.
* Indicate to the dialing channel in app_dial that it needs to play media.
*
* This is a lot easier than other ways of trying to send early media to the channel
* (such as every call from the core to dahdi_read, sending the channel one frame of the audio file, etc.)
*/
/* There's not a particularly good stock audio prompt to use here. The Pat Fleet library has some better
* ones but we want one that is also in the default Allison Smith library. "One moment please" works okay.
* Check if a variable containing the prompt to use was specified on the call waiting channel, and
* fall back to a reasonable default if not. */
/* The SUB_CALLWAIT channel is already locked here, no need to lock and unlock to get the variable. */
announce_var = pbx_builtin_getvar_helper(p->subs[ANALOG_SUB_CALLWAIT].owner, "CALLWAITDELUXEANNOUNCEMENT");
ast_copy_string(announcement, S_OR(announce_var, "one-moment-please"), sizeof(announcement));
ast_debug(2, "Call Waiting Deluxe announcement for %s: %s\n", ast_channel_name(p->subs[ANALOG_SUB_CALLWAIT].owner), announcement);
ast_channel_unlock(p->subs[ANALOG_SUB_CALLWAIT].owner);
/* Tell app_dial what file to play. */
ast_queue_control_data(p->subs[ANALOG_SUB_CALLWAIT].owner, AST_CONTROL_PLAYBACK_BEGIN, announcement, strlen(announcement) + 1);
/* Unlike all the other options, the call waiting is still active with this option,
* so we don't call analog_stop_callwait(p)
* The call waiting will continue to be here, and at some later point the user can flash again and choose a finalizing option
* (or even queue the announcement again... and again... and again...)
*/
break;
case CWD_FORWARD:
/* Go away, call waiting, call again some other day... */
analog_stop_callwait(p);
/* Can't use p->call_forward exten because that's for *72 forwarding, and sig_analog doesn't
* have a Busy/Don't Answer call forwarding exten internally, so let the dialplan deal with it.
* by sending the call to the 'f' extension.
*/
ast_channel_call_forward_set(p->subs[ANALOG_SUB_CALLWAIT].owner, "f");
ast_channel_unlock(p->subs[ANALOG_SUB_CALLWAIT].owner);
/* app_dial already has a verbose message for forwarding, so we don't really need one here also since that does the job */
break;
case CWD_DROP:
/* Fall through: logic is identical to hold, except we drop the original call right after we swap. */
default:
/* Swap to call-wait, same as with the non-deluxe call waiting handling. */
analog_swap_subs(p, ANALOG_SUB_REAL, ANALOG_SUB_CALLWAIT);
analog_play_tone(p, ANALOG_SUB_REAL, -1);
analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner);
ast_debug(1, "Making %s the new owner\n", ast_channel_name(p->owner));
if (ast_channel_state(p->subs[ANALOG_SUB_REAL].owner) == AST_STATE_RINGING) {
ast_setstate(p->subs[ANALOG_SUB_REAL].owner, AST_STATE_UP);
ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_ANSWER);
}
analog_stop_callwait(p);
if (option == CWD_DROP) {
/* Disconnect the previous call (the original call is now the SUB_CALLWAIT since we swapped above) */
ast_queue_hangup(p->subs[ANALOG_SUB_CALLWAIT].owner);
ast_verb(3, "Dropping original call and swapping to call waiting on %s\n", ast_channel_name(p->subs[ANALOG_SUB_REAL].owner));
} else {
/* Start music on hold if appropriate */
if (!p->subs[ANALOG_SUB_CALLWAIT].inthreeway) {
ast_queue_hold(p->subs[ANALOG_SUB_CALLWAIT].owner, p->mohsuggest);
}
ast_verb(3, "Holding original call and swapping to call waiting on %s\n", ast_channel_name(p->subs[ANALOG_SUB_REAL].owner));
}
/* Stop ringing on the incoming call */
ast_queue_hold(p->subs[ANALOG_SUB_REAL].owner, p->mohsuggest);
ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner);
/* Unlock the call-waiting call that we swapped to real-call. */
ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner);
}
analog_update_conf(p);
return 0;
}
void analog_handle_dtmf(struct analog_pvt *p, struct ast_channel *ast, enum analog_sub idx, struct ast_frame **dest)
{
struct ast_frame *f = *dest;
@ -1627,6 +1782,50 @@ void analog_handle_dtmf(struct analog_pvt *p, struct ast_channel *ast, enum anal
p->subs[idx].f.frametype = AST_FRAME_NULL;
p->subs[idx].f.subclass.integer = 0;
*dest = &p->subs[idx].f;
} else if (p->callwaitdeluxepending) {
if (f->frametype == AST_FRAME_DTMF_END) {
unsigned int mssinceflash = ast_tvdiff_ms(ast_tvnow(), p->flashtime);
p->callwaitdeluxepending = 0;
/* This is the case where a user explicitly took action (made a decision)
* for Call Waiting Deluxe.
* Because we already handled the hook flash, if the user doesn't do
* anything within a second, then we still need to eventually take
* the default action (swap) for the call waiting.
*
* dahdi_write will also drop audio if callwaitdeluxepending is set HIGH,
* and also check if flashtime hits 1000, in which case it will set the flag LOW and then take the
* default action, e.g. analog_callwaiting_deluxe(p, 0);
*/
/* Slightly less than 1000, so there's no chance of a race condition
* between do_monitor when it sees flashtime hitting 1000 and us. */
if (mssinceflash > 990) {
/* This was more than a second ago, clear the flag and process normally. */
/* Because another thread has to monitor channels with pending CWDs,
* in theory, we shouldn't need to check this here. */
ast_debug(1, "It's been %u ms since the last flash, this is not a Call Waiting Deluxe DTMF\n", mssinceflash);
analog_cb_handle_dtmf(p, ast, idx, dest);
return;
}
/* Okay, actually do something now. */
switch (f->subclass.integer) {
case CWD_CONFERENCE:
case CWD_HOLD:
case CWD_DROP:
case CWD_ANNOUNCEMENT:
case CWD_FORWARD:
ast_debug(1, "Got some DTMF, but it's for Call Waiting Deluxe: %c\n", f->subclass.integer);
analog_callwaiting_deluxe(p, f->subclass.integer);
break;
default:
ast_log(LOG_WARNING, "Invalid Call Waiting Deluxe option (%c), using default\n", f->subclass.integer);
analog_callwaiting_deluxe(p, 0);
}
}
p->subs[idx].f.frametype = AST_FRAME_NULL;
p->subs[idx].f.subclass.integer = 0;
*dest = &p->subs[idx].f;
} else {
analog_cb_handle_dtmf(p, ast, idx, dest);
}
@ -2967,6 +3166,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
break;
}
}
p->callwaitdeluxepending = 0;
ast_queue_control_data(ast, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size);
ast_channel_hangupcause_hash_set(ast, cause_code, data_size);
switch (p->sig) {
@ -3282,6 +3482,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
}
/* Remember last time we got a flash-hook */
gettimeofday(&p->flashtime, NULL);
p->callwaitdeluxepending = 0;
switch (mysig) {
case ANALOG_SIG_FXOLS:
case ANALOG_SIG_FXOGS:
@ -3310,6 +3511,20 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
goto winkflashdone;
}
/* If line has Call Waiting Deluxe, see what the user wants to do.
* Only do this if this is an as yet unanswered call waiting, not an existing, answered SUB_CALLWAIT. */
if (ast_channel_state(p->subs[ANALOG_SUB_CALLWAIT].owner) == AST_STATE_RINGING) {
if (p->callwaitingdeluxe) {
/* This thread cannot block, so just set the flag that we need
* to wait for a Call Waiting Deluxe option (or let it time out),
* and then we're done for now. */
ast_channel_unlock(p->subs[ANALOG_SUB_CALLWAIT].owner);
p->callwaitdeluxepending = 1;
ast_debug(1, "Deferring call waiting manipulation, waiting for Call Waiting Deluxe option from user\n");
goto winkflashdone;
}
}
/* Swap to call-wait */
analog_swap_subs(p, ANALOG_SUB_REAL, ANALOG_SUB_CALLWAIT);
analog_play_tone(p, ANALOG_SUB_REAL, -1);
@ -3844,6 +4059,7 @@ void *analog_handle_init_event(struct analog_pvt *i, int event)
res = analog_off_hook(i);
i->fxsoffhookstate = 1;
i->cshactive = 0;
i->callwaitdeluxepending = 0;
if (res && (errno == EBUSY)) {
break;
}

View File

@ -331,6 +331,7 @@ struct analog_pvt {
/* XXX: All variables after this are internal */
unsigned int callwaiting:1; /*!< TRUE if call waiting is enabled. (Active option) */
unsigned int callwaitingdeluxe:1; /*!< TRUE if Call Waiting Deluxe options are available */
unsigned int cshactive:1; /*!< TRUE if FXS channel is currently held by an incoming call */
unsigned int dialednone:1;
unsigned int dialing:1; /*!< TRUE if in the process of dialing digits or sending something */
@ -346,6 +347,12 @@ struct analog_pvt {
* gives a positive reply.
*/
unsigned int callwaitcas:1;
/*!
* \brief TRUE if a Call Waiting Deluxe action is currently pending.
*/
unsigned int callwaitdeluxepending:1;
unsigned int call_qualifier:1; /*!< Call qualifier delivery */
char callwait_num[AST_MAX_EXTENSION];
@ -397,6 +404,8 @@ void *analog_handle_init_event(struct analog_pvt *i, int event);
int analog_config_complete(struct analog_pvt *p);
int analog_callwaiting_deluxe(struct analog_pvt *p, int option);
void analog_handle_dtmf(struct analog_pvt *p, struct ast_channel *ast, enum analog_sub index, struct ast_frame **dest);
enum analog_cid_start analog_str_to_cidstart(const char *value);

View File

@ -685,6 +685,31 @@ usecallerid=yes
;
callwaiting=yes
;
; Whether or not to allow for Call Waiting Deluxe options to be used.
; The Call Waiting Deluxe options are:
; 3: Conference - conference the call waiting call with the existing call
; 7: Drop - drop current call and switch to new (same as hanging up and answering)
; 6/8: Announcement - play announcement to call waiting caller telling them to hold
; 9: Forward - forward the call waiting call to a preconfigured extension
; Doing nothing after 1 second triggers default behavior of holding the current call
; and switching to the new one.
;
; Some CPE (Caller ID units, screenphones, etc.) have dedicated buttons for utilizing
; the Call Waiting Deluxe features, but these can also be used from any phone manually
; by simply hook flashing as usual and then quickly dialing the appropriate DTMF digit.
; If no digit is received within 1 second, the default action is assumed,
; as if Call Waiting Deluxe was not used.
;
; The following extensions are also available in the dialplan to utilize this functionality:
; - The CALLWAITDELUXEANNOUNCEMENT variable can be set on the incoming channel to control the
; announcement prompt played to the call waiting caller. Default is "one-moment-please".
; - If the forward option is used, the call waiting channel will be forwarded to the 'f' extension
; in the channel's configured context. You can then use any dialplan mechanism to route the call.
;
; Default is 'no'.
;
;callwaitingdeluxe=yes
;
; Configure the number of outstanding call waiting calls for internal ISDN
; endpoints before bouncing the calls as busy. This option is equivalent to
; the callwaiting option for analog ports.

View File

@ -379,6 +379,9 @@ static void print_frame(struct ast_frame *frame)
case AST_CONTROL_RECORD_MUTE:
ast_verbose("SubClass: RECORD_MUTE\n");
break;
case AST_CONTROL_PLAYBACK_BEGIN:
ast_verbose("SubClass: PLAYBACK_BEGIN\n");
break;
}
if (frame->subclass.integer == -1) {

View File

@ -335,6 +335,7 @@ enum ast_control_frame_type {
AST_CONTROL_STREAM_RESTART = 1002, /*!< Indicate to a channel in playback to restart the stream */
AST_CONTROL_STREAM_REVERSE = 1003, /*!< Indicate to a channel in playback to rewind */
AST_CONTROL_STREAM_FORWARD = 1004, /*!< Indicate to a channel in playback to fast forward */
AST_CONTROL_PLAYBACK_BEGIN = 1005, /*!< Indicate to a dialing interface that playback of an audio file should begin on the dialing channel. Currently only supported by app_dial. */
/* Control frames to manipulate recording on a channel. */
AST_CONTROL_RECORD_CANCEL = 1100, /*!< Indicated to a channel in record to stop recording and discard the file */
AST_CONTROL_RECORD_STOP = 1101, /*!< Indicated to a channel in record to stop recording */

View File

@ -4317,6 +4317,7 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con
case AST_CONTROL_RECORD_STOP:
case AST_CONTROL_RECORD_SUSPEND:
case AST_CONTROL_RECORD_MUTE:
case AST_CONTROL_PLAYBACK_BEGIN:
break;
case AST_CONTROL_INCOMPLETE:
@ -4618,6 +4619,7 @@ static int indicate_data_internal(struct ast_channel *chan, int _condition, cons
case AST_CONTROL_RECORD_STOP:
case AST_CONTROL_RECORD_SUSPEND:
case AST_CONTROL_RECORD_MUTE:
case AST_CONTROL_PLAYBACK_BEGIN:
/* Nothing left to do for these. */
res = 0;
break;