Merging my maps code to head.

This commit is contained in:
Rodrigo Damazio
2011-03-16 14:23:19 -07:00
5 changed files with 145 additions and 68 deletions
+2 -2
View File
@@ -17,8 +17,8 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.maps.mytracks" package="com.google.android.maps.mytracks"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="25" android:versionCode="26"
android:versionName="1.1.3rc1"> android:versionName="1.1.3">
<application android:icon="@drawable/arrow_icon" <application android:icon="@drawable/arrow_icon"
android:label="@string/app_name" android:label="@string/app_name"
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent"> android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
@@ -1,12 +1,12 @@
/* /*
* Copyright 2010 Google Inc. * Copyright 2010 Google Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * 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 * use this file except in compliance with the License. You may obtain a copy of
* the License at * the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * 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.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor; 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.Track;
import com.google.android.apps.mytracks.content.Waypoint; 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.io.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import android.content.Context;
import android.location.Location; import android.location.Location;
import android.os.Build; import android.os.Build;
@@ -30,6 +32,7 @@ import java.io.PrintWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
/** /**
@@ -37,20 +40,35 @@ import java.util.TimeZone;
* As defined by: * As defined by:
* http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 * 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 * @author Sandor Dornbush
*/ */
public class TcxTrackWriter implements TrackFormatWriter { 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 PrintWriter pw = null;
private Track track; private Track track;
static final SimpleDateFormat TIMESTAMP_FORMAT = public TcxTrackWriter(Context context) {
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); this.context = context;
static {
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
public TcxTrackWriter() { timestampFormatter = new SimpleDateFormat(TIMESTAMP_FORMAT);
timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
} }
@Override @Override
@@ -93,16 +111,13 @@ public class TcxTrackWriter implements TrackFormatWriter {
if (pw == null) { if (pw == null) {
return; return;
} }
String startTime = timestampFormatter.format(track.getStatistics().getStartTime());
pw.println(" <Activities>"); pw.println(" <Activities>");
pw.print(" <Activity Sport=\""); pw.format(" <Activity Sport=\"%s\">\n", categoryToTcxSport(track.getCategory()));
if (track.getCategory() != null) { pw.format(" <Id>%s</Id>\n", startTime);
pw.print(track.getCategory()); pw.format(" <Lap StartTime=\"%s\">\n", startTime);
}
pw.println("\">");
pw.print(" <Id>");
pw.print(TIMESTAMP_FORMAT.format(track.getStatistics().getStartTime()));
pw.println("</Id>");
pw.println(" <Lap>");
pw.print(" <TotalTimeSeconds>"); pw.print(" <TotalTimeSeconds>");
pw.print(track.getStatistics().getTotalTime() / 1000); pw.print(track.getStatistics().getTotalTime() / 1000);
pw.println("</TotalTimeSeconds>"); pw.println("</TotalTimeSeconds>");
@@ -113,7 +128,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
// Calories are a required element just put in 0. // Calories are a required element just put in 0.
pw.print("<Calories>0</Calories>"); pw.print("<Calories>0</Calories>");
pw.println("<Intensity>Active</Intensity>"); pw.println("<Intensity>Active</Intensity>");
pw.println("<TriggerMethod>Manual</TriggerMethod>)"); pw.println("<TriggerMethod>Manual</TriggerMethod>");
} }
@Override @Override
@@ -130,7 +145,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
} }
pw.println(" <Trackpoint>"); pw.println(" <Trackpoint>");
Date d = new Date(location.getTime()); 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.println(" <Position>");
pw.print(" <LatitudeDegrees>"); pw.print(" <LatitudeDegrees>");
@@ -194,10 +209,18 @@ public class TcxTrackWriter implements TrackFormatWriter {
} }
pw.println(" </Lap>"); pw.println(" </Lap>");
pw.print(" <Creator xsi:type=\"Device_t\">"); pw.print(" <Creator xsi:type=\"Device_t\">");
pw.print("<Name>"); pw.format("<Name>%s</Name>\n", Build.MODEL);
pw.print(Build.MODEL);
pw.print("</Name>"); // The following code is correct. ID is inconsistently capitalized in the
pw.println("</Creator>)"); // 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(" </Activity>");
pw.println(" </Activities>"); pw.println(" </Activities>");
} }
@@ -207,8 +230,24 @@ public class TcxTrackWriter implements TrackFormatWriter {
if (pw == null) { if (pw == null) {
return; return;
} }
pw.print(" <Author xsi:type=\"Application_t\">"); pw.println(" <Author xsi:type=\"Application_t\">");
pw.print("<Name>My Tracks by Google</Name>");
// 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(context));
pw.println("<Build>");
pw.println("<Version>");
pw.format("<VersionMajor>%d</VersionMajor>\n", MyTracksUtils.getMyTracksVersionCode(context));
pw.println("<VersionMinor>0</VersionMinor>");
pw.println("</Version>");
pw.format("<Type>%s</Type>\n", MyTracksUtils.isRelease(context) ? 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("</Author>");
pw.println("</TrainingCenterDatabase>"); pw.println("</TrainingCenterDatabase>");
} }
@@ -217,4 +256,15 @@ public class TcxTrackWriter implements TrackFormatWriter {
public void writeWaypoint(Waypoint waypoint) { public void writeWaypoint(Waypoint waypoint) {
// TODO Write out the waypoints somewhere. // 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;
}
}
} }
@@ -1,12 +1,12 @@
/* /*
* Copyright 2010 Google Inc. * Copyright 2010 Google Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * 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 * use this file except in compliance with the License. You may obtain a copy of
* the License at * the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -53,7 +53,7 @@ public class TrackWriterFactory {
TCX { TCX {
@Override @Override
public TrackFormatWriter newFormatWriter(Context context) { public TrackFormatWriter newFormatWriter(Context context) {
return new TcxTrackWriter(); return new TcxTrackWriter(context);
} }
}; };
@@ -1,12 +1,12 @@
/* /*
* Copyright 2008 Google Inc. * Copyright 2008 Google Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * 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 * use this file except in compliance with the License. You may obtain a copy of
* the License at * the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * 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.MyTracksConstants;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.GeoPoint; import com.google.android.maps.GeoPoint;
import android.app.Activity; import android.app.Activity;
@@ -38,7 +37,7 @@ import java.util.Stack;
/** /**
* Utility class for decimating tracks at a given level of precision. * Utility class for decimating tracks at a given level of precision.
* *
* @author Leif Hendrik Wilden * @author Leif Hendrik Wilden
*/ */
public class MyTracksUtils { public class MyTracksUtils {
@@ -47,7 +46,7 @@ public class MyTracksUtils {
/** /**
* Computes the distance on the two sphere between the point c0 and the line * Computes the distance on the two sphere between the point c0 and the line
* segment c1 to c2. * segment c1 to c2.
* *
* @param c0 the first coordinate * @param c0 the first coordinate
* @param c1 the beginning of the line segment * @param c1 the beginning of the line segment
* @param c2 the end of the lone 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 * Decimates the given locations for a given zoom level. This uses a
* Douglas-Peucker decimation algorithm. * Douglas-Peucker decimation algorithm.
* *
* @param tolerance in meters * @param tolerance in meters
* @param locations input * @param locations input
* @param decimated output * @param decimated output
@@ -152,7 +151,7 @@ public class MyTracksUtils {
/** /**
* Decimates the given track for the given precision. * Decimates the given track for the given precision.
* *
* @param track a track * @param track a track
* @param precision desired precision in meters * @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 * Limits number of points by dropping any points beyond the given number of
* points. Note: That'll actually discard points. * points. Note: That'll actually discard points.
* *
* @param track a track * @param track a track
* @param numberOfPoints maximum number of points * @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 * Splits a track in multiple tracks where each piece has less or equal than
* maxPoints. * maxPoints.
* *
* @param track the track to split * @param track the track to split
* @param maxPoints maximum number of points for each piece * @param maxPoints maximum number of points for each piece
* @return a list of one or more track pieces * @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. * Test if a given GeoPoint is valid, i.e. within physical bounds.
* *
* @param geoPoint the point to be tested * @param geoPoint the point to be tested
* @return true, if it is a physical location on earth. * @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 = * 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 * 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. * as these are most likely "bad" measurements which often cause trouble.
* *
* @param location the location to test * @param location the location to test
* @return true if the location is a valid location. * @return true if the location is a valid location.
*/ */
@@ -239,7 +238,7 @@ public class MyTracksUtils {
/** /**
* Gets a location from a GeoPoint. * Gets a location from a GeoPoint.
* *
* @param p a GeoPoint * @param p a GeoPoint
* @return the corresponding location * @return the corresponding location
*/ */
@@ -259,17 +258,17 @@ public class MyTracksUtils {
* Returns whether or not this is a release build. * Returns whether or not this is a release build.
*/ */
public static boolean isRelease(Context context) { public static boolean isRelease(Context context) {
try { PackageInfo packageInfo = getPackageInfo(context, PackageManager.GET_SIGNATURES);
Signature [] sigs = context.getPackageManager().getPackageInfo( if (packageInfo == null) {
context.getPackageName(), PackageManager.GET_SIGNATURES).signatures; return false;
for (Signature sig : sigs) {
if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) {
return true;
}
}
} catch (NameNotFoundException e) {
Log.e(MyTracksConstants.TAG, "Unable to get signatures", e);
} }
for (Signature sig : packageInfo.signatures) {
if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) {
return true;
}
}
return false; return false;
} }
@@ -279,17 +278,34 @@ public class MyTracksUtils {
* @return the version, or an empty string in case of failure. * @return the version, or an empty string in case of failure.
*/ */
public static String getMyTracksVersion(Context context) { 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 { try {
PackageInfo pi = context.getPackageManager().getPackageInfo( return context.getPackageManager().getPackageInfo(
"com.google.android.maps.mytracks", "com.google.android.maps.mytracks", flags);
PackageManager.GET_META_DATA);
return pi.versionName;
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
Log.w(MyTracksConstants.TAG, "Failed to get version info.", 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 * 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. * and gives up trying in case the wake lock cannot be acquired.
@@ -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.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor; 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.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import java.util.Date;
import java.util.List;
/** /**
* Tests for the GPX track exporter. * Tests for the GPX track exporter.
* *
* @author Sandor Dornbush * @author Sandor Dornbush
*/ */
public class TcxTrackWriterTest extends TrackFormatWriterTest { 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 { public void testXmlOutput() throws Exception {
TrackFormatWriter writer = new TcxTrackWriter(); TrackFormatWriter writer = new TcxTrackWriter(null);
String result = writeTrack(writer); String result = writeTrack(writer);
Document doc = parseXmlDocument(result); Document doc = parseXmlDocument(result);
@@ -58,8 +69,8 @@ public class TcxTrackWriterTest extends TrackFormatWriterTest {
getChildTextValue(posTag, "LatitudeDegrees")); getChildTextValue(posTag, "LatitudeDegrees"));
assertEquals(Double.toString(loc.getLongitude()), assertEquals(Double.toString(loc.getLongitude()),
getChildTextValue(posTag, "LongitudeDegrees")); getChildTextValue(posTag, "LongitudeDegrees"));
assertEquals(
TcxTrackWriter.TIMESTAMP_FORMAT.format(new Date(loc.getTime())), assertEquals(timestampFormatter.format(loc.getTime()),
getChildTextValue(tag, "Time")); getChildTextValue(tag, "Time"));
assertEquals(Double.toString(loc.getAltitude()), assertEquals(Double.toString(loc.getAltitude()),
getChildTextValue(tag, "AltitudeMeters")); getChildTextValue(tag, "AltitudeMeters"));