Export (0) Print
Expand All

Working with Presence and Groups in UCMA 3.0: Finding a Contact (Part 3 of 5)

Summary:   Learn how to use presence to find an available member of a Microsoft Lync 2010 contact group, and then add the contact to a multiparty group conversation.

After the UCMA 3.0 application starts, it waits for an instant message from a Lync 2010 user. When an instant message arrives, the UCMA 3.0 application responds with an instant message that requests the Lync 2010 user to press 1 to be connected with the Service Department, or press 2 to be connected with the Sales Department.

The following example lists the declarations of global variables that are used in the rest of this article.

// GroupID constants for the Lync contact groups that are used in this application.
enum GroupID
{
  SERVICE_GROUP = 3, // Service Department group.
  SALES_GROUP = 4    // Sales Department group.
};
private UCMASampleHelper _helper;
private UserEndpoint _userEndpoint;
private Uri _remoteContactUri;
private InstantMessagingCall _instantMessagingCall;
private RemotePresenceView _remotePresenceView;
private bool _isFirstMessage = true;
private Conversation _incomingConversation;
private GroupID _groupID;
private AutoResetEvent _waitUntilIncomingCallIsAccepted = new AutoResetEvent(false);
NoteNote

The code for the application described in this series of articles appears in Working with Presence and Groups in UCMA 3.0: Code Listing and Conclusion (Part 5 of 5).

