Reproducing Automatic Presence with the UCC API – Part 2

My last post detailed how to monitor user activity and update the user’s state when they become inactive.  With that code, we can switch between Available, Inactive, and Away states based on user input via the keyboard or mouse.  The next step is to notify OCS when the user locks their machine, by changing the user’s availability to Away.  To do this, we’re going to need to use P/Invoke again, this time calling the WTSRegisterSessionNotification function, part of WtsApi32.dll.  This will only work on a Windows XP or newer OS.  This function takes a handle to a window we own, which is then notified of session changes by the WM_WTSSESSION_CHANGE message.  To receive that notification we need a class that can hook into the windows message loop, something derived from System.Windows.Forms.Control, which I added as a private nested class in the PresenceManager:

        /// <summary>

        /// Registers for session changes for this session by calling

        /// WTSRegisterSessionNotification.  Provides events that

        /// notify when the machine has been locked/unlocked.

        /// </summary>

        private sealed class SessionChangeHandler : Control

        {

            [DllImport(“WtsApi32.dll”, SetLastError = true)]

            [return: MarshalAs(UnmanagedType.Bool)]

            private static extern bool WTSRegisterSessionNotification(

                IntPtr hWnd, [MarshalAs(UnmanagedType.U4)]int dwFlags);

            [DllImport(“WtsApi32.dll”, SetLastError = true)]

            [return: MarshalAs(UnmanagedType.Bool)]

            private static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd);

 

            private const int NOTIFY_FOR_THIS_SESSION = 0;

            private const int WM_WTSSESSION_CHANGE = 0x2b1;

            private const int WTS_SESSION_LOCK = 0x7;

            private const int WTS_SESSION_UNLOCK = 0x8;

 

            /// <summary>

            /// Raised when the machine has been locked.

            /// </summary>

            public event EventHandler MachineLocked;

            /// <summary>

            /// Raised when the machine has been unlocked.

            /// </summary>

            public event EventHandler MachineUnlocked;

 

            public SessionChangeHandler()

            {

                if (!WTSRegisterSessionNotification(this.Handle, NOTIFY_FOR_THIS_SESSION))

                {

                    Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

                }

            }

 

            protected override void WndProc(ref Message m)

            {

                if (m.Msg == WM_WTSSESSION_CHANGE)

                {

                    int value = m.WParam.ToInt32();

                    if (value == WTS_SESSION_LOCK)

                    {

                        OnMachineLocked(EventArgs.Empty);

                    }

                    else if (value == WTS_SESSION_UNLOCK)

                    {

                        OnMachineUnlocked(EventArgs.Empty);

                    }

                }

                base.WndProc(ref m);

            }

 

            protected override void OnHandleDestroyed(EventArgs e)

            {

                // unregister this instance before it’s destroyed

                if (!WTSUnRegisterSessionNotification(this.Handle))

                {

                    Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

                }

 

                base.OnHandleDestroyed(e);

            }

 

            private void OnMachineLocked(EventArgs e)

            {

                EventHandler temp = MachineLocked;

                if (temp != null)

                {

                    temp(this, e);

                }

            }

            private void OnMachineUnlocked(EventArgs e)

            {

                EventHandler temp = MachineUnlocked;

                if (temp != null)

                {

                    temp(this, e);

                }

            }

        }

 

Surprisingly, WTSRegisterSessionNotification was not yet documented at pinvoke.net, so I added a page for it.  The SessionChangeHandler registers itself when it is constructing, and makes a corresponding call to WTSUnRegisterSessionNotification just before the handle is destroyed.  Any time the machine is locked or unlocked, the appropiate event is fired, which the PresenceManager can easily handle:

 

        private void MachineLocked(object sender, EventArgs e)

        {

            // disable the timer and notify OCS that the user is away

            myLastInputTimer.Change(Timeout.Infinite, Timeout.Infinite);

            this.CurrentAvailability = Availability.Away;

        }

        private void MachineUnlocked(object sender, EventArgs e)

        {

            // change the machine’s state back to available and re-enable the timer

            this.CurrentAvailability = Availability.Away;

            myLastInputTimer.Change(2000, 2000);

        }

We can use the same code to publish the presence state to OCS as we did in the timer callback, which I’ll cover in part three.  If you’re not sure it worked, adding a few calls to Debug.WriteLine can help.  At this point, the PresenceManager handles availability the same way we are used to seeing from other IM clients.  It just needs to be instantiated and kept somewhere, and it will handle automatic presence until disposed.  In the last part of this series I’ll cover publishing presence state to OCS, and the issues I encountered with the UCC API.

