ExtensionMethods.cs
using System.Threading.Tasks;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;
namespace LyncBreakReminder
{
public static class ExtensionMethods
{
public static Task StartupAsync(this CollaborationPlatform platform)
{
return Task.Factory.FromAsync(platform.BeginStartup,
platform.EndStartup, null);
}
public static Task ShutdownAsync
(this CollaborationPlatform platform)
{
return Task.Factory.FromAsync(platform.BeginShutdown,
platform.EndShutdown, null);
}
public static Task<SipResponseData> EstablishAsync(this
LocalEndpoint endpoint)
{
return Task<SipResponseData>.Factory.FromAsync(
endpoint.BeginEstablish,
endpoint.EndEstablish, null);
}
public static Task TerminateAsync(this LocalEndpoint endpoint)
{
return Task.Factory.FromAsync(endpoint.BeginTerminate,
endpoint.EndTerminate, null);
}
public static Task<CallMessageData> AcceptAsync(this Call call)
{
return Task<CallMessageData>.Factory.FromAsync(call.BeginAccept,
call.EndAccept, null);
}
public static Task<CallMessageData> EstablishAsync(this Call call,
string destinationUri, CallEstablishOptions options)
{
return Task<CallMessageData>.Factory.FromAsync(
call.BeginEstablish, call.EndEstablish,
destinationUri, options, null);
}
public static Task TerminateAsync(this Call call)
{
return Task.Factory.FromAsync(call.BeginTerminate,
call.EndTerminate, null);
}
public static Task<SendInstantMessageResult>
SendInstantMessageAsync(this InstantMessagingFlow flow,
string textBody)
{
return Task<SendInstantMessageResult>.Factory.FromAsync(
flow.BeginSendInstantMessage,
flow.EndSendInstantMessage, textBody, null);
}
}
}
BreakReminderUserAgent.cs
using System;
using System.Collections.Concurrent;
using System.Configuration;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;
namespace LyncBreakReminder
{
internal class BreakReminderUserAgent
{
CollaborationPlatform _platform;
ApplicationEndpoint _endpoint;
// Collection of break reminder sessions currently in use
readonly ConcurrentDictionary<string, BreakReminderSession> _sessions =
new ConcurrentDictionary<string,BreakReminderSession>();
internal async void Start()
{
string applicationId =
ConfigurationManager.AppSettings["applicationId"];
ProvisionedApplicationPlatformSettings settings =
new ProvisionedApplicationPlatformSettings("breakreminder",
applicationId);
_platform = new CollaborationPlatform(settings);
_platform.RegisterForApplicationEndpointSettings(
OnApplicationEndpointDiscovered);
try
{
await _platform.StartupAsync();
}
catch (InvalidOperationException iex)
{
Console.WriteLine(iex);
}
catch (RealTimeException rex)
{
Console.WriteLine(rex);
}
}
internal async void Stop()
{
try
{
await _endpoint.TerminateAsync();
await _platform.ShutdownAsync();
}
catch (InvalidOperationException iex)
{
Console.WriteLine(iex);
}
catch (RealTimeException rex)
{
Console.WriteLine(rex);
}
try
{
_endpoint.BeginTerminate(terminateAsyncResult =>
{
try
{
_endpoint.EndTerminate(terminateAsyncResult);
try
{
_platform.BeginShutdown(shutdownAsyncResult =>
{
try
{
_platform.EndShutdown(shutdownAsyncResult);
}
catch (RealTimeException ex)
{
Console.WriteLine(ex);
}
},
null);
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex);
}
}
catch (RealTimeException ex)
{
Console.WriteLine(ex);
}
},
null);
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex);
}
}
private async void OnApplicationEndpointDiscovered(object sender,
ApplicationEndpointSettingsDiscoveredEventArgs e)
{
_endpoint = new ApplicationEndpoint(_platform,
e.ApplicationEndpointSettings);
try
{
await _endpoint.EstablishAsync();
_endpoint.RegisterForIncomingCall<InstantMessagingCall>(
OnIncomingInstantMessagingCallReceived);
}
catch (InvalidOperationException iex)
{
Console.WriteLine(iex);
}
catch (RealTimeException rex)
{
Console.WriteLine(rex);
}
}
private void OnIncomingInstantMessagingCallReceived(object sender,
CallReceivedEventArgs<InstantMessagingCall> e)
{
// Grab the SIP URI of the user sending the IM.
string sipUri = e.Call.RemoteEndpoint.Participant.Uri;
// Create a new session to use if there isn't already one,
// and subscribe to session status changes in advance to avoid
// race conditions.
BreakReminderSession newSession = new BreakReminderSession(sipUri);
newSession.BreakReminderSessionStatusChanged +=
OnBreakReminderSessionStatusChanged;
// Get the existing session or add the new one in a threadsafe way.
BreakReminderSession sessionToUse = _sessions.GetOrAdd(sipUri,
newSession);
// Pass the IM along to the session.
sessionToUse.HandleMessage(e.Call, e.ToastMessage);
// If we didn't end up using the new session, unsubscribe our
// event handler to avoid memory leaks.
if (newSession != sessionToUse)
{
newSession.BreakReminderSessionStatusChanged -=
OnBreakReminderSessionStatusChanged;
}
}
void OnBreakReminderSessionStatusChanged(object sender,
BreakReminderSessionStatusChangedEventArgs e)
{
if (e.Status == BreakReminderSessionStatus.Terminated)
{
// If a session becomes terminated, remove it from the dictionary
// in a threadsafe manner and unsubscribe from events.
BreakReminderSession session = (BreakReminderSession)sender;
BreakReminderSession removedSession;
bool succeeded = _sessions.TryRemove(session.SipUri, out
removedSession);
session.BreakReminderSessionStatusChanged -=
OnBreakReminderSessionStatusChanged;
}
}
}
}
BreakReminderSession.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Collaboration.Presence;
using Microsoft.Rtc.Signaling;
namespace LyncBreakReminder
{
internal class BreakReminderSession
{
internal event EventHandler<BreakReminderSessionStatusChangedEventArgs>
BreakReminderSessionStatusChanged;
internal string SipUri { get; set; }
internal BreakReminderSessionStatus Status
{
get
{
return _status;
}
set
{
lock (_statusSyncObject)
{
BreakReminderSessionStatus oldStatus = _status;
_status = value;
if (_status != oldStatus)
{
if (BreakReminderSessionStatusChanged != null)
{
BreakReminderSessionStatusChanged(this,
new BreakReminderSessionStatusChangedEventArgs()
{
PreviousStatus = oldStatus,
Status = _status
});
}
}
}
}
}
private BreakReminderSessionStatus _status =
BreakReminderSessionStatus.New;
private LocalEndpoint _endpoint;
private RemotePresenceView _remotePresenceView;
private Timer _availableTimer;
private bool _availableTimerActive;
private int _minutesBetweenBreaks = 30;
private readonly object _timerSyncObject = new object();
private readonly object _statusSyncObject = new object();
private readonly object _monitoringSyncObject = new object();
internal BreakReminderSession(string startingSipUri)
{
SipUri = startingSipUri;
try
{
_minutesBetweenBreaks =
int.Parse(
ConfigurationManager.AppSettings["minutesBetweenBreaks"]);
}
catch (ConfigurationErrorsException)
{
Console.WriteLine("No minutes between breaks setting found.");
}
catch (FormatException)
{
Console.WriteLine("Invalid minutes between breaks setting.");
}
}
internal async void HandleMessage(InstantMessagingCall call,
ToastMessage toast)
{
_endpoint = call.Conversation.Endpoint;
try
{
await call.AcceptAsync();
if (this.Status == BreakReminderSessionStatus.New)
{
await call.Flow.SendInstantMessageAsync(
"Starting break monitoring.");
await call.TerminateAsync();
StartBreakMonitoring();
}
else
{
await call.Flow.SendInstantMessageAsync(
"Ending break monitoring.");
await call.TerminateAsync();
StopBreakMonitoring();
}
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex);
}
catch (RealTimeException rex)
{
Console.WriteLine(rex);
}
}
private void StartBreakMonitoring()
{
lock (_monitoringSyncObject)
{
this.Status = BreakReminderSessionStatus.Initiating;
List<RemotePresentitySubscriptionTarget> targets =
new List<RemotePresentitySubscriptionTarget>();
targets.Add(new RemotePresentitySubscriptionTarget(this.SipUri));
_remotePresenceView = new RemotePresenceView(_endpoint);
_remotePresenceView.PresenceNotificationReceived +=
OnPresenceNotificationReceived;
_remotePresenceView.StartSubscribingToPresentities(targets);
this.Status = BreakReminderSessionStatus.Active;
}
}
void OnPresenceNotificationReceived(object sender,
RemotePresentitiesNotificationEventArgs e)
{
foreach (var notification in e.Notifications)
{
if (notification.AggregatedPresenceState.Availability ==
PresenceAvailability.Online)
{
StartAvailableTimer();
}
else
{
StopAvailableTimer();
}
}
}
private void StopBreakMonitoring()
{
lock (_monitoringSyncObject)
{
this.Status = BreakReminderSessionStatus.Terminating;
StopAvailableTimer();
List<string> targets = new List<string>();
targets.Add(this.SipUri);
_remotePresenceView.PresenceNotificationReceived -=
OnPresenceNotificationReceived;
_remotePresenceView.StartUnsubscribingToPresentities(targets);
this.Status = BreakReminderSessionStatus.Terminated;
}
}
private void StartAvailableTimer()
{
lock (_timerSyncObject)
{
if (_availableTimerActive)
{
Console.WriteLine(
"Did not start available timer -- already active.");
}
else
{
_availableTimerActive = true;
_availableTimer = new Timer(OnAvailableTimerElapsed, null,
TimeSpan.FromMinutes(_minutesBetweenBreaks),
TimeSpan.FromMinutes(_minutesBetweenBreaks));
}
}
}
private async void OnAvailableTimerElapsed(object state)
{
try
{
Conversation reminderConversation = new Conversation(_endpoint);
InstantMessagingCall reminderCall =
new InstantMessagingCall(reminderConversation);
await reminderCall.EstablishAsync(this.SipUri, null);
await reminderCall.Flow.SendInstantMessageAsync(
"This is your reminder to take a break.");
await reminderCall.TerminateAsync();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex);
}
catch (RealTimeException rex)
{
Console.WriteLine(rex);
}
}
private void StopAvailableTimer()
{
lock (_timerSyncObject)
{
if (!_availableTimerActive)
{
Console.WriteLine(
"Did not stop available timer -- not started.");
}
else
{
_availableTimer.Dispose();
_availableTimer = null;
_availableTimerActive = false;
}
}
}
}
internal enum BreakReminderSessionStatus
{
New,
Initiating,
Active,
Terminating,
Terminated
}
internal class BreakReminderSessionStatusChangedEventArgs : EventArgs
{
internal BreakReminderSessionStatus PreviousStatus { get; set; }
internal BreakReminderSessionStatus Status { get; set; }
}
}
Program.cs
using System;
namespace LyncBreakReminder
{
class Program
{
static void Main(string[] args)
{
BreakReminderUserAgent ua = new BreakReminderUserAgent();
ua.Start();
Console.WriteLine("started");
Console.ReadLine();
ua.Stop();
Console.WriteLine("stopping");
Console.ReadLine();
}
}
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="applicationId" value="urn:application:mgtest"/>
<add key="minutesBetweenBreaks" value="1"/>
</appSettings>
</configuration>