diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index d6f558ac4..442d922eb 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -17,8 +17,8 @@ limitations under the License. + android:versionCode="26" + android:versionName="1.1.3"> 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..ef7931896 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,11 +17,13 @@ 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.content.Context; import android.location.Location; import android.os.Build; @@ -30,6 +32,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 +40,35 @@ 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 final Context context; 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(Context context) { + this.context = context; - public TcxTrackWriter() { + timestampFormatter = new SimpleDateFormat(TIMESTAMP_FORMAT); + timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); } @Override @@ -93,16 +111,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 +128,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 +145,7 @@ public class TcxTrackWriter implements TrackFormatWriter { } pw.println(" "); Date d = new Date(location.getTime()); - pw.println(" "); + pw.println(" "); pw.println(" "); pw.print(" "); @@ -194,10 +209,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 +230,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(context)); + + pw.println(""); + pw.println(""); + pw.format("%d\n", MyTracksUtils.getMyTracksVersionCode(context)); + pw.println("0"); + pw.println(""); + pw.format("%s\n", MyTracksUtils.isRelease(context) ? 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 +256,15 @@ public class TcxTrackWriter implements TrackFormatWriter { public void writeWaypoint(Waypoint waypoint) { // TODO Write out the waypoints somewhere. } + + private String categoryToTcxSport(String category) { + category = category.trim(); + 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/io/TrackWriterFactory.java b/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriterFactory.java index 55261f8a5..785454b0b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriterFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriterFactory.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 @@ -53,7 +53,7 @@ public class TrackWriterFactory { TCX { @Override public TrackFormatWriter newFormatWriter(Context context) { - return new TcxTrackWriter(); + return new TcxTrackWriter(context); } }; 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..8e1c6e22b 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 @@ -18,7 +18,6 @@ package com.google.android.apps.mytracks.util; 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 +37,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 +46,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 +88,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 +151,7 @@ public class MyTracksUtils { /** * Decimates the given track for the given precision. - * + * * @param track a track * @param precision desired precision in meters */ @@ -165,7 +164,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 +178,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 +213,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 +227,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 +238,7 @@ public class MyTracksUtils { /** * Gets a location from a GeoPoint. - * + * * @param p a GeoPoint * @return the corresponding location */ @@ -259,17 +258,17 @@ 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; } @@ -279,17 +278,34 @@ public class MyTracksUtils { * @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; + } + + /** + * 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(Context context) { + PackageInfo packageInfo = getPackageInfo(context, 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 PackageInfo"); + 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..874b7ebfa 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java @@ -4,21 +4,32 @@ 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(); + TrackFormatWriter writer = new TcxTrackWriter(null); String result = writeTrack(writer); Document doc = parseXmlDocument(result); @@ -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"));