Posted by: rcosic | 17/11/2011

Availability (presence) in Lync Client

Note: this post is part of Lync Client development series. You can find the following topics here:

This time, I will explain how presence state (availability and activity) can be handled by using Lync Client API.

There are several reasons you might want to handle it: you can publish it to some other application, or you can synchronize it with other application providing its own “availability” states. In either way, you have to know how availability is implemented in Lync and how it can be handled. Just to note, I wrote some of this topic in one of my recent posts, so please take a look on it also.

First of all, to get the availability status from Lync, you can use two approaches: subscribe to an event when this state is changed, and to call some methods on Lnyc API to find out this information. Let’s see this:

1) Subscribe to ContactInformationChanged event on Self object:

m_lyncClient.Self.Contact.ContactInformationChanged += new EventHandler(Self_ContactInformationChanged);

void Self_ContactInformationChanged(object sender, ContactInformationChangedEventArgs e)
{
// in case of signing out or in, self contact is already released
if (m_lyncClient.State == ClientState.SigningOut || m_lyncClient.State == ClientState.SignedOut || m_lyncClient.State == ClientState.SigningIn)
{
return;
}

Contact self = sender as Contact;

// has user changed his availability (therefore, his presence status)?
if (e.ChangedContactInformation.Contains(ContactInformationType.Availability))
{
//get actual contactModel availability (Will be int value within OCOM availaiblity ranges)
ContactAvailability availability = (ContactAvailability)self.GetContactInformation(ContactInformationType.Availability);

string activity = (string)self.GetContactInformation(ContactInformationType.Activity);

OnAvailabilityChanged(availability, activity);
}
}

2) Call Lync API methods to find out user’s availability:

internal ContactAvailability GetCurrentUserAvailability()
{
// Microsoft Lync Client is not even available
if (!Connected)
{
return ContactAvailability.Offline;
}

// in case of signing out or re-signing, self contact is already released
if (m_lyncClient.State == ClientState.SigningOut || m_lyncClient.State == ClientState.SignedOut || m_lyncClient.State == ClientState.SigningIn)
{
return ContactAvailability.Offline;
}

Contact self = m_lyncClient.Self.Contact;

//get actual contactModel availability (Will be int value within OCOM availaiblity ranges)
return (ContactAvailability)self.GetContactInformation(ContactInformationType.Availability);
}
Please note that here I check whether some Availability has changed for the currently logged-in user, and also I retrieve Activity state. This is necessary, especially if you want to track the exact state of user’s presence.

As you probably know, ContactAvailability is an enumeration which enlists the following values:

  • Invalid (-1),
  • None (0) – Do not use this enumerator. This flag indicates that the cotact state is unspecified.,
  • Free (3500) – A flag indicating that the contact is available,
  • FreeIdle (5000) – Contact is free but inactive,
  • Busy (6500) – A flag indicating that the contact is busy and inactive,
  • BusyIdle (7500) – Contact is busy but inactive,
  • DoNotDisturb (9500) – A flag indicating that the contact does not want to be disturbed,
  • TemporarilyAway (12500) – A flag indicating that the contact is temporarily away,
  • Away (15500) – A flag indicating that the contact is away,
  • Offline (18500) – A flag indicating that the contact is signed out.

But if you look at the presence states you can choose from the presence chooser combo inside the Lync Client, there are some other states:

  • Available,
  • Busy,
  • Do Not Disturb,
  • Be Right Back,
  • Off Work,
  • Appear Away.

So, which status corresponds to which, and can we synchronize them?

First, you should be aware of idle states. These states cannot be published, since they are set automatically by Lync Client. You can modify time needed to trigger these states inside the Options window. There are two idle states: Inactive and Away. Both do the same thing, only difference is time when they are automatically triggered.

Next, you can match those states which do an exact match, such as: Free (matches to Available), Busy (matches to Busy), DoNotDisturb (matches to Do Not Disturb), and Away (matches to Appear Away - but it is displayed as Away!).

