diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java
index dfd3513ad..1140dba89 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -17,10 +17,11 @@ package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
+import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
-import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
+import com.google.android.apps.mytracks.util.MyTracksUtils;
import android.location.Location;
import android.os.Build;
@@ -30,6 +31,7 @@ import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.Locale;
import java.util.TimeZone;
/**
@@ -37,20 +39,32 @@ import java.util.TimeZone;
* As defined by:
* http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2
*
+ * The TCX file written by this class has been verified as compatible with
+ * Garmin Training Center 3.5.3.
+ *
* @author Sandor Dornbush
*/
public class TcxTrackWriter implements TrackFormatWriter {
+ protected static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+
+ // These are the only sports allowed by the TCX v2 specification for fields
+ // of type Sport_t.
+ private static final String TCX_SPORT_BIKING = "Biking";
+ private static final String TCX_SPORT_RUNNING = "Running";
+ private static final String TCX_SPORT_OTHER = "Other";
+
+ // Values for fields of type Build_t/Type.
+ private static final String TCX_TYPE_RELEASE = "Release";
+ private static final String TCX_TYPE_INTERNAL = "Internal";
+
+ private final SimpleDateFormat timestampFormatter;
private PrintWriter pw = null;
private Track track;
- static final SimpleDateFormat TIMESTAMP_FORMAT =
- new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
- static {
- TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
- }
-
public TcxTrackWriter() {
+ timestampFormatter = new SimpleDateFormat(TIMESTAMP_FORMAT);
+ timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@Override
@@ -93,16 +107,13 @@ public class TcxTrackWriter implements TrackFormatWriter {
if (pw == null) {
return;
}
+
+ String startTime = timestampFormatter.format(track.getStatistics().getStartTime());
+
pw.println(" ");
- pw.print(" ");
- pw.print(" ");
- pw.print(TIMESTAMP_FORMAT.format(track.getStatistics().getStartTime()));
- pw.println("");
- pw.println(" ");
+ pw.format(" \n", categoryToTcxSport(track.getCategory()));
+ pw.format(" %s\n", startTime);
+ pw.format(" \n", startTime);
pw.print(" ");
pw.print(track.getStatistics().getTotalTime() / 1000);
pw.println("");
@@ -113,7 +124,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
// Calories are a required element just put in 0.
pw.print("0");
pw.println("Active");
- pw.println("Manual)");
+ pw.println("Manual");
}
@Override
@@ -130,7 +141,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
}
pw.println(" ");
Date d = new Date(location.getTime());
- pw.println(" ");
+ pw.println(" ");
pw.println(" ");
pw.print(" ");
@@ -194,10 +205,18 @@ public class TcxTrackWriter implements TrackFormatWriter {
}
pw.println(" ");
pw.print(" ");
- pw.print("");
- pw.print(Build.MODEL);
- pw.print("");
- pw.println(")");
+ pw.format("%s\n", Build.MODEL);
+
+ // The following code is correct. ID is inconsistently capitalized in the
+ // TCX schema.
+ pw.println("0");
+ pw.println("0");
+
+ pw.println("");
+ pw.println("0");
+ pw.println("0");
+ pw.println("");
+ pw.println("");
pw.println(" ");
pw.println(" ");
}
@@ -207,8 +226,24 @@ public class TcxTrackWriter implements TrackFormatWriter {
if (pw == null) {
return;
}
- pw.print(" ");
- pw.print("My Tracks by Google");
+ pw.println(" ");
+
+ // We put the version in the name because there isn't a better place for
+ // it. The TCX schema tightly defined the Version tag, so we can't put it
+ // there. They've similarly constrained the PartNumber tag, so it can't go
+ // there either.
+ pw.format("My Tracks %s by Google\n", MyTracksUtils.getMyTracksVersion());
+
+ pw.println("");
+ pw.println("");
+ pw.format("%d\n", MyTracksUtils.getMyTracksVersionCode());
+ pw.println("0");
+ pw.println("");
+ pw.format("%s\n", MyTracksUtils.isRelease() ? TCX_TYPE_RELEASE
+ : TCX_TYPE_INTERNAL);
+ pw.println("");
+ pw.format("%s\n", Locale.getDefault().getLanguage());
+ pw.println("000-00000-00");
pw.println("");
pw.println("");
}
@@ -217,4 +252,14 @@ public class TcxTrackWriter implements TrackFormatWriter {
public void writeWaypoint(Waypoint waypoint) {
// TODO Write out the waypoints somewhere.
}
+
+ private String categoryToTcxSport(String category) {
+ if (category.equalsIgnoreCase(TCX_SPORT_RUNNING)) {
+ return TCX_SPORT_RUNNING;
+ } else if (category.equalsIgnoreCase(TCX_SPORT_BIKING)) {
+ return TCX_SPORT_BIKING;
+ } else {
+ return TCX_SPORT_OTHER;
+ }
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java
index 59ba09b3d..1170726be 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,10 +15,10 @@
*/
package com.google.android.apps.mytracks.util;
+import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
-
import com.google.android.maps.GeoPoint;
import android.app.Activity;
@@ -38,7 +38,7 @@ import java.util.Stack;
/**
* Utility class for decimating tracks at a given level of precision.
- *
+ *
* @author Leif Hendrik Wilden
*/
public class MyTracksUtils {
@@ -47,7 +47,7 @@ public class MyTracksUtils {
/**
* Computes the distance on the two sphere between the point c0 and the line
* segment c1 to c2.
- *
+ *
* @param c0 the first coordinate
* @param c1 the beginning of the line segment
* @param c2 the end of the lone segment
@@ -89,7 +89,7 @@ public class MyTracksUtils {
/**
* Decimates the given locations for a given zoom level. This uses a
* Douglas-Peucker decimation algorithm.
- *
+ *
* @param tolerance in meters
* @param locations input
* @param decimated output
@@ -152,7 +152,7 @@ public class MyTracksUtils {
/**
* Decimates the given track for the given precision.
- *
+ *
* @param track a track
* @param precision desired precision in meters
*/
@@ -165,7 +165,7 @@ public class MyTracksUtils {
/**
* Limits number of points by dropping any points beyond the given number of
* points. Note: That'll actually discard points.
- *
+ *
* @param track a track
* @param numberOfPoints maximum number of points
*/
@@ -179,7 +179,7 @@ public class MyTracksUtils {
/**
* Splits a track in multiple tracks where each piece has less or equal than
* maxPoints.
- *
+ *
* @param track the track to split
* @param maxPoints maximum number of points for each piece
* @return a list of one or more track pieces
@@ -214,7 +214,7 @@ public class MyTracksUtils {
/**
* Test if a given GeoPoint is valid, i.e. within physical bounds.
- *
+ *
* @param geoPoint the point to be tested
* @return true, if it is a physical location on earth.
*/
@@ -228,7 +228,7 @@ public class MyTracksUtils {
* on Earth. Note: The special separator locations (which have latitude =
* 100) will not qualify as valid. Neither will locations with lat=0 and lng=0
* as these are most likely "bad" measurements which often cause trouble.
- *
+ *
* @param location the location to test
* @return true if the location is a valid location.
*/
@@ -239,7 +239,7 @@ public class MyTracksUtils {
/**
* Gets a location from a GeoPoint.
- *
+ *
* @param p a GeoPoint
* @return the corresponding location
*/
@@ -259,37 +259,69 @@ public class MyTracksUtils {
* Returns whether or not this is a release build.
*/
public static boolean isRelease(Context context) {
- try {
- Signature [] sigs = context.getPackageManager().getPackageInfo(
- context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
- for (Signature sig : sigs) {
- if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) {
- return true;
- }
- }
- } catch (NameNotFoundException e) {
- Log.e(MyTracksConstants.TAG, "Unable to get signatures", e);
+ PackageInfo packageInfo = getPackageInfo(context, PackageManager.GET_SIGNATURES);
+ if (packageInfo == null) {
+ return false;
}
+
+ for (Signature sig: packageInfo.signatures) {
+ if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) {
+ return true;
+ }
+ }
+
return false;
}
+ /**
+ * @see isRelease(Context)
+ */
+ public static boolean isRelease() {
+ return isRelease(MyTracks.getInstance());
+ }
+
/**
* Get the My Tracks version from the manifest.
*
* @return the version, or an empty string in case of failure.
*/
public static String getMyTracksVersion(Context context) {
+ PackageInfo packageInfo = getPackageInfo(context, PackageManager.GET_META_DATA);
+ return packageInfo == null ? "" : packageInfo.versionName;
+ }
+
+ /**
+ * @see getMyTracksVersion(Context)
+ */
+ public static String getMyTracksVersion() {
+ return getMyTracksVersion(MyTracks.getInstance());
+ }
+
+ /**
+ * Return the My Tracks version code from the manifest.
+ *
+ * @return the version code, or an empty string in case of failure.
+ */
+ public static int getMyTracksVersionCode() {
+ PackageInfo packageInfo = getPackageInfo(MyTracks.getInstance(),
+ PackageManager.GET_META_DATA);
+ return packageInfo == null ? -1 : packageInfo.versionCode;
+ }
+
+ private static PackageInfo getPackageInfo(Context context, int flags) {
+ if (context == null) {
+ Log.w(MyTracksConstants.TAG, "No context found when attempting to get version");
+ return null;
+ }
try {
- PackageInfo pi = context.getPackageManager().getPackageInfo(
- "com.google.android.maps.mytracks",
- PackageManager.GET_META_DATA);
- return pi.versionName;
+ return context.getPackageManager().getPackageInfo(
+ "com.google.android.maps.mytracks", flags);
} catch (NameNotFoundException e) {
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
- return "";
+ return null;
}
}
-
+
/**
* Tries to acquire a partial wake lock if not already acquired. Logs errors
* and gives up trying in case the wake lock cannot be acquired.
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java
index 897df2792..aacdf1146 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java
@@ -4,18 +4,29 @@ package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.TimeZone;
+
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.Date;
-import java.util.List;
-
/**
* Tests for the GPX track exporter.
*
* @author Sandor Dornbush
*/
public class TcxTrackWriterTest extends TrackFormatWriterTest {
+ private DateFormat timestampFormatter;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ timestampFormatter = new SimpleDateFormat(TcxTrackWriter.TIMESTAMP_FORMAT);
+ timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
public void testXmlOutput() throws Exception {
TrackFormatWriter writer = new TcxTrackWriter();
@@ -58,8 +69,8 @@ public class TcxTrackWriterTest extends TrackFormatWriterTest {
getChildTextValue(posTag, "LatitudeDegrees"));
assertEquals(Double.toString(loc.getLongitude()),
getChildTextValue(posTag, "LongitudeDegrees"));
- assertEquals(
- TcxTrackWriter.TIMESTAMP_FORMAT.format(new Date(loc.getTime())),
+
+ assertEquals(timestampFormatter.format(loc.getTime()),
getChildTextValue(tag, "Time"));
assertEquals(Double.toString(loc.getAltitude()),
getChildTextValue(tag, "AltitudeMeters"));