Skip to content

Commit

Permalink
sig_analog: Add Call Waiting Deluxe support.
Browse files Browse the repository at this point in the history
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: asterisk#271
  • Loading branch information
InterLinked1 committed Oct 20, 2024
1 parent 97770a9 commit 2ad2a2a
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 0 deletions.
9 changes: 9 additions & 0 deletions apps/app_dial.c
Original file line number Diff line number Diff line change
Expand Up @@ -1722,6 +1722,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));
Expand Down
24 changes: 24 additions & 0 deletions channels/chan_dahdi.c
Original file line number Diff line number Diff line change
Expand Up @@ -9298,6 +9298,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->callwaitingdeluxepending) {
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->callwaitingdeluxepending = 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",
Expand Down Expand Up @@ -13060,6 +13080,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 */
Expand Down Expand Up @@ -13398,6 +13419,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);
Expand Down Expand Up @@ -18680,6 +18702,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")) {
Expand Down
4 changes: 4 additions & 0 deletions channels/chan_dahdi.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,10 @@ struct dahdi_pvt {
* \note Set from the "callwaiting" value read in from chan_dahdi.conf
*/
unsigned int permcallwaiting:1;
/*!
* \brief TRUE if Call Waiting Deluxe options should be available
*/
unsigned int callwaitingdeluxe:1;
/*!
* \brief TRUE if the outgoing caller ID is blocked/restricted/hidden.
* \note Set from the "hidecallerid" value read in from chan_dahdi.conf
Expand Down
1 change: 1 addition & 0 deletions channels/chan_iax2.c
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,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:
Expand Down
216 changes: 216 additions & 0 deletions channels/sig_analog.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -2965,6 +3164,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) {
Expand Down Expand Up @@ -3280,6 +3480,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:
Expand Down Expand Up @@ -3308,6 +3509,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);
Expand Down Expand Up @@ -3842,6 +4057,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;
}
Expand Down
9 changes: 9 additions & 0 deletions channels/sig_analog.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ struct analog_pvt {
unsigned int immediate:1;
unsigned int immediatering:1; /*!< TRUE if ringing should be provided for immediate execution */
unsigned int permcallwaiting:1; /*!< TRUE if call waiting is enabled. (Configured option) */
unsigned int callwaitingdeluxe:1; /*!< TRUE if Call Waiting Deluxe options are available */
unsigned int permhidecallerid:1; /*!< Whether to hide our outgoing caller ID or not */
unsigned int pulse:1;
unsigned int threewaycalling:1;
Expand Down Expand Up @@ -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 callwaitingdeluxepending:1;

unsigned int call_qualifier:1; /*!< Call qualifier delivery */

char callwait_num[AST_MAX_EXTENSION];
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 2ad2a2a

Please sign in to comment.