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.

Advertisements

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);