Detecting user inactivity in
Symbian OS
Introduction
This article describes how to detect user activity and
inactivity in Symbian OS. An active object is described, that can be used to
detect user activity events. By user activity I mean any key presses or other
activity the user performs with the phone, such as opening or closing the
keypad on the 7650. User activity can also be simulated by applications with
the system call:
User::ResetInactivityTime();
Several system applications simulate user activity. For
example the phone application simulates user activity when there is an incoming
call. This is because user activity causes the system screen saver to disappear
and the back light to come on for example. System information notes might also
call ResetInactivityTime(). You too can control system screen saver and system
back light in your applications by calling ResetInactivityTime(). In addition,
you can detect the number of user inactivity seconds at any time by calling:
However, to be notified when there has been a certain
amount of seconds without user activity you need to use the call:
Rtimer::Inactivity(TRequestStatus& aStatus,
TTimeIntervalSeconds aSeconds);
This call can be used to implement an active object that
allows you to be notified when the has been a certain period of user
inactivity. This is the basis for implementing applications such as screen
savers or other applications that need to do some processing when there is no user
activity.
In this article such an active object is described. This
active object uses the observer paradigm as an interface towards the rest of
the application. You can therefore implement the logic of your application in
the observer callbacks. Detecting user inactivity is relatively
straightforward. However not so detecting when user activity is resumed after
an inactivity period. This is because there is no API to do that. Whilst one
could be tempted to periodically call User::InactivityTime() this would cause a
drain of battery life. This article therefore also describes a trick to detect
user activity by abusing a little bit the semantics of the Rtimer::Inactivity()
call.
Implementation
The active object monitoring user activity or inactivity
uses an observer to communicate to the rest of the application. The declaration
of the observer is as follows:
class MactivityManagerObserver
{
public :
virtual void
ActivityDetected() = 0;
virtual void
InactivityDetected() = 0;
};
In InactivityDetected() you implement what your application
needs to do when there has been inactivity, e.g. if you are writing a screen
saver your application will come to the foreground. In ActivityDetected() you
implement what your application needs to do when activity is resumed after the
inactivity period, e.g. if you are writing a screen saver your application will
go to the background.
This declaration of the active object is as follows:
class CActivityManager : public CActive
{
public:
IMPORT_C static CActivityManager* NewL(MActivityManagerObserver*
aObserver, TInt aTimeout = 60);
IMPORT_C ~CActivityManager();
IMPORT_C void SetTimeout(TInt aTimeout);
IMPORT_C void Start();
IMPORT_C void Reset();
protected: // from CActive
void
DoCancel();
void
RunL();
protected:
CActivityManager(MActivityManagerObserver* aObserver, TInt aTimeout);
void
ConstructL();
protected:
enum
TWatch { ENone = 0, EWaitingForInactivity, EWaitingForActivity };
protected:
RTimer iTimer;
TWatch iWatch;
MActivityManagerObserver* iObserver;
///The observer of activity status
TInt
iTimeout;
///Current inactivity period
};
You create an instance of this active object by calling
NewL. You can change the timeout (in seconds) by calling SetTimeout(). This is the
timeout after which, if there has been no user activity, InactivityDetected()
is called in your observer. You start monitoring user activity by calling
Start() and stop by calling Reset(). Calling SetTimeout() always causes a reset of inactivity
monitoring.
As for the implementation, here are some trivial methods:
EXPORT_C CActivityManager*
CActivityManager::NewL(MActivityManagerObserver* aObserver, TInt aTimeout)
{
CActivityManager* self = new (ELeave) CActivityManager(aObserver,
aTimeout);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
return self;
}
CActivityManager::CActivityManager(MActivityManagerObserver*
aObserver, TInt aTimeout)
:
CActive(CActive::EPriorityHigh), iObserver(aObserver), iTimeout(aTimeout)
{
CActiveScheduler::Add(this);
}
EXPORT_C CActivityManager::~CActivityManager()
{
Cancel();
iTimer.Close();
}
void CActivityManager::ConstructL()
{
iTimer.CreateLocal();
}
EXPORT_C void CActivityManager::SetTimeout(TInt
aTimeout)
{
iTimeout
= aTimeout;
Reset();
}
EXPORT_C void CActivityManager::Reset()
{
Cancel();
Start();
}
void CActivityManager::DoCancel()
{
iTimer.Cancel();
iWatch
= ENone;
}
The iWatch variable implements the state machine of the
active object: we are either doing nothing, or monitoring inactivity or
monitoring activity. The core methods are Start() and RunL(). This is the
implementation of Start():
EXPORT_C
void CActivityManager::Start()
{
if (!IsActive())
{
iWatch = EWaitingForInactivity;
iTimer.Inactivity(iStatus, iTimeout);
SetActive();
}
}
Essentially we set the state variable to monitoring
inactivity and launch the inactivity notification offered by Rtimer by specifying
the desired timeout in seconds. RunL() willl be executed after iSeconds seconds
of user inactivity. Note that any previous user inactivity period is taken into
consideration. This means that if there have already been, for example, 5
seconds of user inactivity when we issue the call, and we request to be
notified after 10 seconds of user inactivity, then the call will complete in 5
seconds and not in 10 seconds.
Another thing which is very important and is at the core of
the mechanism to detect user activity after an inactivity period is that we are
not notified for an interval less than the current inactivity time. To explain
it better, if we request to be notified after 3 seconds of user inactivity but
the system has already been inactive for 5 seconds then we are not notified
until activity is resumed and a following 3 second inactivity period occurs.
This is the implementation of RunL():
void CActivityManager::RunL()
{
if (iStatus
== KErrNone)
{
if
(iWatch == EWaitingForInactivity)
{
TInt
inactivity = User::InactivityTime().Int();
if
(inactivity >= iTimeout)
{
if
(iObserver)
{
iObserver->InactivityDetected();
}
if
(!IsActive()) //observer might have called a Reset()
{
iTimer.Inactivity(iStatus,0);
iWatch
= EWaitingForActivity;
}
}
else
{
iTimer.Inactivity(iStatus,iTimeout);
}
}
else
if (iWatch == EWaitingForActivity)
{
if
(iObserver)
{
iObserver->ActivityDetected();
}
if
(!IsActive()) //observer might have called a Reset()
{
iTimer.Inactivity(iStatus,iTimeout);
iWatch
= EWaitingForInactivity;
}
}
if
(!IsActive()) //observer might have called a Reset()
{
SetActive();
}
}
else
{
iWatch
= ENone;
}
}
As you can see it is a simple binary state machine going
from monitoring inactivity to monitoring activity and so forth and calling the
appropriate observer callbacks. After an inactivity period we use a call to Rtimer::Inactivity(iStatus, 0) to be
notified of activity again. This is the trick to detect activity without polling
User::InactivityTime(). If there has already been any inactivity period longer
than 1 second, Rtimer::Inactivity(iStatus,0) will in fact complete exactly when
activity is resumed.
> Detecting user inactivity
> Detecting user inactivity
Detecting user inactivity