Search code examples
javaandroidandroid-asynctask

Countdown timer which runs in the background in android


Here is my fully functional code in which when I press the button, the button gets disabled and the countdown timer gets started and whenever it gets over button gets enabled. My problem is that if I leave that activity the process resets.

My question is how that can be done in the background so even if I close the application the timer runs in the background?

package com.mycompany.myapp;

import android.app.*;
import android.os.*;
import android.widget.*;
import android.view.View.*;
import android.view.*;

public class MainActivity extends Activity {
    Button btnCountdown;
    TextView tvCountdown;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btnCountdown = findViewById(R.id.btnCountdown);
        tvCountdown = findViewById(R.id.tvCountdown);
        btnCountdown.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Timer();
                btnCountdown.setEnabled(false);
            }
            
        });
    }
    private void Timer() {
        new CountDownTimer(30*1000,1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                long second = (millisUntilFinished / 1000) % 60;
                long minutes = (millisUntilFinished / (1000*60)) % 60;
                tvCountdown.setText(minutes + ":" + second);
            }
            @Override
            public void onFinish() {
                tvCountdown.setText("Fin");
                btnCountdown.setEnabled(true);
            }
        }.start();
    }
}

Solution

  • Add to your AndroidManifest.xml

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    

    BroadcastReceiver.java

    public class BroadcastReceiver extends AppCompatActivity {
    
    TextView tvTimer, tvTimerRunningState, tvTimerFinishedState;
    private static final String TAG = "CountdownTimer";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_broadcast_receiver);
    }
    public void handleStartTimer(View view) {
        Intent intent = new Intent(this, BroadcastService.class);
        intent.putExtra("inputExtra", "");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            ContextCompat.startForegroundService(this, intent);
        } else {
            this.startService(intent);
        }
        Log.i(TAG, "timerStarted");
    }
    public void handleCancelTimer (View view) {
        Intent intent = new Intent(this, BroadcastService.class);
        stopService(intent);
    }
    /* CountDown */
    final private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            updateGUI(intent);
        }
    };
    
    @Override
    public void onResume() {
        super.onResume();
        registerReceiver(broadcastReceiver, new IntentFilter(BroadcastService.COUNTDOWN_BR));
        Log.i(TAG, "Registered broadcast receiver");
    }
    
    @Override
    public void onPause() {
        super.onPause();
        unregisterReceiver(broadcastReceiver);
        Log.i(TAG, "Unregistered broadcast receiver");
    }
    
    @Override
    public void onStop() {
        try {
            unregisterReceiver(broadcastReceiver);
        } catch (Exception e) {
            // Receiver was probably already stopped in onPause()
        }
        super.onStop();
    }
    
    private void updateGUI(Intent intent) {
        if (intent.getExtras() != null) {
            long millisUntilFinished = intent.getLongExtra("countdown", 0);
            long seconds = (millisUntilFinished / 1000) % 60;
            long minutes = (millisUntilFinished / (1000*60)) % 60;
            long hours = (millisUntilFinished / (1000*60*60)) % 60;
            String time = (hours + " : " + minutes + " : " + seconds);
            tvTimer = findViewById(R.id.tvTimer);
            tvTimer.setText(time);
    
            boolean countdownTimerRunning = intent.getBooleanExtra("countdownTimerRunning", false);
            tvTimerRunningState = findViewById(R.id.tvTimerRunningState);
            if (countdownTimerRunning) {
                tvTimerRunningState.setText("CountdownTimerRunning");
            } else {
                tvTimer.setText("0 : 0 : 0");
                tvTimerRunningState.setText("CountdownTimerNotRunning");
            }
    
            boolean countdownTimerFinished = intent.getBooleanExtra("countdownTimerFinished", false);
            tvTimerFinishedState = findViewById(R.id.tvTimerFinishedState);
            if (countdownTimerFinished) {
                tvTimerFinishedState.setText("Finished");
            } else {
                tvTimerFinishedState.setText("Unfinished");
            }
        }
    }
    

    activity_broadcast_receiver.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <Button
        android:id="@+id/btnStartJob"
        android:onClick="handleStartTimer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Timer" />
    <Button
        android:id="@+id/btnStopJob"
        android:onClick="handleCancelTimer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Cancel Timer" />
    <TextView
        android:id="@+id/tvTimer"
        android:text="0 : 0 : 0"
        android:gravity="center"
        android:textSize="30sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tvTimerFinishedState"
        android:gravity="center"
        android:textSize="20sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tvTimerRunningState"
        android:gravity="center"
        android:textSize="18sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    

    BroadcastService.java

    public class BroadcastService extends Service {
    
    public static final String CHANNEL_ID = "ForegroundServiceChannel";
    private final static String TAG = "BroadcastService";
    public static final String COUNTDOWN_BR = "your.package.name";
    Intent bi = new Intent(COUNTDOWN_BR);
    
    CountDownTimer cdt = null;
    
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Starting timer...");
        cdt = new CountDownTimer(30000, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                Log.i(TAG, "Countdown seconds remaining: " + millisUntilFinished / 1000);
                bi.putExtra("countdown", millisUntilFinished);
                bi.putExtra("countdownTimerRunning", true);
                bi.putExtra("countdownTimerFinished", false);
                sendBroadcast(bi);
            }
            @Override
            public void onFinish() {
                Log.i(TAG, "Timer finished");
                bi.putExtra("countdownTimerFinished", true);
                sendBroadcast(bi);
                stopForeground(true);
                stopSelf();
            }
        }; cdt.start();
    
    }
    
    @Override
    public void onDestroy() {
        cdt.cancel();
        Log.i(TAG, "Timer cancelled");
        bi.putExtra("countdownTimerRunning", false);
        sendBroadcast(bi);
        super.onDestroy();
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        /* Notification */
        String input = intent.getStringExtra("inputExtra");
        createNotificationChannel();
        Intent notificationIntent = new Intent(this, BroadcastReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,
                0, notificationIntent, 0);
        /* NotificationBuilder */
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Foreground Service")
                .setContentText(input)
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentIntent(pendingIntent)
                .build();
        startForeground(1, notification);
        return START_NOT_STICKY;
    }
    
    @Nullable
    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }
    
    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel serviceChannel = new NotificationChannel(
                    CHANNEL_ID,
                    "Foreground Service Channel",
                    NotificationManager.IMPORTANCE_DEFAULT
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(serviceChannel);
        }
    }