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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s