UAS API - Transfer Menu Sample Application

  • Filename:

    samples/transfer_menu.py

    Description:

    An inbound application that prompts the user to select a target to be transfered to. The target names (to be read out using TTS) and addresses (to be transfered to using the channel API) are given in application_parameters which is configured on the Inbound Services page. The target addresses must be other inbound services that have been registered on the Inbound Services page.

    This example requires that application_parameters is a semi-colon delimited list in the format: <target name>,<target address>;<target name>,<target address> for example: join a conference,sip:AddToConfService@sip-0-2-0.aculab.com;listen to music,sip:InBoundMusic@sip-0-2-0.aculab.com This application requires one extra channel.

    The application rings and answers the call, it then tries to get the extra channel. It then splits up the information in application_arguments to generate the transfer menu list. The application then uses TTS to prompt the caller for a menu choice. Finally, it uses the channel.transfer_to_inbound_service() function to transfer the caller to another inbound service. This function requires a reference to an extra channel.

    The get_menu_selection() function has a loop which says each item in the transfer menu list, and then waits for a valid DTMF digit (one which corresponds to a transfer option). The transfer name and destination are returned by the function.

    Code:

    __uas_identify__ = "application"
    __uas_version__ = "1.0b1"
    
    from prosody.uas import Hangup, Error, PlayableMedia
    from prosody.uas.highlevel import HighLevelCallChannel, HighLevelGrammar, HighLevelDigitInputOptions
    
    """
        An inbound application that prompts the user to select a target to be transferred to. 
        
        The target names and addresses - or phone numbers - are provided in application_parameters.
        These are set, during service registration, on the inbound service registration page at cloud.aculab.com.    
    
        The application_parameters must be a delimited string in the format: <name>, <address>; <name>, <address>
        
        For example:
        "Fred,sip:fred@place.com;Jack,sip:jack@place.com"
    
        The target names are used to create an automatic speech recognition (ASR) grammar.
        ASR is used to get the caller's choice and the call is then connected to the target address.
        
        A digit is associated with each name, and this can be entered on the telephone keypad instead of speaking.
    
        At application start, the target names are read out along with the associated digit.
    
        This application requires 1 extra channel.
        
        Actions:
            - wait for an inbound call
            - generate the transfer options menu
            - speak the options using TTS
            - listen for the caller's selection using ASR and DTMF
            - transfer the call
    
    """
    
    def transfer_menu(application_parameters, my_log):
        # Split the application_parameters up into transfer options
        # (split with semicolon), and then again with commas. Return the 
        # data as a dictionary
    
        try:
            return dict(tuple(a.split(',')) for a in application_parameters.split(';'))
        except Exceptionas exc:
             my_log.error("The application parameters could not be parsed correctly: {0}".format(exc))
        return {}
    
    
    def get_menu_selection(channel, transfer_list, my_log):
    
        channel.DTMFDetector.clear_digits()
        my_log.info("transfer_list: {0}".format(transfer_list))
        
        # create a speech recognition grammar of the IVR options
        my_grammar = HighLevelGrammar()
        my_grammar.create_from_alternatives(*transfer_list.keys())
        
        valid_digits = range(len(transfer_list))
        my_digit_options = HighLevelDigitInputOptions(valid_digits=''.join(map(str, valid_digits)))
    
        prompt_text = 'Please say the name of the person you want to call. Or press '
        for digit in valid_digits:
            # associate digits with the names
            prompt_text = prompt_text + "{0} for {1}, ".format(digit, transfer_list.keys()[digit])
    
        prompt = PlayableMedia(text_to_say=prompt_text, channel=channel)
        response = channel.capture_input(prompt, speech_recognition_grammar=my_grammar, 
                                      digit_input_options=my_digit_options)
        # if response is None, no valid input was detected
        if response is not None:
            # check whether we got speech or digits
            if response.type == response.Type.SPEECH:
                # return the name
                return response.input
            else:
                # return the name associated with the digit
                return transfer_list.keys()[int(response.input)]
        return None
    
    
    def main(channel, application_instance_id, file_man, my_log, application_parameters):
        my_log.info("Transfer menu started")
        return_code = 0
        try:
            channel.ring(1)
            channel.answer()
            my_log.info("Call answered")
    
            # in this application, we will using the high level API wrapper
            # for the channel object
            high_level_channel = HighLevelCallChannel(channel, my_log)        
            try:
                # an extra channel is required, the number of extra channels
                # available is set on the service page
                out_channel = channel.ExtraChannel[0]
            except:
                raise Error("You need to register an extra channel for this application.")
    
            # Convert the application_parameters into a transfer menu list for later use
            transfer_list = transfer_menu(application_parameters)
            if not transfer_list:
                raise Error("Invalid transfer menu")
            
            # Prompt for digit
            channel.FilePlayer.say("Hello. ")
            target = get_menu_selection(high_level_channel, transfer_list, my_log)
    
            if not target:
                channel.FilePlayer.say("There is nobody to call.")
            else:
                # we will use the high level channel to place an outbound call and connect
                # the inbound and outbound calls together. If the target is a PSTN call
                # you will have to supply a call_from as well - please see the online documentation
                # on placing outbound PSTN calls.
                print("Calling {0}".format(target))
                if high_level_channel.call_and_connect(out_channel, call_to=transfer_list[target]) is False:
                    channel.FilePlayer.say("I wasn't able to transfer you. Please try again later.")
                    channel.hang_up()
                
            channel.wait_for_idle()
    
        except Hangup as exc:
            my_log.info("Got Hangup")
            return_code = 100
    
        except Error as exc:
            my_log.error("Got Error: {0}".format(exc))
            return_code = -101
    
        except Exception as exc:
            my_log.exception("Got unexpected exception: {0}".format(exc))
            return_code = -102
    
        finally:
            if channel.state() != channel.State.IDLE:
                channel.hang_up()
        return return_code
    
  • Filename:

    Samples\UASApplications\TransferMenu\src\main\java\com\aculab\uas\app\TransferMenu.java

    Description:

    This application first checks to ensure that the Inbound Service that invoked this application has allocated sufficient extra channels, by inspecting the channel.getExtraChannels() array. Extra channels are required to make outbound calls in an inbound application and can be configured in the Inbound Service configuration. Once the check has been made, the application splits the applicationParameters into an array for further use.

    The call is answered, a small TTS message is sent to the user and then the getMenuSelection() method is called. This method will prompt the user for a DTMF tone after giving a list of the transfer options. We use the channel.getDtmfDetector().getDigits() method to grab a single keypad press and then make sure the returned item is in our list of transfer options. This loop will continue until either a valid keypress has been detected, or the user hangs up.

    To transfer the call, we use the channel.transferToInboundService() method. We specify the target service name and the extra channel to use for the transfer. The function returns the state of the call that indicates if the transfer attempt was successful. This transfer method uses the retrievable transfer mechanism and therefore we must maintain the reference to this channel to allow the transferred call to continue. So we wait for the transferred call to be hung up before terminating.

    Code:

    package com.aculab.uas.app;
    
    // An inbound application that prompts the user to select a target inbound service 
    // to be transferred to. A prompt (to be read out) and service name (to be transferred to) 
    // for each possible target need to be listed in the applicationParameters when
    // this service is registered.
    //
    // Requires:
    // [applicationParameters = semicolon delimited list of <action><service name>
    //      e.g. "join a conference,AddToConference;
    //            listen to media file,InboundPlayWav"]
    // [1 extra channel]
    import java.util.ArrayList;
    import java.util.List;
    
    import com.aculab.amsapi.*;
    import com.aculab.uasapi.*;
    
    // The application class.
    // This must extend either UASInboundApplication or UASOutboundApplication and
    // override the run method.
    // It must be packaged in a jar file of the same name together with a manifest file.
    public class TransferMenu extends UASInboundApplication
    {
        // Success Codes:
        final int Success = 0;
        // ... any positive integer
    
        // Fail Codes:
        // -1 to -99 reserved
        final int ExceptionThrown = -100;
        final int NotSufficientExtraChannels = -101;
        final int NotSufficientDestinations = -102;
    
        // A class to represent each of the menu options
        class TransferTarget
        {
            public TransferTarget(String entry, int key) throws IllegalArgumentException
            {
                String[] details = entry.split(",");
                if (details.length != 2)
                {
                    throw new IllegalArgumentException("applicationParameters contains invalid destination details");
                }
                Prompt = details[0].trim();
                ServiceName = details[1].trim();
                Key = key;
            }
            public String Prompt;
            public String ServiceName;
            public int Key;
        }
    
        // This is the entry point for the application
        @Override
        public int run(UASCallChannel channel, String applicationParameters)
            throws Exception
        {
            this.getTrace().traceInfo("Start - appParms [%s]", applicationParameters);
            int returnCode = Success;
            boolean primaryCallHungUp = false;
    
            // Check we've got an extra Channel
            if (channel.getExtraChannels().length < 1)
            {
                this.getTrace().traceWarning("Needs 1 extra channel to be configured");
                return NotSufficientExtraChannels;
            }
            
            UASCallChannel targetChannel = channel.getExtraChannels()[0];
    
            try
            {
                // Decipher the applicationParameters
                String[] entries = applicationParameters.split(";");
                if (entries.length < 1)
                {
                    this.getTrace().traceWarning("Needs at least 1 transfer target to be configured");
                    return NotSufficientDestinations;
                }
                List<TransferTarget> targets = new ArrayList<TransferTarget>(entries.length);
                int key = 0;
                for (String entry : entries)
                {
                    targets.add(new TransferTarget(entry, key++));
                }
    
                // The selected target
                TransferTarget target = null;
    
                // Answer the call
                CallState state = channel.answer();
                if (state != CallState.Answered)
                {
                    primaryCallHungUp = true;
                }
    
                if (!primaryCallHungUp)
                {
                    this.getTrace().traceInfo("Call answered");
    
                    // Prompt for a digit
                    channel.getFilePlayer().say("Hello.");
                    target = getMenuSelection(channel, targets);
                    if (target == null)
                    {
                        primaryCallHungUp = true;
                    }
                }
    
                if (!primaryCallHungUp)
                {
                    // Transfer to that target
                    channel.getFilePlayer().say("Transferring to %s", target.Prompt);
                    state = channel.transferToInboundService(
                                                target.ServiceName,
                                                channel.getCallDetails().getCallFrom(),
                                                targetChannel,
                                                null, 
                                                30);
    
                    if (state != CallState.Transferred)
                    {
                        channel.getFilePlayer().say("I wasn't able to transfer you.");
                        channel.getFilePlayer().say("Please try again later.");
                    }
                    else
                    {
                        // Wait for transferree or far end to hangup 
                        channel.waitForTransferredCallRetrieved(Timeout.Infinite);
                    }
                }
            }
            catch (Exception e)
            {
                this.getTrace().traceError("Exception thrown %s", e.getMessage());
                returnCode = ExceptionThrown;
            }
            finally
            {
                channel.hangUp();
            }
    
            this.getTrace().traceInfo("Completed with return code %d", returnCode);
            return returnCode;
        }
    
        private TransferTarget getMenuSelection(UASCallChannel channel, List<TransferTarget> targets) 
               throws NumberFormatException, IllegalStateException, InterruptedException, IllegalArgumentException, ObjectDisposedException
        {
            // Prepare to receive digits
            channel.getDtmfDetector().clearDigits();
    
            // Get the target selected
            do
            {
                // Play the transfer options to the caller with bargein
                for (TransferTarget target : targets)
                {
                    String msg = String.format("Press %s to %s", target.Key, target.Prompt);
                    FilePlayerCause cause = channel.getFilePlayer().say(true, msg);
                    if (cause == FilePlayerCause.HangUp)
                    {
                        return null;
                    }
                    if (cause == FilePlayerCause.BargeIn)
                    {
                        break;
                    }
                }
    
                String digits = channel.getDtmfDetector().getDigits(1, 10);
                if (channel.getDtmfDetector().getCause() == DtmfDetectorCause.Count)
                {
                    // Get the transfer target
                    int keyPressed = Integer.valueOf(digits);
                    this.getTrace().traceInfo("Got keypress %d", keyPressed);
                    if (keyPressed < targets.size())
                    {
                        return targets.get(keyPressed);
                    }
                    channel.getFilePlayer().say("Selection not valid.");
                }
            } while (channel.getDtmfDetector().getCause() != DtmfDetectorCause.HangUp);
    
            // returning null on hangup
            return null;
        }
    }
    
  • Filename:

    Samples\C#\TransferMenu\TransferMenu.cs

    Description:

    This application first checks to ensure that the Inbound Service that invoked this application has allocated sufficient extra channels, by inspecting the channel.ExtraChannels array. Extra channels are required to make outbound calls in an inbound application and can be configured in the Inbound Service configuration. Once the check has been made, the application splits the applicationParameters into an array for further use.

    The call is answered, a small TTS message is sent to the user and then the GetMenuSelection() method is called. This method will prompt the user for a DTMF tone after giving a list of the transfer options. We use the channel.DtmfDetector.GetDigits() method to grab a single keypad press and then make sure the returned item is in our list of transfer options. This loop will continue until either a valid keypress has been detected, or the user hangs up.

    To transfer the call, we use the channel.TransferToInboundService() method. We specify the target address and the extra channel to use for the transfer. The function returns a boolean value indicating if the transfer attempt was successful. This transfer method uses the retrievable transfer mechanism and therefore we must maintain the state of the original channel to allow the transferred call to continue. So we wait for the transferred call to be hung up before terminating.

    Code:

    using System;
    using System.Threading;
    using System.Collections.Generic;
    using AMSClassLibrary;
    using UASAppAPI;
    
    // An inbound application that prompts the user to select a target inbound service 
    // to be transferred to. A prompt (to be read out) and service name (to be transferred to) 
    // for each possible target need to be listed in the applicationParameters when
    // this service is registered.
    //
    // Note: this sample will only run on platforms that support Transfer.
    //
    // Requires:
    // [applicationParameters = semicolon delimited list of <action>,<service name<
    //      e.g. "join a conference,AddToConference;
    //            listen to media file,InboundPlayWav"]
    // [1 extra channel]
    namespace TransferMenu
    {
        // The application class.join a 
        // This must have the same name as the assembly and must inherit from either 
        // UASInboundApplication or UASOutboundApplication.
        // It must override the Run method.
        public class TransferMenu : UASInboundApplication
        {
            // Possible return codes
            enum ReturnCode
            {
                // Success Codes:
                Success = 0,
                // ... any positive integer
    
                // Fail Codes:
                // -1 to -99 reserved
                ExceptionThrown = -100,
                NotSufficientExtraChannels = -101,
                NotSufficientDestinations = -102
            }
    
            // A class to represent each of the menu options
            class TransferTarget
            {
                public TransferTarget(string entry, int key)
                {
                    string[] details = entry.Split(',');
                    if (details.Length != 2)
                    {
                        throw new ArgumentException("Invalid destination details", "applicationParameters");
                    }
                    Prompt = details[0].Trim();
                    ServiceName = details[1].Trim();
                    Key = key;
                }
                public string Prompt;
                public string ServiceName;
                public int Key;
            }
    
            // This is the entry point for the application
            public override int Run(UASCallChannel channel,
                                    string applicationParameters)
            {
                this.Trace.TraceInfo("Started");
                ReturnCode reply = ReturnCode.Success;
                bool primaryCallHungUp = false;
    
                // Check we've got an extra Channel
                if (channel.ExtraChannels.Length < 1)
                {
                    this.Trace.TraceWarning("Needs 1 extra channel to be configured");
                    return (int)ReturnCode.NotSufficientExtraChannels;
                }
                UASCallChannel targetChannel = channel.ExtraChannels[0];
    
                try
                {
                    // Decipher the applicationParameters
                    string[] entries = applicationParameters.Split(';');
                    if (entries.Length < 1)
                    {
                        this.Trace.TraceWarning("Needs at least 1 transfer target to be configured");
                        return (int)ReturnCode.NotSufficientDestinations;
                    }
                    List<TransferTarget> targets = new List<TransferTarget>(entries.Length);
                    int key = 0;
                    foreach (string entry in entries)
                    {
                        targets.Add(new TransferTarget(entry, key++));
                    }
    
                    // The selected target
                    TransferTarget target = null;
    
                    // Answer the call
                    CallState state = channel.Answer();
                    if (state != CallState.Answered)
                    {
                        primaryCallHungUp = true;
                    }
    
                    if (!primaryCallHungUp)
                    {
                        this.Trace.TraceInfo("Call answered");
    
                        // Prompt for a digit
                        channel.FilePlayer.Say("Hello.");
                        target = getMenuSelection(channel, targets);
                        if (target == null)
                        {
                            primaryCallHungUp = true;
                        }
                    }
    
                    if (!primaryCallHungUp)
                    {
                        // Transfer to that target
                        channel.FilePlayer.Say("Transferring to {0}", target.Prompt);
                        state = channel.TransferToInboundService(
                                                    target.ServiceName,
                                                    targetChannel);
    
                        if (state != CallState.Transferred)
                        {
                            channel.FilePlayer.Say("I wasn't able to transfer you.");
                            channel.FilePlayer.Say("Please try again later.");
                        }
                        else
                        {
                            // Wait for transferree or far end to hangup 
                            channel.WaitForTransferredCallRetrieved(Timeout.Infinite);
                        }
                    }
                }
                catch (Exception e)
                {
                    this.Trace.TraceError("Exception thrown {0}", e.Message);
                    reply = ReturnCode.ExceptionThrown;
                }
                finally
                {
                    channel.HangUp();
                }
    
                this.Trace.TraceInfo("Completed");
                return (int)reply;
            }
    
            TransferTarget getMenuSelection(UASCallChannel channel, List<TransferTarget> targets)
            {
                // Prepare to receive digits
                channel.DtmfDetector.ClearDigits();
    
                // Get the target selected
                do
                {
                    // Play the transfer options to the caller with bargein
                    foreach (TransferTarget target in targets)
                    {
                        string msg = string.Format("Press {0} to {1}", target.Key, target.Prompt);
                        FilePlayerCause cause = channel.FilePlayer.Say(msg, true);
                        if (cause == FilePlayerCause.HangUp)
                        {
                            return null;
                        }
                        if (cause == FilePlayerCause.BargeIn)
                        {
                            break;
                        }
                    }
    
                    string digits;
                    channel.DtmfDetector.GetDigits(1, out digits, 10);
                    if (channel.DtmfDetector.Cause == DtmfDetectorCause.Count)
                    {
                        // Get the transfer target
                        int keyPressed = Convert.ToInt32(digits);
                        this.Trace.TraceInfo("Got keypress {0}", keyPressed);
                        if (keyPressed < targets.Count)
                        {
                            return targets[keyPressed];
                        }
                        channel.FilePlayer.Say("Selection not valid.");
                    }
                } while (channel.DtmfDetector.Cause != DtmfDetectorCause.HangUp);
    
                // returning null on hangup
                return null;
            }
        }
    }
  • Filename:

    Samples\VB\TransferMenu\TransferMenu.vb

    Description:

    This application first checks to ensure that the Inbound Service that invoked this application has allocated sufficient extra channels, by inspecting the channel.ExtraChannels array. Extra channels are required to make outbound calls in an inbound application and can be configured in the Inbound Service configuration. Once the check has been made, the application splits the applicationParameters into an array for further use.

    The call is answered, a small TTS message is sent to the user and then the GetMenuSelection() method is called. This method will prompt the user for a DTMF tone after giving a list of the transfer options. We use the channel.DtmfDetector.GetDigits() method to grab a single keypad press and then make sure the returned item is in our list of transfer options. This loop will continue until either a valid keypress has been detected, or the user hangs up.

    To transfer the call, we use the channel.TransferToInboundService() method. We specify the target address and the extra channel to use for the transfer. The function returns a boolean value indicating if the transfer attempt was successful. This transfer method uses the retrievable transfer mechanism and therefore we must maintain the state of the original channel to allow the transferred call to continue. So we wait for the transferred call to be hung up before terminating.

    Code:

    Imports System.Collections.Generic
    Imports AMSClassLibrary
    Imports UASAppAPI
    
    ' An inbound application that prompts the user to select a target inbound service 
    ' to be transferred to. A prompt (to be read out) and service name (to be transferred to) 
    ' for each possible target need to be listed in the applicationParameters when
    ' this service is registered.
    '
    ' Note: this sample will only run on platforms that support Transfer.
    '
    ' Requires:
    ' [applicationParameters = semicolon delimited list of <action>,<service name>
    '      e.g. "join a conference,AddToConference;listen to media file,InboundPlayWav"]
    ' [1 extra channel]
    Namespace TransferMenu
    
        Public Class TransferMenu
            Inherits UASInboundApplication
    
            ' Possible return codes
            Enum ReturnCode
                ' Success Codes:
                Success = 0
                ' ... any positive integer
    
                ' Fail Codes:
                ' -1 to -99 reserved
                ExceptionThrown = -100
                NotSufficientExtraChannels = -101
                NotSufficientDestinations = -102
            End Enum
    
            ' A class to represent each of the menu options
            Class TransferTarget
                Public Sub New(ByVal entry As String, _
                               ByVal keyin As Integer)
                    Dim details = entry.Split(",")
                    If details.Length <> 2 Then
                        Throw New ArgumentException("Invalid destination details", "applicationParameters")
                    End If
                    Prompt = details(0).Trim()
                    ServiceName = details(1).Trim()
                    Key = keyin
                End Sub
    
                Public Prompt As String
                Public ServiceName As String
                Public Key As Integer
            End Class
    
            ' This is the entry point for the application
            Overrides Function Run(ByVal channel As UASCallChannel, _
                                   ByVal applicationParameters As String) _
                                   As Integer
    
                Me.Trace.TraceInfo("Started")
    
                Dim reply = ReturnCode.Success
                Dim primaryCallHungUp = False
    
                ' Check we've got an extra Channel
                If channel.ExtraChannels.Length < 1 Then
                    Me.Trace.TraceWarning("Needs 1 extra channel to be configured")
                    Return ReturnCode.NotSufficientExtraChannels
                End If
    
                Dim targetChannel = channel.ExtraChannels(0)
    
                Try
                    ' Decipher the applicationParameters
                    Dim entries = applicationParameters.Split(";")
                    If entries.Length < 1 Then
                        Me.Trace.TraceWarning("Needs at least 1 transfer target to be configured")
                        Return ReturnCode.NotSufficientDestinations
                    End If
    
                    Dim targets = New List(Of TransferTarget)
                    Dim key = 0
                    For Each entry In entries
                        targets.Add(New TransferTarget(entry, key))
                        key += 1
                    Next
    
                    ' The selected target
                    Dim target As TransferTarget = Nothing
    
                    ' Answer the call
                    Dim state = channel.Answer()
                    If state <> CallState.Answered Then
                        primaryCallHungUp = True
                    End If
    
                    If Not primaryCallHungUp Then
                        Me.Trace.TraceInfo("Call answered")
    
                        ' Prompt for a digit
                        channel.FilePlayer.Say("Hello.")
                        target = getMenuSelection(channel, targets)
                        If target Is Nothing Then
                            primaryCallHungUp = True
                        End If
                    End If
    
                    If Not primaryCallHungUp Then
                        ' Transfer to that target
                        channel.FilePlayer.Say("Transferring to {0}", target.Prompt)
                        state = channel.TransferToInboundService(target.ServiceName, targetChannel)
    
                        If state <> CallState.Transferred Then
                            channel.FilePlayer.Say("I wasn't able to transfer you.")
                            channel.FilePlayer.Say("Please try again later.")
                        Else
                            ' Wait for transferree or far end to hangup 
                            channel.WaitForTransferredCallRetrieved(Timeout.Infinite)
                        End If
                    End If
                Catch e As Exception
                    Me.Trace.TraceError("Exception thrown {0}", e.Message)
                    reply = ReturnCode.ExceptionThrown
                Finally
                    channel.HangUp()
                End Try
    
                Me.Trace.TraceInfo("Completed")
                Return reply
    
            End Function
    
            Function getMenuSelection(ByVal channel As UASCallChannel, ByVal targets As List(Of TransferTarget)) As TransferTarget
                ' Prepare to receive digits
                channel.DtmfDetector.ClearDigits()
    
                ' Get the target selected
                Do
                    ' Play the transfer options to the caller with bargein
                    For Each target In targets
                        Dim msg = String.Format("Press {0} to {1}", target.Key, target.Prompt)
                        Dim cause = channel.FilePlayer.Say(msg, True)
                        If cause = FilePlayerCause.HangUp Then
                            Return Nothing
                        End If
                        If cause = FilePlayerCause.BargeIn Then
                            Exit Do
                        End If
                    Next
    
                    Dim digits As String = ""
                    channel.DtmfDetector.GetDigits(1, digits, 10)
                    If channel.DtmfDetector.Cause = DtmfDetectorCause.Count Then
                        ' Get the transfer target
                        Dim keyPressed = Convert.ToInt32(digits)
                        Me.Trace.TraceInfo("Got keypress {0}", keyPressed)
                        If keyPressed < targets.Count Then
                            Return targets(keyPressed)
                        End If
                        channel.FilePlayer.Say("Invalid selection.")
                    End If
                Loop While channel.DtmfDetector.Cause <> DtmfDetectorCause.HangUp
    
                ' returning null on hangup
                Return Nothing
            End Function
    
        End Class
    
    End Namespace