So, what about the others? Well, for this purpose, you should use Activity state. The problem is, as stated in SDK’s documentation, it’s ”A token describing current contact activity. Contact information item value type is String.” Unfortunatelly, you should match strings to be able to find out which status exactly is it. For example, Be Right Back state should resemble ContactAvailability of TemporarilyAway and Activity of “BeRightBack”; Off Work state should resemble ContactAvailability of Away and Activity of “OffWork”, etc.

All these present should be sufficient to be able to correctly set the user’s availability, as presented below:
internal void SetCurrentUserAvailability(ContactAvailability newState, string activityId)
{
// Microsoft Lync Client is not even available
if (Connected)
{
Dictionary publishData = new Dictionary();
publishData.Add(PublishableContactInformationType.Availability, newState);

if (!string.IsNullOrEmpty(activityId))
{
publishData.Add(PublishableContactInformationType.ActivityId, activityId);
}

object[] asyncState = { m_lyncClient.Self };

m_lyncClient.Self.BeginPublishContactInformation(publishData, null, asyncState);
}
}

I hope you find this post useful. Kind regards.

Posted by: rcosic | 09/11/2011

Handling conference calls in Lync Client

Note: this post is part of Lync Client development series. You can find the following topics here:

Hello everyone!

This time, I’ll deal with some scenarios concerning conference calls. As you know, there are lots of ways you can start a conference: you can do it implicitly by clicking on ‘Meet Now’ button (on a main menu or when clicking Options button), or explicitly by inviting someone else in an existing conversation. In either way, a conference call is a special call (not just by number of participants) that could/should be handled.

First of all, as with a call transfer, there is no special state on a conversation level to indicate we are in a conference. Also, this is the case with an active audio/video modality. But, let’s see what can we track to be able to see how is conference going. We will be looking at one typical scenario: imagine a user who calls another user, and after that, this second user invites another user into this very conversation (albeight, audio call). In this moment a conference is being initiated and participants changed. Let’s see a workflow for it:

Scenario: user1 calls user2 , which invites user3 into the call (makes a conference).

This is what happens from the side of user2 (user which actually creates a conference out of an incoming call):

  • user2 clicks on ‘People Options’ button and clicks on ‘Show Participant List’ to see who is in a call, and then clicks on ‘Invite by Name or Phone Number…’ option to invite user3,
  • conversation’s property ConferenceEscalationProgress is changed to 2 (ConnectingToConference),
  • ConferenceEscalationProgress = 4 (JoiningLocalMedia),
  • ConferenceEscalationProgress = 6 (WaitingForPeer),
  • user’s availability changes to Busy (activity changes to: In a conference call),
  • AVModalityState = OnHold,
  • ConferenceEscalationProgress = 7 (Completed),
  • ConferenceEscalationResult = 0 (without errors),
  • ConferencingUri = sip address of user1,
  • ConferenceAccessInformation property is available,
  • AVModalityState = Connected,
  • ConversationState = Inactive,
  • AVModalityState = Disconnected.

If you notice the changes in the GUI, firstly, picked (invited) user is in the Attendees list, and when he accepts the conference call, he is in the Presenters list, together with the user1 and user2. We will not deal with the details of sorts of participants in this post, but it is notable to say that it also could/should be handled if necessary.

After user2 ends the call/conference, ConversationState changes to Inactive state, and AVModaltityState to Disconnected state, which is quite understandable. On the conversation window a notification panel appears (in yellowish color) to indicate that ’You have left the call’, whit an option to ‘Rejoin’ the conference.

ConferenceEscalationProgress is not changed after that, which is important, so you have to keep track on the active modality and conversation to know whether a conference has ended or still active.

So, from the perspective of a second user, what could we do to keep track on a conference call? We should store two important conversation properties – ConferenceEscalationProgress and ConferencingUri – when they are changed. Next, when audio call gets Connecting, we should query this Uri property and set the call to an incoming type and calculate values for the call.

