Extending Android's Chronometer to get Elapsed Time

(2009)

In revamping one of my Games on the Android Market, I wanted to add an onscreen timer. The built-in Chronometer class does 95% of what I needed, but getting that extra 5% was annoying enough that I decided to post my solution.

The OOTB Chronometer keeps tracks of time, but it can't tell you the number of seconds elapsed since it started. This is easily remedied, as I discovered from the fine people on StackOverflow. However, simply getting the elapsed time is not enough because the Chronometer will reset itself everytime the app is Paused or Killed. The two cases are quite different. When the app is Paused, it is still alive and may resume (ex. an incoming call). When the app is Killed, it needs to store the elapsed time somewhere so it can be recovered when the app is Restored (ex. orientation change, or b/c the OS needs memory).

So here is a stripped down solution that seems to do the job:

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.Menu;
import android.widget.Chronometer;

public class CustomChronometerActivity extends Activity {
    private static final String TAG = "CustomChronometerActivity";
    private static final String MS_ELAPSED = "com.etc.etc.MsElapsed";

    private MyChronometer chrono;

    @Override
 public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);

     //start the chronometer
     chrono = new MyChronometer(this);
     chrono.start();
     setContentView(chrono);
 }

    @Override
    protected void onPause() {
        Log.i(TAG, "onPause()");
        super.onPause();
        chrono.stop();
    }

    @Override
    protected void onResume() {
        Log.i(TAG, "onResume()");
        super.onResume();
        chrono.start();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.i(TAG, "onSaveInstanceState()");
        chrono.stop();
        outState.putInt(MS_ELAPSED, chrono.getMsElapsed());
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.i(TAG, "onRestoreInstanceState()");
        int ms = savedInstanceState.getInt(MS_ELAPSED);
        chrono.setMsElapsed(ms);
        chrono.start();
    }

    class MyChronometer extends Chronometer {

        public int msElapsed;
        public boolean isRunning = false;

        public MyChronometer(Context context) {
            super(context);
        }

        public int getMsElapsed() {
            return msElapsed;
        }

        public void setMsElapsed(int ms) {
            setBase(getBase() - ms);
            msElapsed  = ms;
        }

        @Override
        public void start() {
            super.start();
            setBase(SystemClock.elapsedRealtime() - msElapsed);
            isRunning = true;
        }

        @Override
        public void stop() {
            super.stop();
            if(isRunning) {
                msElapsed = (int)(SystemClock.elapsedRealtime() - this.getBase());
            }
            isRunning = false;
        }
    }
}

Certainly there are other ways to do this - either by implementing your own timer using Threads or Handlers, or perhaps by implementing an OnChronometerTickListener and subscribing to events. I rather like this solution, but if you're the clever sort and see some situation where this doesn't work or some reason why it might be a bad idea, please let me know.