// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.invalidation;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import androidx.annotation.Nullable;
/**
* Runner which can be paused. When the runner is paused, the execution of its
* scheduled task is delayed till the runner is resumed. This runner could be
* used as follows:
*
* <pre>
* {@code
*
* ResumableDelayedTaskRunner runner = new ResumableDelayedTaskRunner();
* runner.setRunnable(task, delayMs);
* runner.resume(); // Starts the count down.
* runner.pause(); // Pauses the count down.
* runner.resume(); // Resumes the count down.
* runner.cancel(); // Stops count down and clears the state.
*
* }
* </pre>
*/
public class ResumableDelayedTaskRunner {
private final Handler mHandler = new Handler();
private final Thread mThread = Thread.currentThread();
/** Runnable which is added to the handler's message queue. */
@Nullable private Runnable mHandlerRunnable;
/** User provided task. */
@Nullable private Runnable mRunnable;
/** Time at which the task is scheduled. */
private long mScheduledTime;
/**
* This requires to be called on a thread where the Looper has been
* prepared.
*/
public ResumableDelayedTaskRunner() {
assert Looper.myLooper() != null
: "ResumableDelayedTaskRunner can only be used on threads with a Looper";
}
/**
* Sets the task to run. The task will be run after the delay once
* {@link #resume()} is called. The previously scheduled task, if any, is
* cancelled. The Runnable |r| shouldn't call setRunnable() itself.
* @param r Task to run.
* @param delayMs Delay in milliseconds after which to run the task.
*/
public void setRunnable(Runnable r, long delayMs) {
checkThread();
cancel();
mRunnable = r;
mScheduledTime = SystemClock.elapsedRealtime() + delayMs;
}
/** Blocks the task from being run. */
public void pause() {
checkThread();
if (mHandlerRunnable == null) {
return;
}
mHandler.removeCallbacks(mHandlerRunnable);
mHandlerRunnable = null;
}
/**
* Unblocks the task from being run. If the task was scheduled for a time in the past, runs
* the task. Does nothing if no task is scheduled.
*/
public void resume() {
checkThread();
if (mRunnable == null || mHandlerRunnable != null) {
return;
}
long delayMs = Math.max(mScheduledTime - SystemClock.elapsedRealtime(), 0);
mHandlerRunnable =
new Runnable() {
@Override
public void run() {
mRunnable.run();
mRunnable = null;
mHandlerRunnable = null;
}
};
mHandler.postDelayed(mHandlerRunnable, delayMs);
}
/** Cancels the scheduled task, if any. */
public void cancel() {
checkThread();
pause();
mRunnable = null;
}
private void checkThread() {
assert mThread == Thread.currentThread()
: "ResumableDelayedTaskRunner must only be used on a single Thread.";
}
}