Reproducing Automatic Presence with the UCC API – Part 1

Most popular IM clients, including Communicator 2007, automatically modify the user’s status when they are inactive or away.  While piloting Office Communications Server we created a simple application that had the main features of Communicator (chat, A/V, presence, etc.) to show that we could incorporate that functionality into our in-house applications so that our agents only have to work with one piece of software.  The presence functionality is important to us, as we don’t want to route inbound calls from Speech Server to an agent who isn’t available.  As part of the pilot, I created a simple presence manager to monitor user activity and update availability when necessary.  After five minutes of inactivity, a state value of Inactive is published.  After 15 minutes, a state value of Away is published.  Finally, if the user locks the machine, a state value of Away is published.

Basically, there are two different things the manager needs to detect: the last time there was input (keyboard/mouse) to the machine, and when Windows is locked.  After a little digging, I found the easiest way to achieve our first goal was to P/Invoke the GetLastInputInfo function, which returns the time of the last keyboard/mouse activity.  Using a timer, we can call this method as often as necessary to check if our thresholds (five and 15 minutes) have been reached.  For the sake of simplicity, I removed the code that publishes the state to OCS.  We could also easily add logic to check if the user is on the phone, so the “In a Call” state isn’t overwritten. 

NOTE: I’m using the System.Threading.Timer class.

    internal sealed class PresenceManager : IDisposable

    {

        [DllImport(“user32.dll”)]

        private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

 

        private Timer myLastInputTimer;

 

        public PresenceManager()

        {

            // initialize the timer to call back every two seconds

            myLastInputTimer = new Timer(CheckIdleTime, null, 2000, 2000);

        }

 

        /// <summary>

        /// Gets the current availability of the signed-in user.

        /// </summary>

        public Availability CurrentAvailability { get; private set; }

 

        /// <summary>

        /// Callback method for <see cref=”myLastInputTimer”/> to check the idle time

        /// of the computer and update the status if needed.

        /// </summary>

        /// <param name=”state”>The state, unused by this method.</param>

        private void CheckIdleTime(object state)

        {

            LASTINPUTINFO lastInputInfo = LASTINPUTINFO.Create();

            if (GetLastInputInfo(ref lastInputInfo))

            {

                // get the idle time by subtracting the tick count of the last input

                // event from the total tick count of the system

                int idleTicks = Environment.TickCount – lastInputInfo.dwTime;

                TimeSpan idleTime = TimeSpan.FromSeconds(idleTicks / 1000.0);

 

                Availability currentAvailability = this.CurrentAvailability;

                if (idleTime < TimeSpan.FromMinutes(5))

                {

                    if (currentAvailability != Availability.Available)

                    {

                        // publish available state to OCS here…

 

                        this.CurrentAvailability = Availability.Available;

                    }

                }

                else if (idleTime < TimeSpan.FromMinutes(15))

                {

                    if (currentAvailability != Availability.AvailableIdle

                        && currentAvailability != Availability.BusyIdle)

                    {

                        if (currentAvailability == Availability.Busy)

                        {

                            // publish busy-idle state to OCS here…

 

                            this.CurrentAvailability = Availability.BusyIdle;

                        }

                        else

                        {

                            // publish inactive state to OCS here…

 

                            this.CurrentAvailability = Availability.AvailableIdle;

                        }

                    }

                }

                else if (currentAvailability != Availability.Away)

                {

                    // publish away state to OCS here…

 

                    this.CurrentAvailability = Availability.Away;

                }

            }

        }

    }

 

Now we just need to define the LASTINPUTINFO structure that we pass in GetLastInputInfo.  I nested it inside the manager:

 

        /// <summary>

        /// Structure used by the <see cref=”GetLastInputInfo”/> method.

        /// </summary>

        [StructLayout(LayoutKind.Sequential)]

        private struct LASTINPUTINFO

        {

            private static readonly int size = Marshal.SizeOf(typeof(LASTINPUTINFO));

            /// <summary>

            /// Creates a new <see cref=”LASTINPUTINFO”/> initialized and ready to

            /// be used by the <see cref=”GetLastInputInfo”/> method.

            /// </summary>

            /// <returns></returns>

            public static LASTINPUTINFO Create()

            {

                LASTINPUTINFO lii = new LASTINPUTINFO();

                lii.cbSize = size;

                return lii;

            }

 

            /// <summary>

            /// The size of this instance, required by the

            /// <see cref=”GetLastInputInfo”/> method.

            /// </summary>

            [MarshalAs(UnmanagedType.U4)]

            public int cbSize;

            /// <summary>

            /// The system tick-count at the last recorded user input, set

            /// by the <see cref=”GetLastInputInfo”/> method.

            /// </summary>

            [MarshalAs(UnmanagedType.U4)]

            public int dwTime;

        }

 

