/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fsck.k9;
import java.util.Timer;
import java.util.TimerTask;
import android.os.Handler;
import timber.log.Timber;
/**
* This class used to "throttle" a flow of events.
*
* When {@link #onEvent()} is called, it calls the callback in a certain timeout later.
* Initially {@link #minTimeout} is used as the timeout, but if it gets multiple {@link #onEvent}
* calls in a certain amount of time, it extends the timeout, until it reaches {@link #maxTimeout}.
*
* This class is primarily used to throttle content changed events.
*/
public class Throttle {
private static final int TIMEOUT_EXTEND_INTERVAL = 500;
private static Timer TIMER = new Timer();
private final Clock clock;
private final Timer timer;
private final String name;
private final Handler handler;
private final Runnable callback;
private final int minTimeout;
private final int maxTimeout;
private int currentTimeout;
/** When {@link #onEvent()} was last called. */
private long lastEventTime;
private MyTimerTask runningTimerTask;
/** Constructor that takes custom timeout */
public Throttle(String name, Runnable callback, Handler handler,int minTimeout,
int maxTimeout) {
this(name, callback, handler, minTimeout, maxTimeout, Clock.INSTANCE, TIMER);
}
/** Constructor for tests */
private Throttle(String name, Runnable callback, Handler handler, int minTimeout,
int maxTimeout, Clock clock, Timer timer) {
if (maxTimeout < minTimeout) {
throw new IllegalArgumentException();
}
this.name = name;
this.callback = callback;
this.clock = clock;
this.timer = timer;
this.handler = handler;
this.minTimeout = minTimeout;
this.maxTimeout = maxTimeout;
currentTimeout = this.minTimeout;
}
private boolean isCallbackScheduled() {
return runningTimerTask != null;
}
public void cancelScheduledCallback() {
if (runningTimerTask != null) {
Timber.d("Throttle: [%s] Canceling scheduled callback", name);
runningTimerTask.cancel();
runningTimerTask = null;
}
}
private void updateTimeout() {
final long now = clock.getTime();
if ((now - lastEventTime) <= TIMEOUT_EXTEND_INTERVAL) {
currentTimeout *= 2;
if (currentTimeout >= maxTimeout) {
currentTimeout = maxTimeout;
}
Timber.d("Throttle: [%s] Timeout extended %d", name, currentTimeout);
} else {
currentTimeout = minTimeout;
Timber.d("Throttle: [%s] Timeout reset to %d", name, currentTimeout);
}
lastEventTime = now;
}
public void onEvent() {
Timber.d("Throttle: [%s] onEvent", name);
updateTimeout();
if (isCallbackScheduled()) {
Timber.d("Throttle: [%s] callback already scheduled", name);
} else {
Timber.d("Throttle: [%s] scheduling callback", name);
runningTimerTask = new MyTimerTask();
timer.schedule(runningTimerTask, currentTimeout);
}
}
/**
* Timer task called on timeout,
*/
private class MyTimerTask extends TimerTask {
private boolean mCanceled;
@Override
public void run() {
handler.post(new HandlerRunnable());
}
@Override
public boolean cancel() {
mCanceled = true;
return super.cancel();
}
private class HandlerRunnable implements Runnable {
@Override
public void run() {
runningTimerTask = null;
if (!mCanceled) { // This check has to be done on the UI thread.
Timber.d("Throttle: [%s] Kicking callback", name);
callback.run();
}
}
}
}
}