Count down with alarm

3

I have a project where I start an action of 10min, for example, and thus starts a countdown timer.

This counter will be displayed on a management screen (it can add +1 minutes, like the timer of the native widget).

When 15 seconds is gone to finish this time, an alert will be played. It can have multiple times open.

I was wondering if with AlamManager rolls do this persistence of this time, so he can do the management add + 1 minute.

Note: I still do not have any code to post, it's just a question of how I can do this.

    
asked by anonymous 06.09.2016 / 22:18

1 answer

2

The AlarmManager was not "made" for this type of processing.

documentation , anticipating possible bad uses, refers to:

  

The Alarm Manager is intended for cases where you want to have your application code run at a specific time, even if your application is not currently running. For normal timing operations (ticks, timeouts, etc.) it is easier and much more efficient to use Handler.

     

AlarmManager is intended for cases where you want to have your code run at any given time, even if the application is not running. For normal timing operations (ticking, inaction intervals, etc.) it is easier and much more efficient to use a Handler.

Android provides the class CountDownTimer that allows you to schedule a countdown, with interval notifications during the count.
However, it does not entirely fit the behavior you want, namely adding more time to the remaining time.

The solution will be to create a class, like CountDownTimer , but tailored to our needs:

CountDown.java (GitHubGist)

public class CountDown {

    //Interface a ser implementada por um listener
    public interface CountDownListener {
        //Chamado quando o valor de secondsLeft é alterado,
        //quando for decrementado ou incrementado.
        void onChange(long timeLeft);
        //Chamado quando o contador chegar ao fim.
        void onEnd();
    }

    private long fromSeconds;
    private long secondsLeft;
    private CountDownListener listener;
    private boolean isCounting = false;

    //Valor em milissegundos de um segundo.
    private static final long ONE_SECOND = 1000;
    private static final int MSG = 1;

    //Constrói o contador com o valor inicial de segundos.
    public CountDown(long fromSeconds){

        this.fromSeconds = fromSeconds;
        handler = new CountDownHandler(this);
    }

    //Inicia a contagem, a partir do valor inícial.
    public synchronized void start(){
        if(isCounting){
            return;//ou talvez lançar uma excepção
        }
        isCounting = true;
        secondsLeft = fromSeconds;
        handler.sendMessage(handler.obtainMessage(MSG));
    }

    //Pára a contagem.
    public synchronized void stop(){
        if(!isCounting){
            return;//ou talvez lançar uma excepção
        }
        isCounting = false;
        handler.removeMessages(MSG);
    }

    //Retoma a contagem.
    public synchronized void resume(){
        if(isCounting || secondsLeft == 0){
            return;//ou talvez lançar uma excepção
        }
        isCounting = true;
        handler.sendMessageDelayed(handler.obtainMessage(MSG), ONE_SECOND);
    }

    //Incrementa o valor do contador.
    public synchronized void increaseBy(long value){
        secondsLeft += value;
    }

    //true se o contador estiver contando.
    public boolean isCounting(){
        return isCounting;
    }

    //Guarda um listener.
    public void setCountDownListener(CountDownListener listener){
        this.listener = listener;
    }

    //Método para formatar um valor em segundos em algo tipo "mm:ss" ou "HH:mm:ss".
    public static String secondsToString(long seconds, String format){
        return DateFormat.format(format, seconds * ONE_SECOND).toString();
    }

    private final Handler handler;

    //Handler para controlar o contador
    private static class CountDownHandler extends Handler
    {

        private final WeakReference<CountDown> countDownWeakReference;

        private CountDownHandler(CountDown countDownInstance) {
            countDownWeakReference = new WeakReference<>(countDownInstance);
        }

