Raf's laboratory Abstracts Feed Raffaele Rialdi personal website

I am Raf logo

User activity detection on the Compact Framework

July 19, 2010
http://www.iamraf.net/Samples/User-activity-detection-on-the-Compact-Framework

While using the Compact Framework on a mobile device, you can need to invalidate some data if the user is inactive for a certain period of time. In my case I have a custom authentication system that create a custom Token class that is invalidated after few minutes the user is inactive.

Windows CE operating system offers two precious global named events called:

  • PowerManager/UserActivity_Active
  • PowerManager/UserActivity_Inactive

They are manual reset events, resetted from the operating system. If the user is active, the active event is set and the inactive is reset. The opposite happens once the user is inactive.

The inactivity event is triggered as soon as the user stops working. There is no delay at all. This means that if the user clicks a button, you will see the activity event immediately followed by inactivity event. Probably in your application you'll want to use a timer before invalidate some data. In this case you will want to use this lambda as the delegate parameter for the Start method:

(bool isActive) => myTimer.Enable = !isActive

I built a class that use a worker thread to monitor these events. But let me write few words about the implementation before looking at the raw code.

  1. PInvoke is used to create named events and wait for them. Compact Framework wrappers can't help in this specific case.
  2. Since the events are global, you should never set/reset them otherwise you can compromise the behavior of other applications/drivers.
  3. I used WaitForMultipleObjects in order to be able to promptly close the thread when requested. The first event in the array is used to quit the worker thread.
  4. In order to signal the change of the activity status, I decided to use the Control.Invoke method that requires the Windows.Form reference. I made this assumption since user activity implicate a user interface application.
  5. The delegate used to inform the change of activity status is an Action<bool> so that you can pass a lambda expression as parameter.

And here is the code!

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
 
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using System.ComponentModel;
 
// Raffaele Rialdi, http://www.iamraf.net, 2010
// This class use the Windows CE global events to know if there is user activity or not.
//
// You should expect that user activity is true for a very small period of time.
// In fact the user activity is true only when the user is currently typing, dragging the
// stylus on the screen or using the touch screen with the finger.
//
// For this reason, if you click on a button, you will see activity on immediately followed
// by activity off
//
// There is no delay (such as the one used to dim the screen) in user activity.
//
// Some comments about the class:
// - it is a component so that the Dispose pattern for derived classes is cleaner
// - it is tied to Windows Forms 'Control' since user activity makes more sense in a UI application
// - it uses Action<bool> so that you can directly pass a simple lambda expression like this:
//   (bool b) => myTimer.Enable = !b
 
/*
// Example.
// Put this code in a windows form project.
// The form has a listbox and a button to quit the thread that listen to user activity
        UserActivity _userActivity;
        private void Form1_Load(object sender, EventArgs e)
        {
            _userActivity = new UserActivity();
            _userActivity.Start(this, new Action<bool>(SignalActivity));
        }
        private void SignalActivity(bool IsUserActive)
        {
            string item;
            if (IsUserActive)
                item = "Active " + DateTime.Now.ToShortTimeString();
            else
                item = "Inactive " + DateTime.Now.ToShortTimeString();
            listBox1.Items.Add(item);
            listBox1.SelectedIndex = listBox1.Items.Count - 1;
        }
        private void btQuit_Click(object sender, EventArgs e)
        {
            _userActivity.Quit();
        }
 */
 
 
namespace DetectUserActivity
{
    public sealed class UserActivity : Component, IDisposable
    {
        #region PInvoke declarations
        private static readonly Int32 EVENT_ALL_ACCESS = 0x1F0003;
        private static readonly string Activity = "PowerManager/UserActivity_Active";
        private static readonly string Inactivity = "PowerManager/UserActivity_Inactive";
 
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr OpenEvent(int desiredAccess, bool inheritHandle, string name);
 
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr CreateEvent(IntPtr securityAttributes, bool IsManual, bool initialState, string name);
 
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool EventModify(IntPtr hEvent, UInt32 dEvent);
 
        private enum EventFlags
        {
            Pulse = 1,
            Reset = 2,
            Set = 3,
        }
 
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool CloseHandle(IntPtr handle);
 
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern int WaitForSingleObject(IntPtr handle, UInt32 dwMilliseconds);
 
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern int WaitForMultipleObjects(UInt32 nCount, IntPtr[] lpHandles, bool fWaitAll, UInt32 dwMilliseconds);
 