The UCMA 3.0 application performs the steps in the following list when it presents the menu to the Lync 2010 user.

  1. Create and establish an endpoint for the application.

    _userEndpoint = _helper.CreateEstablishedUserEndpoint("FindContact Sample User");
    

    The CreateEstablishedUserEndpoint method is defined in UCMASampleHelper.cs. The UCMASampleHelper.cs file is part of the UCMA 3.0 Core SDK.

  2. Register a delegate to be called when an incoming instant message arrives.

    _userEndpoint.RegisterForIncomingCall<InstantMessagingCall>(InstantMessagingCall_Received);
    

    When an incoming instant message arrives, the InstantMessagingCall_Received delegate accepts the instant message call. This method is described in the next section.

  3. Pause the main thread.

    _waitUntilIncomingCallIsAccepted.WaitOne();
    

    The thread will resume operation when an incoming instant message is accepted.

  4. After the incoming instant message is accepted, send an instant message to the caller, by using the BeginSendInstantMessage method on the InstantMessagingFlow instance, which can be obtained from the Flow property on the InstantMessagingCall instance. This instant message presents a simple menu to the Lync 2010 user.

    InstantMessagingFlow imFlow = _instantMessagingCall.Flow;
    imFlow.BeginSendInstantMessage("Press 1 for Service Department.\n” +
              ”Press 2 for Sales Department.", CallSendInstantMessageCB, _instantMessagingCall);
    
    

    The CallSendInstantMessageCB callback method is discussed in the next section.

  5. Register to receive notifications of the MessageReceived event on the InstantMessagingFlow instance. When the MessageReceived event is raised, the IMFLow_MessageReceived event handler is invoked.

    imFlow.MessageReceived += new EventHandler<InstantMessageReceivedEventArgs>(IMFlow_MessageReceived);
    

This section describes the callback methods and event handlers that are associated with receiving an incoming instant message, and presenting a simple menu to the Lync 2010 user.

InstantMessagingCall_Received Delegate

The InstantMessagingCall_Received delegate is invoked when the UCMA 3.0 application receives an instant message. The InstantMessagingCall_Received delegate performs the following four principal tasks.

  • The _instantMessagingCall global variable is set to the value in the Call property on the e parameter.

  • The _incomingConversation global variable is set to the value in the Conversation property on _instantMessagingCall. The _incomingConversation variable is used later, when the conversation is escalated to a conference session.

  • A handler is registered for the StateChanged event on the InstantMessagingCall instance

  • The BeginAccept method on the InstantMessagingCall instance is called.

The following example shows the definition of this delegate.

void InstantMessagingCall_Received(object sender, CallReceivedEventArgs<InstantMessagingCall> e)
{
  // Type checking was done by the platform; no risk of this being any 
  // type other than the type expected.
  _instantMessagingCall = e.Call;
  _incomingConversation = _instantMessagingCall.Conversation;

  // Call: StateChanged: Hooked up for logging, to show call state transitions.
  _instantMessagingCall.StateChanged += 
       new EventHandler<CallStateChangedEventArgs>(InstantMessagingCall_StateChanged);
  
  // RemoteParticipantUri is the URI of the remote caller in this 
  // conversation. Toast is the message set by the caller as the 
  // 'greet' message for this call. 
  Console.WriteLine("Call Received! From: " + e.RemoteParticipant.Uri + " Toast is: " + 
       e.ToastMessage.Message);
  
  // Accept the call.
  _instantMessagingCall.BeginAccept(CallAcceptCB, _instantMessagingCall);
}

CallAcceptCB Method

The following example is the definition of the CallAcceptCB callback method. In this method the EndAccept method on the InstantMessagingCall instance is called. Before the method returns, the Set method on the AutoResetEvent instance is called. This action causes the main thread to resume execution. The main thread was paused by a call to WaitOne in step 3 in the previous section.

private void CallAcceptCB(IAsyncResult ar)
{
  InstantMessagingCall instantMessagingCall = ar.AsyncState as InstantMessagingCall;
  try
  {
    // Determine whether the IM Call was accepted successfully.
    instantMessagingCall.EndAccept(ar);
  }
  catch (RealTimeException exception)
  {
    // RealTimeException can be thrown on media or link-layer failures. 
    Console.WriteLine(exception.ToString());
  }
  finally
  {
    // Synchronize with main thread.
    _waitUntilIncomingCallIsAccepted.Set();
  }
}

CallSendInstantMessageCB Method

The CallSendInstantMessageCB callback method calls EndSendInstantMessage on the InstantMessagingFlow instance.

The following example is the definition of the CallSendInstantMessageCB method.

private void CallSendInstantMessageCB(IAsyncResult ar)
{
  InstantMessagingCall imCall = ar.AsyncState as InstantMessagingCall;
  try
  {
    imCall.Flow.EndSendInstantMessage(ar);
  }
  // A production application should have catch blocks for a number of
  // other exceptions, including FailureResponseException, ServerPolicyException, 
  // and OperationTimeoutException.
  catch (RealTimeException exception)
  {
    Console.WriteLine(exception.ToString());
  }
}

IMFlow_MessageReceived Event Handler

The IMFlow_MessageReceived delegate is invoked when the MessageReceived event is raised on the InstantMessagingFlow instance.

The IMFlow_MessageReceived method takes no action other than to write a message in the console the first time that it is invoked. When it is invoked for the second time, it expects the TextBody property to contain either “1” (for the Service Department) or “2” (for the Sales Department). For either of these values, this method sets a global variable, _groupID, to one of the values in the GroupID enumeration. After this method sets _groupID, it calls the GetFirstAvailableContact method, which is discussed in the next section.

Caution note Caution

The first if block in the following example is not thread-safe. If two instant messages arrive at nearly the same time, it is possible that both can be considered the first message.

The following example is the definition of the IMFlow_MessageReceived method.

void IMFlow_MessageReceived(object sender, InstantMessageReceivedEventArgs e)
{
  // If this is the first message, set _isFirstMessage to false and return.
  if (_isFirstMessage)
  {
    _isFirstMessage = false;
    Console.WriteLine("First message has arrived.");
    return;
  }
  
  // _isFirstMessage must be false, so look for a menu choice of 1 or 2.
  string choice = e.TextBody;
  Uri contact = null;
  
  if (choice.Equals("1"))
  {
    // Contact someone in the Service Department.
    _groupID = GroupID.SERVICE_GROUP;
  }
  else if (choice.Equals("2"))
  {
    // Contact someone in the Sales Department.
    _groupID = GroupID.SALES_GROUP;
  }
  contact = GetFirstAvailableContact();
}

After the Lync 2010 user chooses to be connected to the Service Department or the Sales Department, the next task for the UCMA 3.0 application is to find an available contact in the Service Department group or the Sales Department group.

To find an available contact in a specified group, the application subscribes to presence notifications from all of the contacts in the specified group. When the application receives presence notifications from these contacts, it looks for the first contact whose presence state is Online. Many of these actions are collected in a helper method, the GetFirstAvailableContact method.

The GetFirstAvailableContact method performs the following tasks.

  1. Create a RemotePresenceView instance.

    The RemotePresenceView instance will be used to subscribe to the presence notifications of all of the contacts in a particular contact group.

  2. Register to receive notifications of the PresenceNotificationReceived event on the RemotePresenceView instance.

  3. Register to receive notifications of the NotificationReceived on a ContactGroupServices instance. The ContactGroupServices property on a UserEndpoint instance is a reference to a ContactGroupServices instance.

    The NotificationReceived event is raised when there are changes in the presence state of a contact or group that is subscribed to.

  4. Subscribe for presence notifications, by calling BeginSubscribe on the ContactGroupServices reference on the UserEndpoint instance.

  5. Return the URI of an available contact.

The following example shows the definition of the GetFirstAvailableContact method.

private Uri GetFirstAvailableContact()
{
  RemotePresenceViewSettings presenceViewSettings = new RemotePresenceViewSettings();
  presenceViewSettings.SubscriptionMode = RemotePresenceViewSubscriptionMode.Persistent;
  _remotePresenceView = new RemotePresenceView(_userEndpoint, presenceViewSettings);
  
  _remotePresenceView.PresenceNotificationReceived += new EventHandler<
       RemotePresentitiesNotificationEventArgs>(RemotePresenceView_PresenceNotificationReceived);
  _userEndpoint.ContactGroupServices.NotificationReceived += new EventHandler
       <Microsoft.Rtc.Collaboration.ContactsGroups.ContactGroupNotificationEventArgs>(
              ContactGroupServices_NotificationReceived);
  _userEndpoint.ContactGroupServices.BeginSubscribe(ContactGroupSubscribeCB,
       _userEndpoint.ContactGroupServices);
  return _remoteContactUri;
}

The goal of the example that is presented in this series of articles is to show how to work with presence and contact groups, and is less concerned about efficiency. A production-quality application would not make presence subscriptions and queries for each call that arrives. Instead, it could maintain two lists of contacts, one for each contact group, and periodically update each list to find the available agents in each group.

This section describes the event handlers and callback methods that are associated with the GetFirstAvailableContact method.

ContactGroupServices_NotificationReceived Event Handler

The ContactGroupServices_NotificationReceived event handler is invoked when the NotificationReceived event on a ContactGroupServices instance is raised. The ContactGroupServices property on an endpoint is a reference to a ContactGroupServices object. The NotificationReceived event is raised when there is a change in an endpoint’s contacts or groups.

This method first creates a variable named targets that is an empty List of RemotePresentitySubscriptionTarget elements. The method then inspects each contact to determine whether this contact is in the group that corresponds to the user’s menu choice. If the contact is in the selected group, the contact is added to the targets list. After all of the contacts are inspected, the StartSubscribingToPresentities method on the _remotePresenceView object is called.

The following example shows the definition of the ContactGroupServices_NotificationReceived method.

private void ContactGroupServices_NotificationReceived(object sender,
     ContactsGroups.ContactGroupNotificationEventArgs e)
{
  List<RemotePresentitySubscriptionTarget>targets = new List<RemotePresentitySubscriptionTarget>();
  foreach (NotificationItem<ContactsGroups.Contact> contactNotification in e.Contacts)
  {
    if (contactNotification.Operation == PublishOperation.Add)
    {
      foreach (int groupID in contactNotification.Item.GroupIds)
      {
        if (groupID == (int)_groupID)
        {
          targets.Add(new RemotePresentitySubscriptionTarget(contactNotification.Item.Uri));
        }
      }
    }
  } 
  _remotePresenceView.StartSubscribingToPresentities(targets);
}

RemotePresenceView_PresenceNotificationReceived Event Handler

The RemotePresenceView_PresenceNotificationReceived event handler is invoked when the PresenceNotificationReceived event is raised, which occurs when presence state changes for the targets of the RemotePresenceView subscription.

This method iterates through all of the elements in the Notifications collection, searching for a presentity whose presence availability is Online, a value of the PresenceAvailability enumeration. If such a contact is found, this method sets the global variable _remoteContactUri to the value of the presentity’s PresentityUri property. Before this method exits, it calls the Set method on the _waitForAvailableTarget object, which allows the main thread to resume operation.

The following example shows the definition of the RemotePresenceView_PresenceNotificationReceived method.

private void RemotePresenceView_PresenceNotificationReceived(object sender,
      RemotePresentitiesNotificationEventArgs e)
{
  // The Notifications property contains all notifications for one user.
  foreach (RemotePresentityNotification notification in e.Notifications)
  {
    if ( (notification.AggregatedPresenceState != null) && 
         (notification.AggregatedPresenceState.Availability == PresenceAvailability.Online))
    {
      _remoteContactUri = new Uri(notification.PresentityUri);
      Console.WriteLine("Remote target URI: " + _remoteContactUri.ToString());
      // Can break out of loop after an available contact is found.
      break;
    }
  }
  _waitForAvailableTarget.Set();
}

ContactGroupSubscribeCB Callback Method

The ContactGroupSubscribeCB callback method calls the EndSubscribe method on a ContactGroupServices instance.

The following example shows the definition of the ContactGroupSubscribeCB method.

private void ContactGroupSubscribeCB(IAsyncResult ar)
{
  ContactGroupServices services = ar.AsyncState as ContactGroupServices;
  try
  {
    services.EndSubscribe(ar);
  }
  // A production application should have catch blocks for a number 
  // of other exceptions, including OperationFailureException and PublishSubscribeException.
  catch (RealTimeException exception)
  {
    Console.WriteLine("Contact Group Subscription failed due to exception: {0}",
                    exception.ToString());
  }
}

Mark Parker is a programming writer at Microsoft whose current responsibility is the UCMA SDK documentation.

Show:
© 2014 Microsoft