        @Override
        public void handleMessage(Message msg) {

            CountDown countDown = countDownWeakReference.get();
            if(countDown == null){
                return;
            }

            synchronized (countDown) {

                //Guarda o instante em que inicia o processamento.
                long tickStart = SystemClock.elapsedRealtime();

                //Se tiver sido parado sai.
                if(!countDown.isCounting){
                    return;
                }

                //Notifica o listener com o segundos que faltam para terminar.
                if (countDown.listener != null) {
                    countDown.listener.onChange(countDown.secondsLeft);
                }

                //O contador chegou ao fim, notifica o listener.
                if (countDown.secondsLeft == 0) {
                    countDown.isCounting = false;
                    if (countDown.listener != null) {
                        countDown.listener.onEnd();
                    }
                } else {
                    //decrementa o contador.
                    countDown.secondsLeft--;

                    //Obtém o tempo para o próximo decremento.
                    //Leva em conta o tempo gasto no processamento,
                    //principalmente o eventualmente gasto pela implementação
                    // do método onChange() no listener.
                    long delay = ONE_SECOND - (SystemClock.elapsedRealtime() - tickStart);

                    //Se o tempo gasto for superior a um segundo, ajusta-o para o próximo.
                    //Se o tempo gasto no método onChange() for próximo ou
                    // superior a um segundo ele só será chamado no próximo.
                    while(delay < 0){
                        countDown.secondsLeft--;
                        delay += ONE_SECOND;
                    }
                    //Garante o término se o tempo for excedido
                    if(countDown.secondsLeft < 0){
                        countDown.listener.onEnd();
                    }else {
                        //Agenda o próximo decremento.
                        sendMessageDelayed(obtainMessage(MSG), delay);
                    }
                }
            }
        }
    };
}

The class allows you to create a counter with a specified number of seconds that, when started by the start() method, will count down to zero by notifying a listener , associated with the setCountDownListener() , when the value of the remaining time is decremented or incremented by the increaseBy() method and when the counter reaches the end. It is possible to stop counting with the stop() method and retake it with the resume() method.

Our counter can now be used to control a class that encapsulates the behavior we wish to have during counting.

From what I understand, the behavior you want is:

  • Display the amount of time remaining on the screen.
  • Activate an alarm when the remaining time reaches 15 seconds.
  • Being able to add more time to the counter (already implemented in CountDown )

CountDownBehavior.java

public abstract class CountDownBehavior implements CountDown.CountDownListener {

    private final long alarmTime;
    private final String displayFormat;

    public CountDownBehavior(long alarmTime, String displayFormat){

        //Valor em segundos no qual deve ser chamado onAlarm().
        this.alarmTime = alarmTime;
        //Formato da string passada ao displayTimeLeft().
        this.displayFormat = displayFormat;
    }

    @Override
    public void onChange(long timeLeft) {
        //Aqui é implementado o comportamento que queremos ter enquanto
        //o CountDown "conta".

        //Deve informar quando chegar a altura de accionar o alarma.
        if(timeLeft == alarmTime)
        {
            onAlarm();
        }
        //Informa o valor actual do contador, com o formato indicado por displayFormat.
        displayTimeLeft(CountDown.secondsToString(timeLeft, displayFormat));

    }

    //Metodos a implementar em resposta ao comportamento.
    protected abstract void onAlarm();
    protected abstract void displayTimeLeft(String timeLeft);
}

When associated with the counter, using the setCountDownListener() method, the counter will call the methods onChange() and onEnd() , every time the count is decremented and when it reaches the end.

The behavior response should be implemented in methods onAlarm() and displayTimeLeft() , in a derived class or "in line", in an anonymous class.

Example usage:

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private CountDown countDown;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView)findViewById(R.id.textView);

        //Cria o contador com 10 minuto
        countDown = new CountDown(10*60);

        //Cria e atribui um CountDownBehavior ao contador
        countDown.setCountDownListener(new CountDownBehavior(15, "mm:ss") {
            @Override
            public void onEnd() {
                Toast.makeText(MainActivity.this, "terminou", Toast.LENGTH_SHORT).show();
            }

            @Override
            protected void onAlarm() {
                Toast.makeText(MainActivity.this, "alarme", Toast.LENGTH_SHORT).show();
            }

            @Override
            protected void displayTimeLeft(String timeLeft) {
                textView.setText(timeLeft);
            }
        });
    }

    protected void startClick(View v){
        countDown.start();
    }

    protected void addClick(View v){
        countDown.increaseBy(60);
    }

    protected void stopClick(View v){
        countDown.stop();
    }

    protected void resumeClick(View v){
        countDown.resume();
    }

    @Override
    protected void onDestroy() {
        //Antes de sair deve parar o contador caso este esteja a contar
        if(countDown.isCounting()){
            countDown.stop();
        }
        super.onDestroy();
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CountDown"
        android:id="@+id/textView" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Iniciar"
        android:id="@+id/button1"
        android:onClick="startClick"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Mais 1 minuto"
        android:id="@+id/button2"
        android:onClick="addClick"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Parar"
        android:id="@+id/button"
        android:onClick="stopClick"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="retomar"
        android:id="@+id/button3"
        android:onClick="resumeClick"/>

</LinearLayout>
    
09.09.2016 / 17:24