GetLastInputInfo requires the cbSize field to be set before the method is called, so I added a Create method to return an instance ready to be uses.  The timer calls back on a thread retrieved from the thread pool, but we can publish to OCS from any thread.  I found that two seconds was a good interval that updated the state to Available in a reasonable time.  In the second post I’ll cover detecting when the machine is locked/unlocked using another P/Invoke operation.

Managing event subscriptions in UCC

The event model when interacting with COM interfaces is different from the .NET event model.  It is more similar to the Observer Pattern used in Java with “listeners” than delegates in .NET.  To listen for events raised by objects in the UCC API, you must advise the object of your implemented event “sink”.  The event handler methods are defined in the various interfaces in the UCC API that begin with an underscore, such as _IUccPlatformEvents. 

When you call Advise, you get a “cookie” that is used to call Unadvise at a later time.  Unadvise should always be called to prevent memory leaks, but what do we do with all the cookies in the meantime?  The documentation has a good example of registering for UCC events, but it uses a custom two-dimensional Dictionary2D, defined at the bottom of the sample, which basically uses a Dictionary of Dictionaries.  The outer dictionary stores an inner dictionary for each type of _IUccXXEvents interface subscribed to, and the inner dictionary stores the cookie for each object that raises the events listed in that interface.

While using a Dictionary2D is certainly one way to go, I decided I wanted a more lightweight cookie jar.  I took advantage of the Pair structure defined in Wintellect’s PowerCollections library to store the cookies in a single Dictionary.

        private static Dictionary<Pair<Type, object>, int> cookies = new Dictionary<Pair<Type, object>, int>();

 

        /// <summary>

        /// Advises the specified <paramref name=”source”/> that the specified

        /// <paramref name=”sink”/> is a registered event sink for events

        /// defined in <typeparamref name=”T”/>.

        /// </summary>

        /// <typeparam name=”T”>The interface defining the event handlers that

        /// are implemented by the <paramref name=”sink”/></typeparam>

        /// <param name=”source”>The source of the events.</param>

        /// <param name=”sink”>The event sink.</param>

        public static void Advise<T>(object source, T sink)

        {

            Pair<Type, object> key = new Pair<Type, object>(typeof(T), source);

 

            // Prevent the same sink from advising on a certain object/type combination more than once.

            // Otherwise, our event handler will be called multiple times, which can cause problems.

            if (!cookies.ContainsKey(key))

            {

                IConnectionPointContainer container = (IConnectionPointContainer)source;

                IConnectionPoint cp;

                int cookie;

                Guid guid = typeof(T).GUID;

 

                container.FindConnectionPoint(ref guid, out cp);

                cp.Advise(sink, out cookie);

 

                cookies.Add(key, cookie);

            }

        }

 

        public static void Unadvise<T>(object source)

        {

            // get the cached cookie for the source

            Pair<Type, object> key = new Pair<Type, object>(typeof(T), source);

            int cookie;

            if (cookies.TryGetValue(key, out cookie))

            {

                IConnectionPointContainer container = (IConnectionPointContainer)source;

                IConnectionPoint cp;

                Guid guid = typeof(T).GUID;

 

                container.FindConnectionPoint(ref guid, out cp);

                cp.Unadvise(cookie);

 

                // remove the cookie from the cached cookies

                cookies.Remove(key);

            }

        }

 

This dictionary uses the combination of the type of interface and the source of the event as its key.  We could also throw an exception if the key already exists, rather than simply doing nothing.  Adding synchronization is the last step we have to do.  It’s simple enough that I won’t cover it here.  When we’re done advising for the event is simple: Utilities.Advise<_IUccSessionEvents>(uccSession, this);

 

 

Greetings

Welcome to yet another technical blog.  My teammates at ExtendHealth, Inc. finally convinced me to start my own blog, and I figured it’d also be useful for me to record the various things I’ve done.  I’ll be detailing problems and solutions I encounter as I work on projects here at Extend, and my thoughts on working with various technologies.

At the moment I’m involved in a project utilizing the UCC API, a client-side API used to communicate with Office Communications Server directly from our own applications.  The API isn’t as straightforward and user-friendly as I’d like, and the documentation is lacking in some areas (I’ll go into more detail in a future post).  Many of my upcoming posts will be related to working with this API, and I hope to provide several usable code samples.

I hope my posts prove useful to others, and answer questions that can’t be found elsewhere on the Internet.  Take a look at the links on the side to see my teammate’s blogs.