And now, let’s see what happens from the side of user3 (user which is invited to a conference – as a third participant):

  • new conversation added,
  • new audio/video call registered,
  • Conversation.ConferencingUri = sip address of user2,
  • Conversation.Inviter = the same as previous,
  • ConversationState = Active,
  • AVModalityState = Notified (this indicates an incoming call from user2),
  • invitation is accepted,
  • ConversationState = Inactive,
  • AVModalityState = Connecting,
  • ConferenceEscalationProgress = 2 (ConnectingToConference),
  • ConferenceEscalationProgress = 4 (JoiningLocalMedia),
  • ConversationState = Active,
  • AVModalityState = Joining,
  • AVModalityState = Connected,
  • ConferenceEscalationProgress = 7 (Completed),
  • ConferenceEscalationResult = 0 (without errors),
  • user’s availability changes to Busy (activity changes to: In a conference call).

Notice couple of things in this workflow: ConferencingUri property is (luckily) set very early in stack, so this should be indication that there will be a conference call pending. There is no OnHold (or Transferring) events, but rather some sort of ’mix’ of events that usually indicate an incoming/outgoing call: Notified -> Connecting -> Joining -> Connected. So, you have to be very careful when dealing with these. Additionaly, there is no ConferenceEscalationProgress change to value 6 (WaitingForPeer), since this in an ‘incoming conference invitation’. And lastly, user’s availability status change is now last in the stack (i.e. when conference escalation is completed).

As you could see, a conference call is quite different from other sorts of calls, and should be treated differently. There are different kinds of conference call scenarios which are not taken into account in this post. And lastly, note that even when a single participant stays in a conference, conference is active (user is in state ‘In a conference call’), talking to himself.

Posted by: rcosic | 08/11/2011

Handling transfers in Lync Client API

Note: this post is part of Lync Client development series. You can find the following topics here:

Hello! As promised, I’ll talk about transferring calls when developing in Lync Client API.

First of all, let me tell you this – it is just not straightforward, as you may seem. There is no “Transferred” state within conversation states (only thing besides Inactive, Active, Invalid and Terminated state, you have Parked state, which is something really different). Also, although there is a Transferring state for audio/video modality available, it is not quite clear when and if this event arises. For more precise explanation, we should revise the call workflow Lync does when doing a call transfer…

In Lync, like in any other system, you can achive a blind or a supervised transfer. Blind transfer means that a call will be transferred to a target user without knowing whether the target user will be able to accept the call. Supervised (or consultative) transfer means that a calling user will be first put ‘on hold’ (either implicitly or automatically by Lync during establishing a second call), another user will be called and queried whether he is available to pick up the call. After that, first call will be transferred to the target user. Let’s see some scenarios…

First scenario: user1 calls user2, who will in turn call another user (user3), and transfer the first call to him. This is the case of a blind transfer.

The first user is not interesting at all, since the first call is actually simple outgoing call to another user. So, we’ll skip the explanation of it. But, let’s see what happens from the perspective of a second user in this workflow (user2):

  • new converstation is added,
  • new audio/video call is registered,
  • ConversationState = Active,
  • AVModalityState = Notified, (this is the indication of an incoming call),
  • AVModalityState = Joining,
  • AVModalityState = Connected,
  • user’s availability changes to Busy (‘In a call’),
  • AVModalityState = OnHold,
  • AVModalityState = Transferring,
  • ConversationState = Inactive,
  • AVModalityState = Disconnected,
  • Call is ended,
  • user’s availability is changed to Free (‘Available’),
  • ConversationState = Terminated, and
  • conversation is removed.

So far, understandable: a call has been put on hold (automatically by Lync), marked as being transferred, and then deactivated and terminated. But, what happens on the side of ‘third participant’ (user3) ?

  • new converstation is added,
  • new audio/video call is registered,
  • ConversationState = Active,
  • AVModalityState = Notified, (this is the indication of an incoming call),
  • AVModalityState = Joining,
  • AVModalityState = Connected,
  • user’s availability changes to Busy (‘In a call’),
  • ….

If you see the wokflow, nothing special is telling us that this call was originated from transferring the call to us. So, if you want to handle it, you have to think about something else…

