NEW - bug 249883: [patch] activity timing not correct when AbstractUserActivityMonitor.getLastInteractionTime() is overridden
https://bugs.eclipse.org/bugs/show_bug.cgi?id=249883
diff --git a/org.eclipse.mylyn.monitor.tests/src/org/eclipse/mylyn/monitor/tests/CheckActivityJobTest.java b/org.eclipse.mylyn.monitor.tests/src/org/eclipse/mylyn/monitor/tests/CheckActivityJobTest.java
new file mode 100644
index 0000000..f4bfcc8
--- /dev/null
+++ b/org.eclipse.mylyn.monitor.tests/src/org/eclipse/mylyn/monitor/tests/CheckActivityJobTest.java
@@ -0,0 +1,204 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 Tasktop Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.monitor.tests;
+
+import junit.framework.TestCase;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.mylyn.internal.monitor.ui.CheckActivityJob;
+import org.eclipse.mylyn.internal.monitor.ui.IActivityManagerCallback;
+
+/**
+ * @author Steffen Pingel
+ */
+public class CheckActivityJobTest extends TestCase {
+
+ private StubCallback callback;
+
+ private TestableCheckActivityJob job;
+
+ @Override
+ protected void setUp() throws Exception {
+ callback = new StubCallback();
+ job = new TestableCheckActivityJob(callback);
+ }
+
+ public void testInactivityTimeout() throws Exception {
+ callback.lastEventTime = System.currentTimeMillis() - 41;
+ job.setInactivityTimeout(40);
+ job.run();
+ assertFalse(job.isActive());
+ job.run();
+ assertFalse(job.isActive());
+ callback.lastEventTime = System.currentTimeMillis();
+ job.run();
+ assertTrue(job.isActive());
+ assertEquals(0, callback.activeTime);
+ Thread.sleep(6);
+ job.run();
+ assertTrue(job.isActive());
+ assertTrue("expected less than 5 < activeTime < 20, got " + callback.activeTime, callback.activeTime > 5
+ && callback.activeTime < 20);
+ }
+
+ public void testResumeFromSleepNoTimeout() throws Exception {
+ job.setInactivityTimeout(0);
+ job.run();
+ assertTrue(job.isActive());
+ job.run();
+ assertTrue(job.isActive());
+ assertEquals(1, callback.eventCount);
+ job.run();
+ assertEquals(2, callback.eventCount);
+ assertTrue(job.isActive());
+ Thread.sleep(11);
+ job.run();
+ assertTrue(job.isActive());
+ assertTrue("expected more than 10 ms, got " + callback.activeTime, callback.activeTime > 10);
+ assertEquals(3, callback.eventCount);
+ }
+
+ public void testResumeFromSleepTimeoutNoEvent() throws Exception {
+ callback.lastEventTime = System.currentTimeMillis();
+ job.setInactivityTimeout(20);
+ job.setTick(20);
+ job.run();
+ assertTrue(job.isActive());
+ job.run();
+ assertTrue(job.isActive());
+ assertEquals(1, callback.eventCount);
+ Thread.sleep(61);
+ // resume from sleep past timeout
+ job.run();
+ assertFalse(job.isActive());
+ job.run();
+ assertFalse(job.isActive());
+ assertTrue("expected less than 10 ms, got " + callback.activeTime, callback.activeTime < 10);
+ assertEquals(1, callback.eventCount);
+ assertEquals(callback.lastEventTime, callback.startTime);
+ }
+
+ public void testResumeFromSleepTimeoutEvent() throws Exception {
+ callback.lastEventTime = System.currentTimeMillis();
+ job.setInactivityTimeout(20);
+ job.setTick(20);
+ job.run();
+ assertTrue(job.isActive());
+ job.run();
+ assertTrue(job.isActive());
+ assertEquals(1, callback.eventCount);
+ Thread.sleep(41);
+ // resume from sleep past timeout
+ job.run();
+ assertTrue(callback.inactive);
+ assertFalse(job.isActive());
+ Thread.sleep(11);
+ // should still discard events
+ job.run();
+ assertFalse(job.isActive());
+ // start activity
+ callback.lastEventTime = System.currentTimeMillis();
+ job.run();
+ assertTrue(job.isActive());
+ assertEquals(1, callback.eventCount);
+ Thread.sleep(11);
+ job.run();
+ // check if time sleeping was logged
+ assertTrue("expected less than 10 < activeTime < 20, got " + callback.activeTime, callback.activeTime > 10
+ && callback.activeTime < 20);
+ assertEquals(2, callback.eventCount);
+ }
+
+ public void testResumeFromSleepTimeoutEventDiscarded() throws Exception {
+ callback.lastEventTime = System.currentTimeMillis();
+ job.setInactivityTimeout(20);
+ job.setTick(20);
+ job.run();
+ assertTrue(job.isActive());
+ job.run();
+ assertTrue(job.isActive());
+ assertEquals(1, callback.eventCount);
+ Thread.sleep(61);
+ // resume from sleep past timeout
+ callback.lastEventTime = System.currentTimeMillis();
+ job.run();
+ assertFalse(callback.inactive);
+ assertTrue(job.isActive());
+ Thread.sleep(6);
+ job.run();
+ assertTrue(job.isActive());
+ // check if time sleeping was logged
+ assertTrue("expected less than 5 < activeTime < 10, got " + callback.activeTime, callback.activeTime > 5
+ && callback.activeTime < 10);
+ assertEquals(2, callback.eventCount);
+ }
+
+ private class TestableCheckActivityJob extends CheckActivityJob {
+
+ public TestableCheckActivityJob(IActivityManagerCallback callback) {
+ super(callback);
+ }
+
+ public IStatus run() {
+ return super.run(new NullProgressMonitor());
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ return true;
+ }
+
+ public void setPreviousEventTime(long previousEventTime) {
+ this.previousEventTime = previousEventTime;
+ }
+
+ public void setTick(long tick) {
+ this.tick = tick;
+ }
+
+ @Override
+ public void reschedule() {
+ // ignore, job is called explicitly from test
+ }
+
+ }
+
+ private class StubCallback implements IActivityManagerCallback {
+
+ private boolean inactive;
+
+ private long lastEventTime;
+
+ private long activeTime;
+
+ private long eventCount;
+
+ private long startTime;
+
+ public void addMonitoredActivityTime(long startTime, long endTime) {
+ this.startTime = startTime;
+ this.activeTime += endTime - startTime;
+ this.eventCount++;
+ }
+
+ public void fireInactive() {
+ this.inactive = true;
+ }
+
+ public long getLastEventTime() {
+ return this.lastEventTime;
+ }
+
+ }
+
+}
diff --git a/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/ActivityContextManager.java b/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/ActivityContextManager.java
index fd11b57..84ae8dc 100644
--- a/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/ActivityContextManager.java
+++ b/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/ActivityContextManager.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2004, 2008 Tasktop Technologies and others.
+ * Copyright (c) 2004, 2008 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -16,10 +16,6 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
@@ -46,25 +42,11 @@
@SuppressWarnings("restriction")
public class ActivityContextManager implements IActivityContextManager {
- private final int TICK = 30 * 1000;
-
- private final int SHORT_TICK = 5 * 1000;
-
private AbstractUserActivityMonitor userActivityMonitor;
private final Set<IUserAttentionListener> attentionListeners = new CopyOnWriteArraySet<IUserAttentionListener>();
- private long startTime = -1;
-
- private int timeout;
-
- private final Object startTimeLock = new Object();
-
- private boolean wasTimedOut = true;
-
- private int wait = SHORT_TICK;
-
- private CheckActivityJob checkJob;
+ private final CheckActivityJob checkJob;
private IWorkingSet[] workingSets;
@@ -82,10 +64,23 @@
}
};
- public ActivityContextManager(int timeout, ArrayList<AbstractUserActivityMonitor> monitors) {
+ public ActivityContextManager(ArrayList<AbstractUserActivityMonitor> monitors) {
this.activityMonitors = monitors;
- this.timeout = timeout;
+ checkJob = new CheckActivityJob(new IActivityManagerCallback() {
+ public void addMonitoredActivityTime(long localStartTime, long currentTime) {
+ ActivityContextManager.this.addMonitoredActivityTime(localStartTime, currentTime);
+ }
+ public void fireInactive() {
+ ActivityContextManager.this.fireInactive();
+ }
+
+ public long getLastEventTime() {
+ return ActivityContextManager.this.getLastEventTime();
+ }
+ });
+ checkJob.setSystem(true);
+ checkJob.setPriority(Job.INTERACTIVE);
}
protected void updateWorkingSetSelection() {
@@ -106,22 +101,15 @@
}
updateWorkingSetSelection();
PlatformUI.getWorkbench().getWorkingSetManager().addPropertyChangeListener(WORKING_SET_CHANGE_LISTENER);
- checkJob = new CheckActivityJob();
- checkJob.setSystem(true);
- checkJob.setPriority(Job.DECORATE);
- checkJob.schedule(TICK);
+ checkJob.reschedule();
}
public void stop() {
for (AbstractUserActivityMonitor monitor : activityMonitors) {
monitor.stop();
}
-
PlatformUI.getWorkbench().getWorkingSetManager().removePropertyChangeListener(WORKING_SET_CHANGE_LISTENER);
-
- if (checkJob != null) {
- checkJob.cancel();
- }
+ checkJob.cancel();
}
public void addListener(IUserAttentionListener listener) {
@@ -200,69 +188,12 @@
return -1;
}
- private long getStartTime() {
- synchronized (startTimeLock) {
- return startTime;
- }
- }
-
- private void setStartTime(long startTime) {
- synchronized (startTimeLock) {
- this.startTime = startTime;
- }
- }
-
- class CheckActivityJob extends Job {
-
- public CheckActivityJob() {
- super("Activity Monitor Job");
- }
-
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- try {
- if (Platform.isRunning()) {
- if (!MonitorUiPlugin.getDefault().getWorkbench().isClosing()) {
-
- long localLastEventTime = getLastEventTime();
- long localStartTime = getStartTime();
- long currentTime = System.currentTimeMillis();
- if ((currentTime - localLastEventTime) >= timeout && timeout != 0) {
- if (wasTimedOut == false) {
- fireInactive();
- // timed out
- wasTimedOut = true;
- }
- wait = SHORT_TICK;
- } else {
- if (wasTimedOut) {
- wasTimedOut = false;
- // back...
- setStartTime(localLastEventTime);
- } else {
- addMonitoredActivityTime(localStartTime, currentTime);
- setStartTime(currentTime);
- }
- wait = TICK;
- }
-
- }
- }
- return Status.OK_STATUS;
- } finally {
- if (Platform.isRunning()) {
- checkJob.schedule(wait);
- }
- }
- }
- }
-
public void setInactivityTimeout(int inactivityTimeout) {
- timeout = inactivityTimeout;
+ checkJob.setInactivityTimeout(inactivityTimeout);
}
public int getInactivityTimeout() {
- return timeout;
+ return checkJob.getInactivityTimeout();
}
/**
diff --git a/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/CheckActivityJob.java b/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/CheckActivityJob.java
new file mode 100644
index 0000000..6792934
--- /dev/null
+++ b/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/CheckActivityJob.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 Tasktop Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.monitor.ui;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * A job that is scheduled periodically to check for user activity.
+ *
+ * @author Robert Elves
+ * @author Steffen Pingel
+ */
+public class CheckActivityJob extends Job {
+
+ /**
+ * If true, the user is assumed to be active.
+ */
+ private boolean active;
+
+ /**
+ * The rescheduling interval in ms when active. Should be reasonably short to provide accurate tracking.
+ */
+ private final int ACTIVE_TICK = 30 * 1000;
+
+ private final IActivityManagerCallback callback;
+
+ /**
+ * The timeout when to assume a user is inactive. If set to 0 it is assumed that a user is always active.
+ */
+ private int inactivityTimeout;
+
+ protected long previousEventTime;
+
+ /**
+ * Protected for testing.
+ */
+ protected long tick = ACTIVE_TICK;
+
+ public CheckActivityJob(IActivityManagerCallback callback) {
+ super("Activity Monitor Job");
+ this.callback = callback;
+ }
+
+ public int getInactivityTimeout() {
+ return this.inactivityTimeout;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ protected boolean isEnabled() {
+ return Platform.isRunning() && !MonitorUiPlugin.getDefault().getWorkbench().isClosing();
+ }
+
+ public void reschedule() {
+ schedule(active ? tick : tick / 6);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ if (isEnabled()) {
+ try {
+ long lastEventTime = callback.getLastEventTime();
+ long currentTime = System.currentTimeMillis();
+ // check if the last activity exceeds timeout
+ if ((currentTime - lastEventTime) >= inactivityTimeout && inactivityTimeout != 0) {
+ if (active) {
+ // time out
+ active = false;
+ callback.fireInactive();
+ }
+ } else {
+ if (!active) {
+ active = true;
+ // back, start recording activity
+ if (inactivityTimeout != 0) {
+ previousEventTime = lastEventTime;
+ } else {
+ // if timeouts are disabled only the currentTime is relevant for tracking activity
+ previousEventTime = currentTime;
+ }
+ } else {
+ // check if the activity internal is unreasonably long, it is likely that
+ // the computer came back from sleep at worst difference should be tick * 2
+ if (currentTime - previousEventTime > tick * 3) {
+ if (inactivityTimeout != 0) {
+ // check for recent event
+ if (currentTime - lastEventTime <= tick) {
+ // event since resume
+ previousEventTime = lastEventTime;
+ } else {
+ // time out
+ active = false;
+ callback.fireInactive();
+ }
+ } else {
+ // if timeouts are disabled only the currentTime is relevant for tracking activity
+ previousEventTime = currentTime;
+ }
+ } else {
+ callback.addMonitoredActivityTime(previousEventTime, currentTime);
+ previousEventTime = currentTime;
+ }
+ }
+ }
+ } finally {
+ reschedule();
+ }
+ }
+ return Status.OK_STATUS;
+ }
+
+ public void setInactivityTimeout(int inactivityTimeout) {
+ this.inactivityTimeout = inactivityTimeout;
+ }
+
+}
diff --git a/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/IActivityManagerCallback.java b/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/IActivityManagerCallback.java
new file mode 100644
index 0000000..0ff5930
--- /dev/null
+++ b/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/IActivityManagerCallback.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 Tasktop Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.monitor.ui;
+
+public interface IActivityManagerCallback {
+
+ public abstract void fireInactive();
+
+ public abstract void addMonitoredActivityTime(long localStartTime, long currentTime);
+
+ public abstract long getLastEventTime();
+
+}
diff --git a/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/MonitorUiPlugin.java b/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/MonitorUiPlugin.java
index 05c4916..ccbba37 100644
--- a/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/MonitorUiPlugin.java
+++ b/org.eclipse.mylyn.monitor.ui/src/org/eclipse/mylyn/internal/monitor/ui/MonitorUiPlugin.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2004, 2008 Tasktop Technologies and others.
+ * Copyright (c) 2004, 2008 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -55,8 +55,6 @@
public static final String ID_PLUGIN = "org.eclipse.mylyn.monitor.ui";
- private static final int TIMEOUT_INACTIVITY_MILLIS = 60 * 1000;
-
private static MonitorUiPlugin INSTANCE;
private ShellLifecycleListener shellLifecycleListener;
@@ -410,7 +408,7 @@
monitors.add(new WorkbenchUserActivityMonitor());
new MonitorUiExtensionPointReader().initExtensions();
- activityContextManager = new ActivityContextManager(TIMEOUT_INACTIVITY_MILLIS, monitors);
+ activityContextManager = new ActivityContextManager(monitors);
updateActivityTimout();