package de.nisble.droidsweeper.utilities; import java.util.ArrayList; import android.os.Handler; /** An Android style timer that ticks with configured frequency and fires events * on each tick and each second. * @author Moritz Nisblé moritz.nisble@gmx.de */ public class Timer { /** Interface for getting updates on timer events. * @author Moritz Nisblé moritz.nisble@gmx.de */ public interface TimerObserver { /** Called on each timer tick. * @param milliseconds Milliseconds elapsed since timer start. */ void onTick(long milliseconds); /** Called each second. * @param seconds Seconds elapsed since timer start. */ void onSecond(long seconds); } private enum STATUS { STOPPED, RUNNING, PAUSED } private static final String CLASSNAME = Timer.class.getSimpleName(); private long mPeriod = 20; private long mTicks = 0; private long mMilliseconds = 0; private long mSeconds = 0; private STATUS mStatus = STATUS.STOPPED; private ArrayList<TimerObserver> mListeners = new ArrayList<Timer.TimerObserver>(5); private Handler handler = new Handler(); private Runnable run = new Runnable() { @Override public void run() { mTicks++; mMilliseconds = mTicks * mPeriod; handler.postDelayed(this, mPeriod); LogDog.v(CLASSNAME, "Ticks: " + mTicks + " -> Milliseconds: " + mMilliseconds); // Inform listeners for (TimerObserver l : mListeners) { l.onTick(mMilliseconds); } if ((mTicks % (1000 / mPeriod)) == 0) { mSeconds++; LogDog.v(CLASSNAME, "Seconds: " + mSeconds); for (TimerObserver l : mListeners) { l.onSecond(mSeconds); } } } }; /** Add a listener. * @param l A listener. */ public void addListener(TimerObserver l) { // Avoid multiple entries of the same object. if (!mListeners.contains(l)) mListeners.add(l); } /** Remove a listener. * @param l A listener. */ public void removeListener(TimerObserver l) { mListeners.remove(l); } /** Start the timer. * @param period The tick period in ms. * @param delay A delay for the first tick in ms. */ public void start(long period, long delay) { if (STATUS.STOPPED == mStatus) { mTicks = 0; mMilliseconds = 0; mSeconds = 0; // Ensure a minimum period time of 10ms this.mPeriod = (period >= 10) ? period : 10; mStatus = STATUS.RUNNING; handler.postDelayed(run, this.mPeriod + delay); LogDog.d(CLASSNAME, "Timer started: period=" + this.mPeriod + " delay=" + delay); } else { LogDog.d(CLASSNAME, "Timer already running"); } } /** Start timer with given period. * @param period The timer period in ms. */ public void start(long period) { start(period, 0); } /** Start timer with currently configured period. * @note Initial period is 20ms. */ public void start() { start(mPeriod, 0); } /** Stop the timer. * @note Tick count and seconds are valid until next start. */ public void stop() { mStatus = STATUS.STOPPED; handler.removeCallbacksAndMessages(null); // handler.removeCallbacks(run); LogDog.d(CLASSNAME, "Timer stopped"); } /** Pause the timer. */ public void pause() { mStatus = STATUS.PAUSED; handler.removeCallbacksAndMessages(null); // handler.removeCallbacks(run); LogDog.d(CLASSNAME, "Timer paused"); } /** Resume timer. */ public void resume() { if (STATUS.PAUSED == mStatus) { mStatus = STATUS.RUNNING; handler.postDelayed(run, this.mPeriod); LogDog.d(CLASSNAME, "Resuming timer"); } else { LogDog.d(CLASSNAME, "Unable to resume timer: status=" + mStatus); } } public boolean isStopped() { return mStatus == STATUS.STOPPED; } public boolean isRunning() { return mStatus == STATUS.RUNNING; } public boolean isPaused() { return mStatus == STATUS.PAUSED; } /** Get elapsed milliseconds. * @return Elapsed milliseconds. */ public long getMilliseconds() { return mMilliseconds; } /** Get elapsed seconds. * @return Elapsed seconds. */ public long getSeconds() { return mSeconds; } }