package com.thibaudperso.sonycamera.timelapse.control; import android.annotation.SuppressLint; import android.os.Handler; import android.os.Message; /** * Countdown based on android.os.CountDownTimer * * @author Thibaud Michel * */ @SuppressLint("HandlerLeak") public abstract class MyCountDownTicks { /** * The number of ticks in the future */ private int mRemainingTicks; /** * The interval in millis that the user receives callbacks */ private final long mCountdownInterval; /** * If the counter shoud wait for a tickProcessed() call before firing a new tick */ private final boolean mWaitForTicksProcessing; /** * If set to true, it will works has a normal counter with interval time */ private boolean isUnlimited; /** * The number of ticks in the future when unlimited */ private int mNumberOfTicks; /** * Current tick has been processed */ private boolean currentTickProcessed = true; /** * States if the handler is waiting for the last tick to be processed */ private boolean isWaitingForTickProcessing = false; /** * @param numberOfTicks The number of ticks in the future from the call * to {@link #start()} until the countdown is done and {@link #onFinish()} * is called. If set to -1, countdown is an unlimited normal counter, you * have to call {@link #cancel()} to stop it. * @param countDownInterval The interval along the way to receive * {@link #onTick(int)} callbacks. */ public MyCountDownTicks(int numberOfTicks, long countDownInterval) { this(numberOfTicks, countDownInterval, false); } /** * @param numberOfTicks The number of ticks in the future from the call * to {@link #start()} until the countdown is done and {@link #onFinish()} * is called. If set to -1, countdown is an unlimited normal counter, you * have to call {@link #cancel()} to stop it. * @param countDownInterval The interval along the way to receive * {@link #onTick(int)} callbacks. * @param waitForTicksProcessing If true, {@link #tickProcessed()} has to be * called before a further callback will happen. If {@link #tickProcessed()} * is called after a new callback should already have happened, it will happen * immediately. */ public MyCountDownTicks(int numberOfTicks, long countDownInterval, boolean waitForTicksProcessing){ isUnlimited = numberOfTicks == -1; mNumberOfTicks = 0; mRemainingTicks = numberOfTicks; mCountdownInterval = countDownInterval; mWaitForTicksProcessing = waitForTicksProcessing; } /** * Cancel the countdown. */ public final void cancel() { mHandler.removeMessages(MSG); currentTickProcessed = true; isWaitingForTickProcessing = false; } /** * Start the countdown. */ public synchronized final MyCountDownTicks start() { //init currentTickProcessed = true; isWaitingForTickProcessing = false; //maybe already finished? if (!isUnlimited && mRemainingTicks <= 0) { onFinish(); return this; } mHandler.sendMessage(mHandler.obtainMessage(MSG)); return this; } /** * Fires a tick and resets the boolean that registers if this tick was processed * @param remainingTicks The number of ticks remaining */ private void fireTick(int remainingTicks){ currentTickProcessed = false; onTick(remainingTicks); } /** * Callback fired on regular interval. * @param remainingTicks The amount of ticks until finished. */ public abstract void onTick(int remainingTicks); /** * Way to notify the Handler about a successfully processed Tick. * No further Tick will be happening until the last was processed. */ public void tickProcessed(){ synchronized (MyCountDownTicks.this){ currentTickProcessed = true; //if the handler was waiting for this tick to be processed, fire event if(isWaitingForTickProcessing) mHandler.post(new Runnable() { @Override public void run() { mHandler.handleMessage(null); } }); } } /** * Method that gets called if two ticks overlap and waitForTicksProcessing is set to 'true' * (i.e. a tick isn't yet processed when the next tick occurs) */ public void onTickOverlap(){ } /** * Callback fired when the time is up. */ public abstract void onFinish(); private static final int MSG = 1; // handles counting down @SuppressLint("HandlerLeak") private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { synchronized (MyCountDownTicks.this) { //if the current (=last) tick has been processed, can fire a new one if(!mWaitForTicksProcessing || currentTickProcessed){ isWaitingForTickProcessing = false; //job finished? if(mRemainingTicks == 0){ onFinish(); return; } if(isUnlimited) { mNumberOfTicks++; fireTick(mNumberOfTicks); } else { mRemainingTicks--; fireTick(mRemainingTicks); //if mustn't wait for tick to finish: fire onFinish() already now if(mRemainingTicks == 0 && !mWaitForTicksProcessing){ onFinish(); return; } } sendMessageDelayed(obtainMessage(MSG), mCountdownInterval); } else { //a new tick (or finish) should happen, but the previous tick wasn't yet processed //remember we are still waiting for the previous tick isWaitingForTickProcessing = true; if(mRemainingTicks > 0 || isUnlimited){ //and notify the listener about a tick overlap if there is a tick to come (not finish) onTickOverlap(); } } } } }; }