1 package net.kazhik.android.tokyorunners;
3 import java.io.InputStream;
4 import java.text.SimpleDateFormat;
6 import java.util.Iterator;
8 import org.xmlpull.v1.XmlPullParser;
9 import org.xmlpull.v1.XmlPullParserFactory;
11 import android.app.AlertDialog;
12 import android.app.Dialog;
13 import android.app.TabActivity;
14 import android.content.Context;
15 import android.content.DialogInterface;
16 import android.content.Intent;
17 import android.content.SharedPreferences;
18 import android.database.Cursor;
19 import android.location.Location;
20 import android.location.LocationListener;
21 import android.location.LocationManager;
22 import android.location.LocationProvider;
23 import android.media.AudioManager;
24 import android.media.ToneGenerator;
25 import android.os.Bundle;
26 import android.os.Debug;
27 import android.os.Handler;
28 import android.os.SystemClock;
29 import android.preference.PreferenceManager;
30 import android.text.format.DateUtils;
31 import android.util.Log;
32 import android.view.KeyEvent;
33 import android.view.Menu;
34 import android.view.MenuItem;
35 import android.view.View;
36 import android.view.WindowManager;
37 import android.view.View.OnClickListener;
38 import android.widget.ArrayAdapter;
39 import android.widget.Button;
40 import android.widget.Chronometer;
41 import android.widget.ListView;
42 import android.widget.TabHost;
43 import android.widget.TextView;
44 import android.widget.Toast;
46 // TODO: Auto-generated Javadoc
48 * The Class TokyoRunners.
50 public class TokyoRunners extends TabActivity implements LocationListener {
53 private SharedPreferences m_prefs;
56 private LocationManager m_locMgr = null;
58 /** The m_elapsed time. */
59 private Chronometer m_elapsedTime;
61 /** The m_lap time. */
62 private Chronometer m_lapTime;
65 private boolean m_running = false;
68 private boolean m_useGPS = false;
70 /** The m_autosplit. */
71 private boolean m_autosplit = false;
74 private Handler m_handler = new Handler();
76 /** The m_running record. */
77 private static RunningRecord m_runningRecord;
79 /** The Constant m_minAccuracy. */
80 private static final int m_minAccuracy = 200;
83 * The Class StartStopButton.
85 class StartStopButton implements OnClickListener {
88 * @see android.view.View.OnClickListener#onClick(android.view.View)
90 public void onClick(View v) {
91 if (m_running == true) {
100 * The Class ResetLapButton.
102 class ResetLapButton implements OnClickListener {
105 * @see android.view.View.OnClickListener#onClick(android.view.View)
107 public void onClick(View v) {
108 if (m_running == true) {
117 * Called when the activity is first created.
119 * @param savedInstanceState the saved instance state
122 public void onCreate(Bundle savedInstanceState) {
123 super.onCreate(savedInstanceState);
125 // Debug.startMethodTracing("tokyorunners");
127 setContentView(R.layout.stopwatch);
130 TabHost tabHost = getTabHost();
132 TabHost.TabSpec stopwatchTab = tabHost.newTabSpec("stopwatch");
133 stopwatchTab.setIndicator(getString(R.string.title_stopwatch));
134 stopwatchTab.setContent(R.id.stopwatchmode);
135 tabHost.addTab(stopwatchTab);
137 TabHost.TabSpec mapTab = tabHost.newTabSpec("map");
138 mapTab.setIndicator(getString(R.string.title_map));
139 mapTab.setContent(new Intent(this, MapMode.class));
140 tabHost.addTab(mapTab);
142 tabHost.setCurrentTab(0);
144 tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
146 public void onTabChanged(String tag) {
147 if (tag.equals("stopwatch")) {
148 } else if (tag.equals("map")) {
154 m_runningRecord = new RunningRecord(getContentResolver());
156 m_prefs = PreferenceManager.getDefaultSharedPreferences(this);
159 m_useGPS = m_prefs.getBoolean("use_gps", true);
165 m_elapsedTime = (Chronometer) findViewById(R.id.elapsed_time);
166 m_lapTime = (Chronometer) findViewById(R.id.lap_time);
169 Button btnStartStop = (Button) findViewById(R.id.button_start_stop);
170 btnStartStop.setOnClickListener(new StartStopButton());
172 Button btnResetLap = (Button) findViewById(R.id.button_reset_lap);
173 btnResetLap.setOnClickListener(new ResetLapButton());
176 ListView lvLaptime = (ListView) findViewById(R.id.lap_history);
177 ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this,
179 lvLaptime.setAdapter(arrayAdapter);
181 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
188 private void initGps() {
189 m_locMgr = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
191 String strGpsFreq = m_prefs.getString("gps_frequency", "0");
192 int gps_freq = Integer.parseInt(strGpsFreq);
193 m_locMgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,
194 gps_freq * 1000, 5, this);
196 m_autosplit = m_prefs.getBoolean("auto_split", false);
198 int interval = Integer.parseInt(m_prefs.getString("split_interval",
200 m_runningRecord.setSplitInterval(interval);
206 * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
209 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
210 super.onActivityResult(requestCode, resultCode, data);
212 switch (requestCode) {
213 case Constants.REQUEST_CODE_SETTINGS:
214 m_useGPS = m_prefs.getBoolean("use_gps", true);
218 if (m_locMgr != null) {
219 m_locMgr.removeUpdates(this);
223 case Constants.REQUEST_CODE_HISTORY:
225 long selectedStartTime = data.getLongExtra("startTime", 0);
226 readHistory(selectedStartTime);
235 * @param filename the filename
237 private void readGpxFile(String filename) {
239 XmlPullParserFactory pullMaker;
241 pullMaker = XmlPullParserFactory.newInstance();
243 XmlPullParser parser = pullMaker.newPullParser();
244 InputStream fis = getAssets().open(filename);
246 parser.setInput(fis, null);
249 boolean inTrackPoint = false;
250 boolean inElevation = false;
251 boolean inName = false;
252 boolean inTime = false;
254 long currentTime = 0;
255 String pointName = null;
256 SimpleDateFormat timeFormatter = new SimpleDateFormat("y'-'M'-'d'T'H':'m':'s'Z'");
258 int eventType = parser.getEventType();
259 while (eventType != XmlPullParser.END_DOCUMENT) {
261 case XmlPullParser.START_DOCUMENT:
263 case XmlPullParser.START_TAG:
264 if (parser.getName().compareTo("trkpt") == 0) {
266 // now get the lat and lon
267 String lat = parser.getAttributeValue(null, "lat");
268 String lon = parser.getAttributeValue(null, "lon");
270 loc = new Location(LocationManager.GPS_PROVIDER);
271 loc.setLatitude(Double.parseDouble(lat));
272 loc.setLongitude(Double.parseDouble(lon));
275 } else if (parser.getName().compareTo("ele") == 0) {
279 } else if (parser.getName().compareTo("name") == 0) {
283 } else if (parser.getName().compareTo("time") == 0) {
289 case XmlPullParser.END_TAG:
290 if (parser.getName().equals("trkpt")) {
291 inTrackPoint = false;
292 m_runningRecord.addRecord(currentTime, loc, pointName, false);
296 } else if (parser.getName().equals("ele")) {
298 } else if (parser.getName().equals("name")) {
300 } else if (parser.getName().equals("time")) {
304 case XmlPullParser.TEXT:
308 pointName = parser.getText();
310 String timeStr = parser.getText();
311 currentTime = timeFormatter.parse(timeStr).getTime();
317 eventType = parser.next();
320 } catch (Exception e) {
321 Log.e("xml_perf", "Pull parser failed", e);
323 m_handler.post(new Runnable() {
325 Toast.makeText(getApplicationContext(), "Finished XmlPull parsing", Toast.LENGTH_SHORT).show();
333 * @param startTime the start time
335 private void readHistory(long startTime) {
337 String[] columns = { RunningRecordProvider.CURRENT_TIME,
338 RunningRecordProvider.LATITUDE,
339 RunningRecordProvider.LONGITUDE,
340 RunningRecordProvider.POINT_NAME };
341 String selection = RunningRecordProvider.START_TIME + " = ?";
342 String[] selectionArgs = { Long.toString(startTime) };
343 String sortOrder = RunningRecordProvider.CURRENT_TIME + " asc";
344 Cursor cursor = managedQuery(RunningRecordProvider.REC_URI, columns,
345 selection, selectionArgs, sortOrder);
346 if (cursor == null) {
350 Log.d(this.getClass().getName(), "row count:" + cursor.getCount());
354 ArrayAdapter<String> lapHistory = getLapHistoryAdapter();
356 long currentTime = 0;
359 String pointName = "";
360 long prevTime = startTime;
362 TextView elapsedTimeView = (TextView) findViewById(R.id.elapsed_time);
363 TextView lapTimeView = (TextView) findViewById(R.id.lap_time);
364 TextView distanceView = (TextView) findViewById(R.id.distance);
366 int idxCurrentTime = cursor
367 .getColumnIndex(RunningRecordProvider.CURRENT_TIME);
368 int idxLatitude = cursor.getColumnIndex(RunningRecordProvider.LATITUDE);
369 int idxLongitude = cursor
370 .getColumnIndex(RunningRecordProvider.LONGITUDE);
371 int idxPointName = cursor
372 .getColumnIndex(RunningRecordProvider.POINT_NAME);
374 while (cursor.moveToNext()) {
375 currentTime = cursor.getLong(idxCurrentTime);
376 latitude = cursor.getInt(idxLatitude);
377 longitude = cursor.getInt(idxLongitude);
378 pointName = cursor.getString(idxPointName);
380 // Log.d(this.getClass().getName(), currentTime + ";" + pointName);
384 if (latitude != 0 && longitude != 0) {
385 loc = new Location(LocationManager.GPS_PROVIDER);
387 loc.setLatitude((double) (latitude / 1E6));
388 loc.setLongitude((double) (longitude / 1E6));
390 m_runningRecord.addRecord(currentTime, loc, pointName, false);
392 if (pointName.length() == 0) {
393 } else if (currentTime != startTime) {
394 StringBuffer strBuff = new StringBuffer();
395 strBuff.append(pointName);
396 strBuff.append(": ");
397 strBuff.append(DateUtils
398 .formatElapsedTime((currentTime - startTime) / 1000));
400 strBuff.append(DateUtils
401 .formatElapsedTime((currentTime - prevTime) / 1000));
402 lapHistory.insert(strBuff.toString(), 0);
403 prevTime = currentTime;
408 elapsedTimeView.setText(DateUtils
409 .formatElapsedTime((currentTime - startTime) / 1000));
411 lapTimeView.setText(DateUtils
412 .formatElapsedTime((currentTime - prevTime) / 1000));
414 distanceView.setText(m_runningRecord.getDistanceString());
419 * @see android.app.ActivityGroup#onDestroy()
422 public void onDestroy() {
423 Log.d(this.getClass().getName(), "onDestroy():");
424 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
426 m_runningRecord = null;
429 m_locMgr.removeUpdates(this);
437 * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
440 public boolean onCreateOptionsMenu(Menu menu) {
441 menu.add(Menu.NONE, Constants.MENU_HISTORY, Menu.NONE,
442 R.string.menu_history).setIcon(R.drawable.history2);
443 menu.add(Menu.NONE, Constants.MENU_SETTING, Menu.NONE,
444 R.string.menu_setting).setIcon(
445 android.R.drawable.ic_menu_preferences);
446 return super.onCreateOptionsMenu(menu);
450 * @see android.app.Activity#onPrepareOptionsMenu(android.view.Menu)
453 public boolean onPrepareOptionsMenu(Menu menu) {
455 if (getTabHost().getCurrentTabTag().equals("stopwatch")) {
456 enabled = !m_running;
460 menu.findItem(Constants.MENU_HISTORY).setEnabled(enabled);
462 MenuItem item = menu.findItem(Constants.MENU_HERE);
464 item.setEnabled(!m_running);
467 return super.onPrepareOptionsMenu(menu);
471 * @see android.app.Activity#onCreateDialog(int)
474 public Dialog onCreateDialog(int id) {
476 AlertDialog.Builder builder = new AlertDialog.Builder(this);
478 case Constants.DIALOG_EXIT_ID:
479 builder.setTitle(R.string.dialog_exit_title);
480 builder.setMessage(R.string.dialog_exit_message);
481 builder.setPositiveButton(R.string.dialog_yes,
482 new DialogInterface.OnClickListener() {
484 public void onClick(DialogInterface dialog, int which) {
485 // Debug.stopMethodTracing();
490 builder.setNegativeButton(R.string.dialog_no,
491 new DialogInterface.OnClickListener() {
493 public void onClick(DialogInterface dialog, int which) {
504 AlertDialog alertDialog = builder.create();
509 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
512 public boolean onOptionsItemSelected(MenuItem item) {
514 switch (item.getItemId()) {
515 case Constants.MENU_HISTORY:
516 intent = new Intent(this, RunningHistory.class);
517 intent.setAction(Intent.ACTION_VIEW);
518 startActivityForResult(intent, Constants.REQUEST_CODE_HISTORY);
520 case Constants.MENU_SETTING:
521 intent = new Intent(this, Config.class);
522 intent.setAction(Intent.ACTION_VIEW);
523 startActivityForResult(intent, Constants.REQUEST_CODE_SETTINGS);
532 * @see android.location.LocationListener#onLocationChanged(android.location.Location)
534 public void onLocationChanged(Location location) {
535 ExLog.put(location.toString());
538 if (m_running == false) {
543 Location prevLocation = m_runningRecord.getPrevLocation();
544 if (prevLocation == null) {
545 ToneGenerator toneGenerator = new ToneGenerator(
546 AudioManager.STREAM_SYSTEM, ToneGenerator.MAX_VOLUME);
547 toneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP);
548 toneGenerator.stopTone();
553 if (location.hasAccuracy()) {
554 if (location.getAccuracy() > m_minAccuracy) {
555 ExLog.put("Accuracy low: " + location.getAccuracy() + ";"
559 if (prevLocation != null
560 && prevLocation.distanceTo(location) < location
562 ExLog.put("Move not enough: " + location.getAccuracy() + ";"
563 + prevLocation.distanceTo(location));
569 String pointName = m_runningRecord.addRecord(location.getTime(),
573 if (pointName.length() > 0) {
574 String laptimeStr = calculateSplitAndLap();
576 ArrayAdapter<String> lapHistory = getLapHistoryAdapter();
578 String strLaptime = pointName + ": " + laptimeStr;
580 lapHistory.insert(strLaptime, 0);
584 TextView distanceView = (TextView) findViewById(R.id.distance);
585 distanceView.setText(m_runningRecord.getDistanceString());
588 Intent intent = new Intent("NewRunningRecord");
589 intent.putExtra("RunningRecord", location.getTime());
590 sendBroadcast(intent);
595 * @see android.location.LocationListener#onProviderDisabled(java.lang.String)
597 public void onProviderDisabled(String provider) {
598 Log.d(this.getClass().getName(), "onProviderDisabled()");
599 m_locMgr.removeUpdates(this);
604 * @see android.location.LocationListener#onProviderEnabled(java.lang.String)
606 public void onProviderEnabled(String provider) {
607 Log.d(this.getClass().getName(), "onProviderEnabled()");
611 * @see android.location.LocationListener#onStatusChanged(java.lang.String, int, android.os.Bundle)
613 public void onStatusChanged(String provider, int status, Bundle extras) {
614 String statusStr = "";
616 case LocationProvider.AVAILABLE:
617 statusStr = "Available";
619 case LocationProvider.OUT_OF_SERVICE:
620 statusStr = "Out of service";
622 case LocationProvider.TEMPORARILY_UNAVAILABLE:
623 statusStr = "Temporarily unavailable";
626 statusStr = "Unknown status";
629 Log.d(this.getClass().getName(), "onStatusChanged(): " + statusStr);
636 private void start() {
638 m_elapsedTime.start();
641 long currentTime = System.currentTimeMillis();
642 Location startPoint = null;
644 startPoint = m_locMgr
645 .getLastKnownLocation(LocationManager.GPS_PROVIDER);
647 if (Math.abs(currentTime - startPoint.getTime()) > 3 * 1000) {
648 Log.d(this.getClass().getName(), "GPS data isn't accurate.");
651 currentTime = startPoint.getTime();
654 m_runningRecord.addRecord(currentTime, startPoint,
655 getString(R.string.point_start));
658 Button startStopButton = (Button) findViewById(R.id.button_start_stop);
659 Button resetLapButton = (Button) findViewById(R.id.button_reset_lap);
660 startStopButton.setText(R.string.button_stop);
662 resetLapButton.setEnabled(false);
664 resetLapButton.setText(R.string.button_lap);
671 private void stop() {
672 long currentTime = System.currentTimeMillis();
673 Location finishPoint = null;
675 finishPoint = m_locMgr
676 .getLastKnownLocation(LocationManager.GPS_PROVIDER);
677 if (Math.abs(System.currentTimeMillis() - finishPoint.getTime()) > 3 * 1000) {
678 Log.d(this.getClass().getName(), "GPS data isn't accurate.");
681 currentTime = finishPoint.getTime();
685 m_runningRecord.addRecord(currentTime, finishPoint,
686 getString(R.string.point_finish));
688 m_elapsedTime.stop();
691 Button startStopButton = (Button) findViewById(R.id.button_start_stop);
692 Button resetLapButton = (Button) findViewById(R.id.button_reset_lap);
693 startStopButton.setText(R.string.button_start);
695 resetLapButton.setEnabled(true);
697 resetLapButton.setText(R.string.button_reset);
705 private void reset() {
706 m_runningRecord.clearRecord();
708 long elapsedRealTime = SystemClock.elapsedRealtime();
709 m_elapsedTime.setBase(elapsedRealTime);
710 m_lapTime.setBase(elapsedRealTime);
713 TextView distanceView = (TextView) findViewById(R.id.distance);
714 distanceView.setText(m_runningRecord.getDistanceString());
716 ArrayAdapter<String> lapHistory = getLapHistoryAdapter();
722 * Calculate split and lap.
726 private String calculateSplitAndLap() {
727 long elapsedRealTime = SystemClock.elapsedRealtime();
728 long splitTimeMillis = elapsedRealTime - m_elapsedTime.getBase();
729 long lapTimeMillis = elapsedRealTime - m_lapTime.getBase();
730 String splitTimeStr = DateUtils
731 .formatElapsedTime(splitTimeMillis / 1000);
732 String lapTimeStr = DateUtils.formatElapsedTime(lapTimeMillis / 1000);
734 m_lapTime.setBase(elapsedRealTime);
736 return splitTimeStr + "/" + lapTimeStr;
741 * Gets the lap history adapter.
743 * @return the lap history adapter
745 @SuppressWarnings("unchecked")
746 private ArrayAdapter<String> getLapHistoryAdapter() {
747 ListView listLaptime = (ListView) findViewById(R.id.lap_history);
749 return (ArrayAdapter<String>) listLaptime.getAdapter();
757 String laptimeStr = calculateSplitAndLap();
759 ArrayAdapter<String> lapHistory = getLapHistoryAdapter();
761 String strLapNo = Integer.toString(lapHistory.getCount() + 1);
763 lapHistory.insert(strLapNo + ": " + laptimeStr, 0);
765 m_runningRecord.addRecord(System.currentTimeMillis(), null, strLapNo);
772 * @param recordTime the record time
776 public static Record getRecord(long recordTime) {
778 return m_runningRecord.getRecord(new Date(recordTime));
783 * Gets the next record.
785 * @param recordTime the record time
787 * @return the next record
789 public static Record getNextRecord(long recordTime) {
791 return m_runningRecord.getNextRecord(new Date(recordTime));
796 * Gets the record iterator.
798 * @return the record iterator
800 public static Iterator<Record> getRecordIterator() {
801 return m_runningRecord.getIterator();
805 * @see android.app.Activity#dispatchKeyEvent(android.view.KeyEvent)
808 public boolean dispatchKeyEvent(KeyEvent event) {
809 return super.dispatchKeyEvent(event);
813 * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
816 public boolean onKeyDown(int keyCode, KeyEvent event) {
817 if (keyCode == KeyEvent.KEYCODE_BACK) {
818 showDialog(Constants.DIALOG_EXIT_ID);
822 return super.onKeyDown(keyCode, event);