As you know, Notified modality state tells us that a call should be incoming call. When this event happens, it is suitable to figure out what we also have in place to figure out if this is a transferred call or not. For this, I will first extract participants of the call: firstly, myself as a SelfParticipant and secondly, another participant. When another (remote) participant is referred to another party, there is a property on participant object holding this information – ReferredByUri. Unluckily, there is only an URI representing the actual contact, but it is enough for most cases. So, this would be clear indication that a call is transferred to here and then we can query some other properties, but now on underlying conversation object – TransferredBy property, which is filled in case of a blind transfer, and Replaced property in case of a supervised transfer. Note, though, that this Replaced property actually applies to a conference, but it is also filled in this special case. After this figured out, I just fill the internal variables to store participants in more meaningful way: a caller, a called party, and a connected party, all representing by its URIs. Take a look on the following code snippet:

Participant selfParticipant = m_conversation.SelfParticipant;
Participant anotherParticipant = m_conversation.Participants[1];

// get the transferrer’s URI when a remote participant was referred by another party
if (anotherParticipant.Properties.ContainsKey(ParticipantProperty.ReferredByUri))
{
// this is an indication of some type of transfer
if (anotherParticipant.Properties[ParticipantProperty.ReferredByUri] != null)
{
m_strTransferredById = anotherParticipant.Properties[ParticipantProperty.ReferredByUri] as string;
}

// the call has been transferred to here

// get the Id of the conversation that originated the transfer
if (m_conversation.Properties.ContainsKey(ConversationProperty.TransferredBy))
{
// this should be an indication of a blind transfer
if (m_conversation.Properties[ConversationProperty.TransferredBy] != null)
{
m_strRelatedConversationId = m_conversation.Properties[ConversationProperty.TransferredBy] as string;
}
}

// get the Id of the conference that originated the transfer
if (m_conversation.Properties.ContainsKey(ConversationProperty.Replaced))
{
// this should be an indication of a supervised (consultative) transfer
if (m_conversation.Properties[ConversationProperty.Replaced] != null)
{
m_strRelatedConversationId = m_conversation.Properties[ConversationProperty.Replaced] as string;
}
}

m_bIsTransferred = true;
}
else
{
m_bIsTransferred = false;
}

if (m_eCallDirection == ELyncCallDirection.CALL_DIRECTION_OUTGOING)
{
m_strCallerId = selfParticipant.Contact.Uri;

// in case we have been transferred to another user
if (m_bIsTransferred)
{
m_strCalledId = m_strTransferredById;
m_strConnectedId = anotherParticipant.Contact.Uri;
}
else
{
m_strCalledId = anotherParticipant.Contact.Uri;
m_strConnectedId = m_strCalledId;
}
}
else if (m_eCallDirection == ELyncCallDirection.CALL_DIRECTION_INCOMING)
{
// in case of call being transferred to us
if (m_bIsTransferred)
{
m_strCallerId = m_strTransferredById;
m_strCalledId = selfParticipant.Contact.Uri;
m_strConnectedId = anotherParticipant.Contact.Uri;
}
else
{
m_strCallerId = anotherParticipant.Contact.Uri;
m_strCalledId = selfParticipant.Contact.Uri;
m_strConnectedId = m_strCallerId;
}
}

Another thing to consider, when handling transfers, is to keep track of active conversations (and windows).

When you are a ‘second party’ (user who handle the transfer), you will have to consider two (or more) calls/conversations to handle on the same time. This also includes figuring out which conversation is bound to which conversation, and how to properly handle the ‘switching’ between them. To make it short, a second conversation is created when there is a second call. In this point of time, you might to react on it to remove it and handle it as the same ‘logical conversation’, which has its own flow. For that to make, you have to consider that first conversation gets on hold, deactivated and finally disconnected. And that, in all this time, some error or rejection might occur. It’s up to you to handle it properly and try to think about all possible cases.

Older Posts »

Categories

Follow

Get every new post delivered to your Inbox.