How to: Transfer an Audio Conversation

[This is preliminary documentation and is subject to change.]

To transfer an active audio conversation, you must obtain the AVModality instance on the conversation you will transfer and a Contact instance representing the person receiving the transferred call. Your application must be able to handle all of the outcomes of the transfer operation. A transfer operation can result in the acceptance of the transfer by a target user or rejection by the same user. When a transfer is rejected, your application is responsible for reacting to the rejection by retrieving the audio/video modality from a hold state or terminating the call. You handle a series of events on the conversation audio/video modality to monitor the progress of the transfer.

The state and the availability of specific actions such as hold, retrieve, transfer, connect, and disconnect must be monitored after a transfer operation is initiated. While a transfer is pending, you cannot retrieve, connect, or disconnect a call. If the transfer is accepted, you are disconnected from the call with no further action on the part of your application. If the transfer is rejected, the retrieve, and disconnect actions are available. The conversation state is set to active.

Tip Tip

When forwarding or transferring a conversation using the audio/video modality, the instant messaging modality is connected to the target participant automatically. When a call is transferred to a public switch telephone network (PSTN) telephone, video and IM are disconnected and cannot be re-connected.

The following table lists modality actions and their availability while a conversation transfer is pending.

Modality Action

Availability

Hold

True

Retrieve

False

Transfer

False

The following table lists modality actions and their availability while a conversation transfer has been rejected.

Modality Action

Availability

Hold

False

Retrieve

True

Transfer

True

  1. Get the LyncClient instance. Verify that the client is signed in to the server. For information about signing in to Microsoft Lync Server 2013 Preview, see How to: Sign In to Lync.

  2. Get a connected instance of Conversation

    The conversation direction can be incoming or outgoing. For information about starting an audio conversation, see How to: Start Audio Conversations.

  3. Register for the StateChanged event on the Conversation instance.

  4. Get the AVModality instance from the collection of modalities on the Conversation instance.

    Read the Modalities property. Use the ModalityTypes.AudioVideo enumerator as an index to specify which modality to get.

  5. Register for the ModalityStateChanged and ActionAvailabilityChanged events on the conversation AVModality instance.

  6. Get a Contact instance that resolves to the user you target for the transfer.

    Tip Tip

    If the target user is not a contact in one of the groups exposed by Groups, you can obtain the contact by searching for the contact using the Microsoft Lync 2013 Preview API search feature, or you can call into GetContactByuri(string) if you have the URI of the target user.

    For information about searching for a contact, see How to: Search For a Contact.

  7. To start the transfer operation, call into the BeginTransfer or BeginTransfer method of the AVModality instance. Pass the target contact as the first argument of the method call. If you are transferring a call to a public switched telephone network (PSTN) phone, voicemail or other type of contact endpoint, call the overload of BeginTransfer and pass the ContactEndpoint representing the transfer target.

  8. Catch the series of ActionAvailabilityChanged events raised by the audio/video modality.

    As the availability of hold, retrieve, and transfer change as the transfer operation progresses, your event callback for these events is invoked. If the transfer is rejected, the retrieve, disconnect, and transfer options are made available. If the transfer is accepted, your event callback for these events is not invoked.

    Tip Tip

    You should use these events to trigger the update of call action button controls you place on your UI. For example, when the Hold action is not available, disable a hold button on your form.

  9. Catch the series of StateChanged events raised by the conversation.

    After completing the transfer operation, your event callback for these events is invoked. If the transfer is accepted, your event callback is invoked and the state of the conversation is ConversationState.Terminated. If the transfer is rejected, your event callback is invoked and the state of the conversation is ConversationState.Active.

    Important note Important

    The conversation state changes several times while the transfer operation is in progress. You receive the event indicating the conversation is active before the transfer is accepted or rejected, and again if the transfer is rejected. Each time you handle the event with this conversation state, you must evaluate the availability of the retrieve action on the audio/video modality (step 8). If the conversation becomes active and the retrieve action is available, the transfer is rejected.

Audio/Video Modality ActionAvailabilityChanged Event

  • Check the Action and IsAvailable properties. If the action is ModalityAction.Retrieve, .Disconnect, .RemoteTransfer, .ConsultativeTransfer, LocalTransfer, or Forward then you get the IsAvailable property. If true, then the transfer was rejected. In this case, you will also receive a conversation state changed event indicating the conversation is now active.

