diff options
author | Mark Michelson <mmichelson@digium.com> | 2013-07-23 15:28:11 +0000 |
---|---|---|
committer | Mark Michelson <mmichelson@digium.com> | 2013-07-23 15:28:11 +0000 |
commit | bf22391b8d3ab34d8bfea29cf8a8ac66a80c2733 (patch) | |
tree | 9c4f35b55aea44e79fd9501b9616fc4125b7961d /bridges/bridge_builtin_features.c | |
parent | e148c6e867540675d1f26e54fa91b3497d0e8b6f (diff) |
Make DTMF attended transfer support feature-complete.
This greatly modifies the operation of DTMF attended transfers so that
the full range of options from features.conf applies.
In addition, a new option has been added that allows for a transferer
to switch between bridges during a transfer before completing the
transfer.
(closes issue ASTERISK-21543)
reported by Matt Jordan
Review: https://reviewboard.asterisk.org/r/2654
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@395151 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'bridges/bridge_builtin_features.c')
-rw-r--r-- | bridges/bridge_builtin_features.c | 417 |
1 files changed, 0 insertions, 417 deletions
diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c index f0c4c2437..87b74d4e2 100644 --- a/bridges/bridge_builtin_features.c +++ b/bridges/bridge_builtin_features.c @@ -54,421 +54,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/mixmonitor.h" #include "asterisk/audiohook.h" -/*! - * \brief Helper function that presents dialtone and grabs extension - * - * \retval 0 on success - * \retval -1 on failure - */ -static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len, const char *context) -{ - int res; - int digit_timeout; - RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup); - - ast_channel_lock(chan); - xfer_cfg = ast_get_chan_features_xfer_config(chan); - if (!xfer_cfg) { - ast_log(LOG_ERROR, "Unable to get transfer configuration\n"); - ast_channel_unlock(chan); - return -1; - } - digit_timeout = xfer_cfg->transferdigittimeout; - ast_channel_unlock(chan); - - /* Play the simple "transfer" prompt out and wait */ - res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY); - ast_stopstream(chan); - if (res < 0) { - /* Hangup or error */ - return -1; - } - if (res) { - /* Store the DTMF digit that interrupted playback of the file. */ - exten[0] = res; - } - - /* Drop to dialtone so they can enter the extension they want to transfer to */ - res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, digit_timeout); - if (res < 0) { - /* Hangup or error */ - res = -1; - } else if (!res) { - /* 0 for invalid extension dialed. */ - if (ast_strlen_zero(exten)) { - ast_debug(1, "%s dialed no digits.\n", ast_channel_name(chan)); - } else { - ast_debug(1, "%s dialed '%s@%s' does not exist.\n", - ast_channel_name(chan), exten, context); - } - ast_stream_and_wait(chan, "pbx-invalid", AST_DIGIT_NONE); - res = -1; - } else { - /* Dialed extension is valid. */ - res = 0; - } - return res; -} - -static void copy_caller_data(struct ast_channel *dest, struct ast_channel *caller) -{ - ast_channel_lock_both(caller, dest); - ast_connected_line_copy_from_caller(ast_channel_connected(dest), ast_channel_caller(caller)); - ast_channel_inherit_variables(caller, dest); - ast_channel_datastore_inherit(caller, dest); - ast_channel_unlock(dest); - ast_channel_unlock(caller); -} - -/*! \brief Helper function that creates an outgoing channel and returns it immediately */ -static struct ast_channel *dial_transfer(struct ast_channel *caller, const char *exten, const char *context) -{ - char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1]; - struct ast_channel *chan; - int cause; - - /* Fill the variable with the extension and context we want to call */ - snprintf(destination, sizeof(destination), "%s@%s", exten, context); - - /* Now we request a local channel to prepare to call the destination */ - chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination, - &cause); - if (!chan) { - return NULL; - } - - /* Who is transferring the call. */ - pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", ast_channel_name(caller)); - - /* To work as an analog to BLINDTRANSFER */ - pbx_builtin_setvar_helper(chan, "ATTENDEDTRANSFER", ast_channel_name(caller)); - - /* Before we actually dial out let's inherit appropriate information. */ - copy_caller_data(chan, caller); - - /* Since the above worked fine now we actually call it and return the channel */ - if (ast_call(chan, destination, 0)) { - ast_hangup(chan); - return NULL; - } - - return chan; -} - -/*! - * \internal - * \brief Determine the transfer context to use. - * \since 12.0.0 - * - * \param transferer Channel initiating the transfer. - * \param context User supplied context if available. May be NULL. - * - * \return The context to use for the transfer. - */ -static const char *get_transfer_context(struct ast_channel *transferer, const char *context) -{ - if (!ast_strlen_zero(context)) { - return context; - } - context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"); - if (!ast_strlen_zero(context)) { - return context; - } - context = ast_channel_macrocontext(transferer); - if (!ast_strlen_zero(context)) { - return context; - } - context = ast_channel_context(transferer); - if (!ast_strlen_zero(context)) { - return context; - } - return "default"; -} - -static void blind_transfer_cb(struct ast_channel *new_channel, void *user_data, - enum ast_transfer_type transfer_type) -{ - struct ast_channel *transferer_channel = user_data; - - if (transfer_type == AST_BRIDGE_TRANSFER_MULTI_PARTY) { - copy_caller_data(new_channel, transferer_channel); - } -} - -/*! \brief Internal built in feature for blind transfers */ -static int feature_blind_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) -{ - char exten[AST_MAX_EXTENSION] = ""; - struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt; - const char *context; - char *goto_on_blindxfr; - - ast_bridge_channel_write_hold(bridge_channel, NULL); - - ast_channel_lock(bridge_channel->chan); - context = ast_strdupa(get_transfer_context(bridge_channel->chan, - blind_transfer ? blind_transfer->context : NULL)); - goto_on_blindxfr = ast_strdupa(S_OR(pbx_builtin_getvar_helper(bridge_channel->chan, - "GOTO_ON_BLINDXFR"), "")); - ast_channel_unlock(bridge_channel->chan); - - /* Grab the extension to transfer to */ - if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) { - ast_bridge_channel_write_unhold(bridge_channel); - return 0; - } - - if (!ast_strlen_zero(goto_on_blindxfr)) { - ast_debug(1, "After transfer, transferer %s goes to %s\n", - ast_channel_name(bridge_channel->chan), goto_on_blindxfr); - ast_after_bridge_set_go_on(bridge_channel->chan, NULL, NULL, 0, goto_on_blindxfr); - } - - if (ast_bridge_transfer_blind(0, bridge_channel->chan, exten, context, blind_transfer_cb, - bridge_channel->chan) != AST_BRIDGE_TRANSFER_SUCCESS && - !ast_strlen_zero(goto_on_blindxfr)) { - ast_after_bridge_goto_discard(bridge_channel->chan); - } - - return 0; -} - -/*! Attended transfer code */ -enum atxfer_code { - /*! Party C hungup or other reason to abandon the transfer. */ - ATXFER_INCOMPLETE, - /*! Transfer party C to party A. */ - ATXFER_COMPLETE, - /*! Turn the transfer into a threeway call. */ - ATXFER_THREEWAY, - /*! Hangup party C and return party B to the bridge. */ - ATXFER_ABORT, -}; - -/*! \brief Attended transfer feature to complete transfer */ -static int attended_transfer_complete(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) -{ - enum atxfer_code *transfer_code = hook_pvt; - - *transfer_code = ATXFER_COMPLETE; - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); - return 0; -} - -/*! \brief Attended transfer feature to turn it into a threeway call */ -static int attended_transfer_threeway(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) -{ - enum atxfer_code *transfer_code = hook_pvt; - - *transfer_code = ATXFER_THREEWAY; - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); - return 0; -} - -/*! \brief Attended transfer feature to abort transfer */ -static int attended_transfer_abort(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) -{ - enum atxfer_code *transfer_code = hook_pvt; - - *transfer_code = ATXFER_ABORT; - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); - return 0; -} - -/*! \brief Internal built in feature for attended transfers */ -static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) -{ - char exten[AST_MAX_EXTENSION] = ""; - struct ast_channel *peer; - struct ast_bridge *attended_bridge; - struct ast_bridge_features caller_features; - int xfer_failed; - struct ast_bridge_features_attended_transfer *attended_transfer = hook_pvt; - const char *complete_sound; - const char *context; - enum atxfer_code transfer_code = ATXFER_INCOMPLETE; - const char *atxfer_abort; - const char *atxfer_threeway; - const char *atxfer_complete; - const char *fail_sound; - RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup); - - ast_bridge_channel_write_hold(bridge_channel, NULL); - - bridge = ast_bridge_channel_merge_inhibit(bridge_channel, +1); - - ast_channel_lock(bridge_channel->chan); - context = ast_strdupa(get_transfer_context(bridge_channel->chan, - attended_transfer ? attended_transfer->context : NULL)); - xfer_cfg = ast_get_chan_features_xfer_config(bridge_channel->chan); - if (!xfer_cfg) { - ast_log(LOG_ERROR, "Unable to get transfer configuration options\n"); - ast_channel_unlock(bridge_channel->chan); - return 0; - } - if (attended_transfer) { - atxfer_abort = ast_strdupa(S_OR(attended_transfer->abort, xfer_cfg->atxferabort)); - atxfer_threeway = ast_strdupa(S_OR(attended_transfer->threeway, xfer_cfg->atxferthreeway)); - atxfer_complete = ast_strdupa(S_OR(attended_transfer->complete, xfer_cfg->atxfercomplete)); - } else { - atxfer_abort = ast_strdupa(xfer_cfg->atxferabort); - atxfer_threeway = ast_strdupa(xfer_cfg->atxferthreeway); - atxfer_complete = ast_strdupa(xfer_cfg->atxfercomplete); - } - fail_sound = ast_strdupa(xfer_cfg->xferfailsound); - ast_channel_unlock(bridge_channel->chan); - - /* Grab the extension to transfer to */ - if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) { - ast_bridge_merge_inhibit(bridge, -1); - ao2_ref(bridge, -1); - ast_bridge_channel_write_unhold(bridge_channel); - return 0; - } - - /* Get a channel that is the destination we wish to call */ - peer = dial_transfer(bridge_channel->chan, exten, context); - if (!peer) { - ast_bridge_merge_inhibit(bridge, -1); - ao2_ref(bridge, -1); - ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); - ast_bridge_channel_write_unhold(bridge_channel); - return 0; - } - -/* BUGBUG bridging API features does not support the features.conf atxfer bounce between C & B channels */ - /* Setup a DTMF menu to control the transfer. */ - if (ast_bridge_features_init(&caller_features) - || ast_bridge_hangup_hook(&caller_features, - attended_transfer_complete, &transfer_code, NULL, 0) - || ast_bridge_dtmf_hook(&caller_features, atxfer_abort, - attended_transfer_abort, &transfer_code, NULL, 0) - || ast_bridge_dtmf_hook(&caller_features, atxfer_complete, - attended_transfer_complete, &transfer_code, NULL, 0) - || ast_bridge_dtmf_hook(&caller_features, atxfer_threeway, - attended_transfer_threeway, &transfer_code, NULL, 0)) { - ast_bridge_features_cleanup(&caller_features); - ast_hangup(peer); - ast_bridge_merge_inhibit(bridge, -1); - ao2_ref(bridge, -1); - ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); - ast_bridge_channel_write_unhold(bridge_channel); - return 0; - } - - /* Create a bridge to use to talk to the person we are calling */ - attended_bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX, - AST_BRIDGE_FLAG_DISSOLVE_HANGUP); - if (!attended_bridge) { - ast_bridge_features_cleanup(&caller_features); - ast_hangup(peer); - ast_bridge_merge_inhibit(bridge, -1); - ao2_ref(bridge, -1); - ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); - ast_bridge_channel_write_unhold(bridge_channel); - return 0; - } - ast_bridge_merge_inhibit(attended_bridge, +1); - - /* This is how this is going down, we are imparting the channel we called above into this bridge first */ -/* BUGBUG we should impart the peer as an independent and move it to the original bridge. */ - if (ast_bridge_impart(attended_bridge, peer, NULL, NULL, 0)) { - ast_bridge_destroy(attended_bridge); - ast_bridge_features_cleanup(&caller_features); - ast_hangup(peer); - ast_bridge_merge_inhibit(bridge, -1); - ao2_ref(bridge, -1); - ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); - ast_bridge_channel_write_unhold(bridge_channel); - return 0; - } - - /* - * For the caller we want to join the bridge in a blocking - * fashion so we don't spin around in this function doing - * nothing while waiting. - */ - ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL, 0); - -/* - * BUGBUG there is a small window where the channel does not point to the bridge_channel. - * - * This window is expected to go away when atxfer is redesigned - * to fully support existing functionality. There will be one - * and only one ast_bridge_channel structure per channel. - */ - /* Point the channel back to the original bridge and bridge_channel. */ - ast_bridge_channel_lock(bridge_channel); - ast_channel_lock(bridge_channel->chan); - ast_channel_internal_bridge_channel_set(bridge_channel->chan, bridge_channel); - ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge); - ast_channel_unlock(bridge_channel->chan); - ast_bridge_channel_unlock(bridge_channel); - - /* Wait for peer thread to exit bridge and die. */ - if (!ast_autoservice_start(bridge_channel->chan)) { - ast_bridge_depart(peer); - ast_autoservice_stop(bridge_channel->chan); - } else { - ast_bridge_depart(peer); - } - - /* Now that all channels are out of it we can destroy the bridge and the feature structures */ - ast_bridge_destroy(attended_bridge); - ast_bridge_features_cleanup(&caller_features); - - /* Is there a courtesy sound to play to the peer? */ - ast_channel_lock(bridge_channel->chan); - complete_sound = pbx_builtin_getvar_helper(bridge_channel->chan, - "ATTENDED_TRANSFER_COMPLETE_SOUND"); - if (!ast_strlen_zero(complete_sound)) { - complete_sound = ast_strdupa(complete_sound); - } else { - complete_sound = NULL; - } - ast_channel_unlock(bridge_channel->chan); - if (complete_sound) { - pbx_builtin_setvar_helper(peer, "BRIDGE_PLAY_SOUND", complete_sound); - } - - xfer_failed = -1; - switch (transfer_code) { - case ATXFER_INCOMPLETE: - /* Peer hungup */ - break; - case ATXFER_COMPLETE: - /* The peer takes our place in the bridge. */ - ast_bridge_channel_write_unhold(bridge_channel); - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); - xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, bridge_channel->chan, NULL, 1); - break; - case ATXFER_THREEWAY: - /* - * Transferer wants to convert to a threeway call. - * - * Just impart the peer onto the bridge and have us return to it - * as normal. - */ - ast_bridge_channel_write_unhold(bridge_channel); - xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, NULL, NULL, 1); - break; - case ATXFER_ABORT: - /* Transferer decided not to transfer the call after all. */ - break; - } - ast_bridge_merge_inhibit(bridge, -1); - ao2_ref(bridge, -1); - if (xfer_failed) { - ast_hangup(peer); - if (!ast_check_hangup_locked(bridge_channel->chan)) { - ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); - } - ast_bridge_channel_write_unhold(bridge_channel); - } - - return 0; -} - enum set_touch_variables_res { SET_TOUCH_SUCCESS, SET_TOUCH_UNSET, @@ -909,8 +494,6 @@ static int unload_module(void) static int load_module(void) { - ast_bridge_features_register(AST_BRIDGE_BUILTIN_BLINDTRANSFER, feature_blind_transfer, NULL); - ast_bridge_features_register(AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, feature_attended_transfer, NULL); ast_bridge_features_register(AST_BRIDGE_BUILTIN_HANGUP, feature_hangup, NULL); ast_bridge_features_register(AST_BRIDGE_BUILTIN_AUTOMON, feature_automonitor, NULL); ast_bridge_features_register(AST_BRIDGE_BUILTIN_AUTOMIXMON, feature_automixmonitor, NULL); |