Smart Watch

Smart Watch – Part 2

In the previous article we looked at some of the considerations that we must make for developing apps for the smaller form factor of the i’m watch Android smart watch, and outlined the football timer app that we will look at in this article.

I'm WatchAs was mentioned previously, the app was tested during the English Championship match between my beloved Watford FC and Sheffield Wednesday on Saturday 14th December 2013. The app performed well, and the timings that we got certainly tallied with the stoppage time added on by the referee, but as for the performance of my team… I just don’t want to talk about it, OK?

Anyway, let’s start by looking at the single layout of the app:




	

	

	

	

	


We have the five different times that were mentioned in the pervious article: In the top left corner is the current time of day; in the top right corner is the actual time played (the total time minus the stoppages); in the bottom left is the duration of the current stoppage (if the current state is Paused); in the bottom right is the total stoppage time; and in the centre is the gross time (the elapsed time since the start before stoppages have been subtracted).

Some of these times will be zero until we encounter a stoppage during the game, so all but the time of day and the gross time are invisible at the start. The actual time played plus the stoppage time should equal the gross time.

The logic is almost entirely pure Java, and we have a GestureDetector which handles the touch events which we’ll use to change states of our state machine, and also clear things:

public class MainActivity extends Activity {
	private TextView mTime;
	private TextView mPlayed;
	private TextView mCurrent;
	private TextView mStoppages;
	private TextView mGross;

	static final private Format sTimeFmt = 
		new SimpleDateFormat("HH:mm");

	private long mStartTime = 0;
	private long mAccumulated = 0;
	private long mAllStoppages = 0;
	private long mCurrentStoppage = 0;

	private Handler mHandler;

	private GestureDetector mDetector;
	private GestureDetector.OnGestureListener mListener = 
		new GestureDetector.SimpleOnGestureListener() {

		@Override
		public boolean onSingleTapConfirmed(MotionEvent e) {
			if (mStartTime == 0) {
				startTimer();
			} else if (mCurrentStoppage == 0) {
				pauseTimer();
			} else {
				resumeTimer();
			}
			return true;
		}

		@Override
		public void onLongPress(MotionEvent e) {
			if (mStartTime > 0) {
				stopTimer();
			} else {
				clearTimer();
			}
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mTime = (TextView) findViewById(R.id.time);
		mPlayed = (TextView) findViewById(R.id.played);
		mCurrent = (TextView) findViewById(R.id.current);
		mStoppages = (TextView) findViewById(R.id.stoppages);
		mGross = (TextView) findViewById(R.id.gross);
		mHandler = new Handler();
		mDetector = new GestureDetector(this, mListener);
	}

	private void updateTime() {
		mTime.setText(sTimeFmt.format(new Date()));
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return mDetector.onTouchEvent(event);
	}

	private void startTimer() {
		mStartTime = System.currentTimeMillis();
		mCurrentStoppage = 0;
	}

	private void stopTimer() {
		mAccumulated += System.currentTimeMillis() 
			- mStartTime;
		mStartTime = 0;
	}

	private void pauseTimer() {
		mCurrentStoppage = System.currentTimeMillis();
		update();
	}

	private void resumeTimer() {
		mAllStoppages += System.currentTimeMillis() 
			- mCurrentStoppage;
		mCurrentStoppage = 0;
		update();
	}

	private void clearTimer() {
		mStartTime = 0;
		mAccumulated = 0;
		mCurrentStoppage = 0;
		mAllStoppages = 0;
		update();
	}

	private void update() {
		long now = System.currentTimeMillis();
		long elapsed = mAccumulated;
		if (mStartTime > 0) {
			elapsed += now - mStartTime;
		}
		if (elapsed > 99 * 60000) {
			elapsed = 99 * 60000;
			mStartTime = 0;
		}
		long allStoppages = mAllStoppages;
		if (mCurrentStoppage > 0) {
			allStoppages += now - mCurrentStoppage;
			mCurrent.setVisibility(View.VISIBLE);
			mCurrent.setText(
				formatTime(now - mCurrentStoppage, 
					true));
			mStoppages.setVisibility(View.VISIBLE);
			mStoppages.setText(
				formatTime(allStoppages, 
					true));
			mPlayed.setVisibility(View.VISIBLE);
		} else {
			mCurrent.setVisibility(View.INVISIBLE);
			if (allStoppages == 0) {
				mStoppages.setVisibility(View.INVISIBLE);
				mPlayed.setVisibility(View.INVISIBLE);
			} else {
				mStoppages.setVisibility(View.VISIBLE);
				mPlayed.setVisibility(View.VISIBLE);
				mStoppages.setText(
					formatTime(allStoppages, 
						true));
			}
		}
		mPlayed.setText(
			formatTime(elapsed - allStoppages, 
				true));
		mGross.setText(
			formatTime(elapsed, false));
		updateTime();
	}

	private String formatTime(long millis, boolean round) {
		long mins = millis / 60000;
		long secs = ((millis - (mins * 60000)) + 
			(round ? 500 : 0)) / 1000;
		return getResources().getString(R.string.time, 
			mins, secs);
	}
}

Hopefully the logic here should be fairly straightforward, so I won’t bother explaining the details.

So, we have the layout and the basic state machine and timers, but what we’re lacking is the mechanism which will update the timers as the match is in progress. In the concluding article in this series we’ll get the timers connected up so that we have a functioning timer app.

The source code for this article can be found here.

Many thanks to Sebastiano Poggi and the rest of the folks at i’m SpA for arranging the loan device which have made this series of articles possible.

© 2013 – 2014, Mark Allison. All rights reserved.

Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.