Conversation StateChanged Event

  • Read the NewState property. The resulting enumerator indicates the state of the transfer operation.

    If the state is ConversationState.Terminated then the transfer is accepted and you should un-register for conversation and modality events and dispose of the conversation. If the state is .Active and the audio/video modality can be retrieved, disconnected, transferred, or forwarded then the transfer operation was rejected.

Class Field Declarations

The following declarations are added to your custom class. You initiate _AVModality with the AVModality obtained from the Modalities property of the active conversation.

        private AVModality _AVModality;
        private LyncClient _LyncClient;

        /// <summary>
        /// Updates a form control based on action.
        /// </summary>
        /// <param name="action">UIDelegateAction. The action to perform on a control.</param>
        /// <param name="formControl">object. The form control to act on.</param>
        /// <param name="updateValue">updateValue. The value to set on the control property.</param>
        delegate void UpdateFormControlDelegate(UIDelegateAction action, object formControl, object updateValue);

    enum UIDelegateAction
    { 
        SetButtonEnableState = 0,
        SetAllButtonsEnableState = 1,
        SetButtonText = 2,
        SetLabelText = 3,
        CloseForm = 4,
        ShutdownForm = 5
    }

Transfer a Call

The following example uses an active Conversation instance where the audio/video modality is connected to a remote user.

Caution note Caution

The example only illustrates the walkthrough tasks need to transfer a call. For an example of starting an audio conversation, see How to: Start Audio Conversations.

        /// <summary>
        /// Performs a "blind transfer" on the active conversation
        /// </summary>
        /// <param name="targetURI">string. Uri string resolving to a contact or a telephone such as a mobile phone.</param>
        private void TransferConversation(string targetURI)
        {
            try
            {
                Contact TargetContact = _LyncClient.ContactManager.GetContactByUri(targetURI);
                if (TargetContact != null)
                {
                    List<string> _context = new List<string>();
                    Object[] asyncState = { ModalityState.Transferring, _context, _AVModality };
                    _AVModality.BeginTransfer(TargetContact, TransferOptions.None, TransferModalityCallback, asyncState);
                }
            }
            catch (ArgumentException) { 
                MessageBox.Show("Entered Uri is not valid " + targetURI); 
            }
            catch (ItemNotFoundException) { 
                MessageBox.Show("Entered Uri could not be resolved to a Contact " + targetURI); 
            }

        }


Transfer Operation Callback

The following example is called asynchronously on the Lync thread when the transfer operation completes.

        /// <summary>
        /// Called on the LyncClient worker thread when a call transfer operation completes.
        /// </summary>
        /// <param name="ar">IAsyncResult. The state of the asynchronous operation.</param>
        private void TransferModalityCallback(IAsyncResult ar)
        {
            if (ar.IsCompleted == true)
            {
                _ConsultTransferTargetConversation = null;
                Object[] _asyncState = (Object[])ar.AsyncState;
                ModalityState _targetState = (ModalityState)_asyncState[0];
                IList<string> _contextProperties = (List<string>)_asyncState[1];
                ((AVModality)_asyncState[2]).EndTransfer(out _targetState, out _contextProperties, ar);

                if (_targetState == ModalityState.Disconnected)
                {
                    this.Invoke(
                        new UpdateFormControlDelegate(UpdateFormControl),
                        new object[] {UIDelegateAction.SetAllButtonsEnableState, 
                        null, 
                        false });
                }
            }
        }

Modality.ActionAvailabilityChanged Event

