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

UserNote: Call Waiting Deluxe can now be enabled for FXS channels
by enabling its corresponding option.
This commit is contained in:
Naveen Albert
2023-08-24 14:07:06 +00:00
committed by George Joseph
parent 9c58dec9b0
commit 6002b6fdcb
10 changed files with 294 additions and 0 deletions

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->callwaitingdeluxepending, 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->callwaitingdeluxepending) {
if (f->frametype == AST_FRAME_DTMF_END) {
unsigned int mssinceflash = ast_tvdiff_ms(ast_tvnow(), p->flashtime);
p->callwaitingdeluxepending = 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 callwaitingdeluxepending 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);
}
@@ -2991,6 +3190,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
break;
}
}
p->callwaitingdeluxepending = 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) {
@@ -3306,6 +3506,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->callwaitingdeluxepending = 0;
switch (mysig) {
case ANALOG_SIG_FXOLS:
case ANALOG_SIG_FXOGS:
@@ -3334,6 +3535,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->callwaitingdeluxepending = 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);
@@ -3868,6 +4083,7 @@ void *analog_handle_init_event(struct analog_pvt *i, int event)
res = analog_off_hook(i);
i->fxsoffhookstate = 1;
i->cshactive = 0;
i->callwaitingdeluxepending = 0;
if (res && (errno == EBUSY)) {
break;
}