From 85d60b6520b5d6856c083901b0105b0b5e1f4cab Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 21 Jun 2006 07:23:23 +0000 Subject: [PATCH] Factored out the idle tracking code from Yohoho. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4205 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- src/java/com/threerings/util/IdleTracker.java | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/java/com/threerings/util/IdleTracker.java diff --git a/src/java/com/threerings/util/IdleTracker.java b/src/java/com/threerings/util/IdleTracker.java new file mode 100644 index 000000000..a3529246d --- /dev/null +++ b/src/java/com/threerings/util/IdleTracker.java @@ -0,0 +1,173 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.util; + +import java.awt.AWTEvent; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; + +import com.samskivert.util.Interval; +import com.samskivert.util.RunQueue; + +/** + * Used to track user idleness in an AWT application. + */ +public abstract class IdleTracker +{ + /** + * Creates an idle tracker that will report idleness (via {@link + * #idledOut}) after toIdleTime milliseconds have elapsed. + * After an additional toAbandonTime milliseconds have + * elapsed, we will report that the user has {@link #abandonedShip}. + */ + public IdleTracker (long toIdleTime, long toAbandonTime) + { + _toIdleTime = toIdleTime; + _toAbandonTime = toAbandonTime; + + // initialize our last event time + _lastEvent = getTimeStamp(); + } + + public void start (KeyboardManager keymgr, RunQueue rqueue) + { + // we want to observe all mouse and keyboard events + long eventMask = + AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK | + AWTEvent.MOUSE_WHEEL_EVENT_MASK | + AWTEvent.KEY_EVENT_MASK; + + // add the global event listener + Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() { + public void eventDispatched (AWTEvent event) { + handleUserActivity(); + } + }, eventMask); + + // and tie into the keyboard manager as well + keymgr.registerKeyObserver(new KeyboardManager.KeyObserver() { + public void handleKeyEvent (int id, int keyCode, long timestamp) { + handleUserActivity(); + } + }); + + // register an interval to periodically check our last activity time + new Interval(rqueue) { + public void expired () { + checkIdle(); + } + }.schedule(IDLE_INTERVAL, true); + } + + /** + * Called when the client has been idle for {@link #_toIdleTime} + * milliseconds. + */ + protected abstract void idledOut (); + + /** + * Called when the client becomes non-idle after we have previously + * reported their idleness. + */ + protected abstract void idledIn (); + + /** + * Called when the client has been idle for {@link #_toIdleTime} plus + * {@link #_toAbandonTime} milliseconds. + */ + protected abstract void abandonedShip (); + + /** + * This should return a timestamp. We would use {@link + * System#getCurrentTimeMillis} except that on Windows that sometimes does + * strange things like leap forward in time causing immediate idleness. + */ + protected abstract long getTimeStamp (); + + /** + * Called with any keyboard or mouse events performed on the frame so + * as to note user activity as it pertains to tracking the client idle + * state. + */ + protected void handleUserActivity () + { + // note the time of the last user action + _lastEvent = getTimeStamp(); + + // idle-in if appropriate + if (_state != State.ACTIVE) { + _state = State.ACTIVE; + idledIn(); + } + } + + /** + * Checks the last user event time and posts a command to idle them + * out if they've been inactive for too long, or log them out if + * they've been idle for too long. + */ + protected void checkIdle () + { + long now = getTimeStamp(); + + switch (_state) { + case ACTIVE: + // check whether they've idled out + if (now >= (_lastEvent + _toIdleTime)) { + Log.info("User idle for " + (now-_lastEvent) + "ms."); + _state = State.IDLE; + idledOut(); + } + break; + + case IDLE: + // check whether they've been idle for too long + if (now >= (_lastEvent + _toIdleTime + _toAbandonTime)) { + Log.info("User idle for " + (now-_lastEvent) + "ms. " + + "Abandoning ship."); + _state = State.ABANDONED; + abandonedShip(); + } + break; + } + } + + /** The user's current state. */ + protected static enum State { ACTIVE, IDLE, ABANDONED }; + + /** The duration after which we declare the user to be idle. */ + protected long _toIdleTime; + + /** The duration after which we declare the user to have abandoned ship. */ + protected long _toAbandonTime; + + /** The time of the last mouse or keyboard event; used to track + * whether the user is idle. */ + protected long _lastEvent; + + /** Whether the user is currently active, idle or abandoned. */ + protected State _state; + + /** The delay in milliseconds between checks for user idle. */ + protected static final long IDLE_INTERVAL = 60L * 1000L; +}