Handle the event to discover ability to hold, retrieve, and transfer. Event is raised when your ability to do any of these actions changes. When transferring, actions change. When transfer is rejected, actions change.

        /// <summary>
        /// The availability of an action on the audio/video modality has changed. This event triggers the enable state of
        /// MainForm action buttons.
        /// </summary>
        /// <param name="sender">object. the AVModality instance whose action availability has changed.</param>
        /// <param name="e">ModalityActionAvailabilityChangedEventArgs. Event state data providing the action and availability that changed.</param>
        void _AVModality_ActionAvailabilityChanged(object sender, ModalityActionAvailabilityChangedEventArgs e)
        {
            string HoldButtonText = string.Empty;
            switch (e.Action)
            {
                case ModalityAction.Hold:
                    if (e.IsAvailable == true)
                    {
                        HoldButtonText = "Hold Conversation";
                    }
                    if (e.IsAvailable == false)
                    {
                        HoldButtonText = "Pick up Conversation";
                    }
                    break;
                case ModalityAction.LocalTransfer:
                    this.Invoke(
                        new UpdateFormControlDelegate(UpdateFormControl),
                        new object[] {UIDelegateAction.SetButtonEnableState, 
                            Transfer_Button, 
                            e.IsAvailable });
                    break;
            }

            if (HoldButtonText.Length > 0)
            {
                this.Invoke(
                    new UpdateFormControlDelegate(UpdateFormControl),
                    new object[] {UIDelegateAction.SetButtonText, 
                        Hold_Button, 
                        HoldButtonText });
            }
        }

ConversationStateChanged Event

Use ConversationState.Inactive when a call is placed on hold after a call transfer. Use ConversationState.Active when transfer is rejected. After the transfer is rejected, the call must be retrieved.


        /// <summary>
        /// Handles event raised when the state of an active conversation has changed. 
        /// </summary>
        /// <param name="source">Conversation. The active conversation that raised the state change event.</param>
        /// <param name="data">ConversationStateChangedEventArgs. Event data containing state change data</param>
        void Conversation_ConversationChangedEvent(object source, ConversationStateChangedEventArgs data)
        {
            UpdateFormControlDelegate del = new UpdateFormControlDelegate(UpdateFormControl);
            if (data.NewState == ConversationState.Inactive)
            {
                MessageBox.Show("Transfer is in progress.", "Conversation State Changed");
            }
            if (data.NewState == ConversationState.Active)
            {
                if (((Conversation)source).Modalities[ModalityTypes.AudioVideo].CanInvoke(ModalityAction.Retrieve) || ((Conversation)source).Modalities[ModalityTypes.AudioVideo].CanInvoke(ModalityAction.Transfer))
                {
                    MessageBox.Show("Transfer has been rejected.", "Conversation State Changed");
                }
            }
            if (data.NewState == ConversationState.Terminated)
            {
                MessageBox.Show("Conversation state is terminated", "Conversation State Changed");
                // Update form status label text with current state of conversation.
            }
        }


Windows Form Control Update Helper

The following example is called using the delegate declared as a class field. This example is invoked on the Lync thread and the event data is marshaled to the UI thread by the Invoke call.

        /// <summary>
        /// Updates a form control based on action.
        /// </summary>
        /// <param name="action">UIDelegateAction. The action to perform on a control.</param>
        /// <param name="formControl">object. The form control to act on.</param>
        /// <param name="updateValue">updateValue. The value to set on the control property.</param>
        private void UpdateFormControl(UIDelegateAction action, object formControl, object updateValue)
        {
            switch (action)
            { 
                case UIDelegateAction.CloseForm:
                    System.Windows.Forms.Form formToClose = (System.Windows.Forms.Form)formControl;
                    formToClose.Close();
                    break;
                case UIDelegateAction.SetButtonEnableState:
                    System.Windows.Forms.Button buttonToToggle = (System.Windows.Forms.Button)formControl;
                    buttonToToggle.Enabled = (Boolean)updateValue;
                    break;
                case UIDelegateAction.SetAllButtonsEnableState:
                    Park_Button.Enabled = (Boolean)updateValue;
                    Hold_Button.Enabled = (Boolean)updateValue;
                    ConsultTransfer_Button.Enabled = (Boolean)updateValue;
                    Transfer_Button.Enabled = (Boolean)updateValue;
                    CompleteConsult_Button.Enabled = (Boolean)updateValue;
                    break;
                case UIDelegateAction.SetButtonText:
                    System.Windows.Forms.Button buttonToUpdate = (System.Windows.Forms.Button)formControl;
                    buttonToUpdate.Text = (string)updateValue;
                    break;
                case UIDelegateAction.SetLabelText:
                    System.Windows.Forms.Label labelToUpdate = (System.Windows.Forms.Label)formControl;
                    labelToUpdate.Text = (string)updateValue;
                    break;
                case UIDelegateAction.ShutdownForm:
                    this._ClientModel.Dispose();
                    this.Close();
                    break;
            }
        }


Community Additions

Show:
© 2014 Microsoft