forked from upstream-mirrors/OpenTracks
Fix TCX export
This commit is contained in:
@@ -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(" <Activities>");
|
||||
pw.print(" <Activity Sport=\"");
|
||||
if (track.getCategory() != null) {
|
||||
pw.print(track.getCategory());
|
||||
}
|
||||
pw.println("\">");
|
||||
pw.print(" <Id>");
|
||||
pw.print(TIMESTAMP_FORMAT.format(track.getStatistics().getStartTime()));
|
||||
pw.println("</Id>");
|
||||
pw.println(" <Lap>");
|
||||
pw.format(" <Activity Sport=\"%s\">\n", categoryToTcxSport(track.getCategory()));
|
||||
pw.format(" <Id>%s</Id>\n", startTime);
|
||||
pw.format(" <Lap StartTime=\"%s\">\n", startTime);
|
||||
pw.print(" <TotalTimeSeconds>");
|
||||
pw.print(track.getStatistics().getTotalTime() / 1000);
|
||||
pw.println("</TotalTimeSeconds>");
|
||||
@@ -113,7 +124,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
// Calories are a required element just put in 0.
|
||||
pw.print("<Calories>0</Calories>");
|
||||
pw.println("<Intensity>Active</Intensity>");
|
||||
pw.println("<TriggerMethod>Manual</TriggerMethod>)");
|
||||
pw.println("<TriggerMethod>Manual</TriggerMethod>");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,7 +141,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
}
|
||||
pw.println(" <Trackpoint>");
|
||||
Date d = new Date(location.getTime());
|
||||
pw.println(" <Time>" + TIMESTAMP_FORMAT.format(d) + "</Time>");
|
||||
pw.println(" <Time>" + timestampFormatter.format(d) + "</Time>");
|
||||
pw.println(" <Position>");
|
||||
|
||||
pw.print(" <LatitudeDegrees>");
|
||||
@@ -194,10 +205,18 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
}
|
||||
pw.println(" </Lap>");
|
||||
pw.print(" <Creator xsi:type=\"Device_t\">");
|
||||
pw.print("<Name>");
|
||||
pw.print(Build.MODEL);
|
||||
pw.print("</Name>");
|
||||
pw.println("</Creator>)");
|
||||
pw.format("<Name>%s</Name>\n", Build.MODEL);
|
||||
|
||||
// The following code is correct. ID is inconsistently capitalized in the
|
||||
// TCX schema.
|
||||
pw.println("<UnitId>0</UnitId>");
|
||||
pw.println("<ProductID>0</ProductID>");
|
||||
|
||||
pw.println("<Version>");
|
||||
pw.println("<VersionMajor>0</VersionMajor>");
|
||||
pw.println("<VersionMinor>0</VersionMinor>");
|
||||
pw.println("</Version>");
|
||||
pw.println("</Creator>");
|
||||
pw.println(" </Activity>");
|
||||
pw.println(" </Activities>");
|
||||
}
|
||||
@@ -207,8 +226,24 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
if (pw == null) {
|
||||
return;
|
||||
}
|
||||
pw.print(" <Author xsi:type=\"Application_t\">");
|
||||
pw.print("<Name>My Tracks by Google</Name>");
|
||||
pw.println(" <Author xsi:type=\"Application_t\">");
|
||||
|
||||
// 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("<Name>My Tracks %s by Google</Name>\n", MyTracksUtils.getMyTracksVersion());
|
||||
|
||||
pw.println("<Build>");
|
||||
pw.println("<Version>");
|
||||
pw.format("<VersionMajor>%d</VersionMajor>\n", MyTracksUtils.getMyTracksVersionCode());
|
||||
pw.println("<VersionMinor>0</VersionMinor>");
|
||||
pw.println("</Version>");
|
||||
pw.format("<Type>%s</Type>\n", MyTracksUtils.isRelease() ? TCX_TYPE_RELEASE
|
||||
: TCX_TYPE_INTERNAL);
|
||||
pw.println("</Build>");
|
||||
pw.format("<LangID>%s</LangID>\n", Locale.getDefault().getLanguage());
|
||||
pw.println("<PartNumber>000-00000-00</PartNumber>");
|
||||
pw.println("</Author>");
|
||||
pw.println("</TrainingCenterDatabase>");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -259,34 +259,66 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user