        // In WinCE SetEvent is a Macro that calls EventModify
        private static bool SetEvent(IntPtr hEvent)
        {
            return EventModify(hEvent, (UInt32)EventFlags.Set);
        } 
        #endregion
 
        #region Dispose Pattern for Derived Classes
        protected override void Dispose(bool bDisposing)
        {
            if (bDisposing)
            {
                // destroy Managed resources
            }
 
            // destroy Unmanaged resources
            SetEvent(QuitHandle);
 
            base.Dispose(bDisposing);
        }
        #endregion
 
        private IntPtr QuitHandle;
        private Action<bool> SignalActivity;
        private Control Control;
 
        /// <summary>
        /// Start monitoring the user activity
        /// </summary>
        /// <param name="control">Windows Form control used to invoke the delegate</param>
        /// <param name="signalActivity">delegate to call when the user activity changes</param>
        public void Start(Control control, Action<bool> signalActivity)
        {
            Control = control;
            SignalActivity = signalActivity;
            QuitHandle = CreateEvent(IntPtr.Zero, true, false, null);
            Thread t = new Thread(MonitorUserActivity);
            t.IsBackground = true;
            t.Start();
        }
 
        public void Quit()
        {
            this.Dispose();
        }
 
        public bool IsUserActive { get; private set; }
 
        private void MonitorUserActivity()
        {
            IntPtr activitySignal = OpenEvent(EVENT_ALL_ACCESS, false, Activity);
            IntPtr inactivitySignal = OpenEvent(EVENT_ALL_ACCESS, false, Inactivity);
 
            if (activitySignal == IntPtr.Zero || inactivitySignal == IntPtr.Zero)
            {
                return;
            }
 
            IntPtr[] evtActivity = new IntPtr[] { QuitHandle, activitySignal };
            IntPtr[] evtInactivity = new IntPtr[] { QuitHandle, inactivitySignal };
 
            // First we have to check for user activity without waiting
            // IsUserActive will be set only if currently there is user activity
            int result = WaitForSingleObject(activitySignal, 0);
            IsUserActive = (result == 0);  // result == 0 <==> object is signaled
 
            while (true)
            {
                if(IsUserActive)
                    result = WaitForMultipleObjects((uint)evtActivity.Length, evtInactivity, false, uint.MaxValue);
                else
                    result = WaitForMultipleObjects((uint)evtActivity.Length, evtActivity, false, uint.MaxValue);
 
                if (result == 0)
                {
                    CloseHandle(activitySignal);
                    CloseHandle(inactivitySignal);
                    CloseHandle(QuitHandle);
                    return;
                }
 
                if(result == -1)
                {
                    Trace.WriteLine("WaitForMultipleObjects returned WAIT_FAILED (-1)");
                    return;
                }
 
                Debug.Assert(result == 1, "result was already checked for the other possible values");
 
                IsUserActive = !IsUserActive;
                SetUserActivity(IsUserActive);
            }
        }
 
        private void SetUserActivity(bool IsActive)
        {
            Control.Invoke(SignalActivity, IsActive);
        }
 
    }
}

 

In the comments there is an example about the usage. To create the complete sample you can:

  • Create a new Compact Framework 3.5 Windows Forms project
  • Add a new class with the above code (pay attention to rename the namespace or use the "using" directive)
  • Add code to the Form1 behind code so that it looks like the following:
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
 
namespace DetectUserActivity
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        UserActivity _userActivity;
 
        private void Form1_Load(object sender, EventArgs e)
        {
            _userActivity = new UserActivity();
            _userActivity.Start(this, new Action<bool>(SignalActivity));
 
        }
 
        private void SignalActivity(bool IsUserActive)
        {
            string item;
            if (IsUserActive)
                item = "Active " + DateTime.Now.ToShortTimeString();
            else
                item = "Inactive " + DateTime.Now.ToShortTimeString();
 
            listBox1.Items.Add(item);
            listBox1.SelectedIndex = listBox1.Items.Count - 1;
        }
 
        private void btQuit_Click(object sender, EventArgs e)
        {
            _userActivity.Quit();
        }
 
        private void btClear_Click(object sender, EventArgs e)
        {
            listBox1.Items.Clear();
        }
    }
}

Enjoy!



rated by 0 users



Share this page on Twitter


Copyright (c) Raffaele Rialdi 2009, Senior Software Developer, Consultant, p.iva IT01741850992, hosted by Vevy Europe Advanced Technologies Division. Site created by Raffaele Rialdi, 2009 - 2011