diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index 3ec314cbf..91f13973e 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -507,6 +507,34 @@ limitations under the License.
%2$d elevation in feet">
Elevation gain: %1$d m (%2$d ft)
+
+ Accuracy (m)
+
+
+ Altitude (m)
+
+
+ Bearing (deg)
+
+
+ Latitude (deg)
+
+
+ Longitude (deg)
+
+
+ Speed (m/s)
+
+
+ Time
+
Power (W)
+
+ Point
+
+
+ Segment
+
Total time: %1$s
-
+
Always
-
- %1$.1f mi
+ %1$.2f: a float number. E.g., '2.22'">
+ %1$.2f ft
+
+
+ %1$.2f km
+
+
+ %1$.2f km/h
+
+
+ %1$.2f m
+
+
+ %1$.2f mi
+
+
+ %1$.2f mi/h
+
+
+ %1$.2f min/km
+
+
+ %1$.2f min/mi
")
- .append(StringUtils.stringAsCData(value))
+ .append(StringUtils.formatCData(value))
.append("");
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java
index 6b5ecb57a..bdf07dbcf 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java
@@ -15,31 +15,37 @@
*/
package com.google.android.apps.mytracks.io.file;
+import com.google.android.apps.mytracks.content.MyTracksLocation;
+import com.google.android.apps.mytracks.content.Sensor;
+import com.google.android.apps.mytracks.content.Sensor.SensorData;
+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.io.file.TrackWriterFactory.TrackFileFormat;
-import com.google.android.apps.mytracks.util.FileUtils;
+import com.google.android.apps.mytracks.util.StringUtils;
+import com.google.android.maps.mytracks.R;
+import android.content.Context;
import android.location.Location;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.NumberFormat;
-import java.util.Date;
/**
- * Exports a track as a CSV file, according to RFC 4180.
- *
- * The first field is a type:
- * TRACK - track description
- * P - point
- * WAYPOINT - waypoint
- *
- * For each type, the fields are:
- *
- * TRACK,,,,,,,,name,description,
- * P,time,lat,lon,alt,bearing,accurancy,speed,,,segmentIdx
- * WAYPOINT,time,lat,lon,alt,bearing,accuracy,speed,name,description,
+ * Write track as CSV to a file. See RFC 4180 for info on CSV. Output three
+ * tables.
+ * The first table contains the track info. Its columns are:
+ * "Track name","Activity type","Track description"
+ *
+ * The second table contains the markers. Its columns are:
+ * "Marker name","Marker type","Marker description","Latitude (deg)","Longitude
+ * (deg)","Altitude (m)","Bearing (deg)","Accuracy (m)","Speed (m/s)","Time"
+ *
+ * The thrid table contains the points. Its columns are:
+ * "Segment","Point","Latitude (deg)","Longitude (deg)","Altitude (m)","Bearing
+ * (deg)","Accuracy (m)","Speed (m/s)","Time","Power (W)","Cadence (rpm)","Heart
+ * rate (bpm)","Battery level (%)"
*
* @author Rodrigo Damazio
*/
@@ -51,121 +57,41 @@ public class CsvTrackWriter implements TrackFormatWriter {
SHORT_FORMAT.setMaximumFractionDigits(4);
}
- private int segmentIdx = 0;
- private int numFields = -1;
- private PrintWriter pw;
+ private final Context context;
+ private PrintWriter printWriter;
private Track track;
+ private int segmentIndex;
+ private int pointIndex;
+
+ public CsvTrackWriter(Context context) {
+ this.context = context;
+ }
@Override
public String getExtension() {
return TrackFileFormat.CSV.getExtension();
}
- @SuppressWarnings("hiding")
@Override
- public void prepare(Track track, OutputStream out) {
- this.track = track;
- this.pw = new PrintWriter(out);
+ public void prepare(Track aTrack, OutputStream out) {
+ track = aTrack;
+ printWriter = new PrintWriter(out);
+ segmentIndex = 0;
+ pointIndex = 0;
+ }
+
+ @Override
+ public void close() {
+ printWriter.close();
}
@Override
public void writeHeader() {
- writeCommaSeparatedLine("TYPE", "TIME", "LAT", "LON", "ALT", "BEARING",
- "ACCURACY", "SPEED", "NAME", "DESCRIPTION", "SEGMENT");
- }
-
- @Override
- public void writeBeginTrack(Location firstPoint) {
- writeCommaSeparatedLine("TRACK",
- null, null, null, null, null, null, null,
- track.getName(), track.getDescription(),
- null);
- }
-
- @Override
- public void writeOpenSegment() {
- // Do nothing
- }
-
- @Override
- public void writeLocation(Location location) {
- String timeStr = FileUtils.FILE_TIMESTAMP_FORMAT.format(new Date(location.getTime()));
- writeCommaSeparatedLine("P",
- timeStr,
- Double.toString(location.getLatitude()),
- Double.toString(location.getLongitude()),
- Double.toString(location.getAltitude()),
- Double.toString(location.getBearing()),
- SHORT_FORMAT.format(location.getAccuracy()),
- SHORT_FORMAT.format(location.getSpeed()),
- null, null,
- Integer.toString(segmentIdx));
- }
-
- @Override
- public void writeBeginWaypoints() {
- // Do nothing
- }
-
- @Override
- public void writeEndWaypoints() {
- // Do nothing
- }
-
- @Override
- public void writeWaypoint(Waypoint waypoint) {
- Location location = waypoint.getLocation();
- String timeStr = FileUtils.FILE_TIMESTAMP_FORMAT.format(new Date(location.getTime()));
- writeCommaSeparatedLine("WAYPOINT",
- timeStr,
- Double.toString(location.getLatitude()),
- Double.toString(location.getLongitude()),
- Double.toString(location.getAltitude()),
- Double.toString(location.getBearing()),
- SHORT_FORMAT.format(location.getAccuracy()),
- SHORT_FORMAT.format(location.getSpeed()),
- waypoint.getName(),
- waypoint.getDescription(),
- null);
- }
-
- /**
- * Writes a single line of a comma-separated-value file.
- *
- * @param strs the values to be written as comma-separated
- */
- private void writeCommaSeparatedLine(String... strs) {
- if (numFields == -1) {
- numFields = strs.length;
- } else if (strs.length != numFields) {
- throw new IllegalArgumentException(
- "CSV lines with different number of fields");
- }
-
- boolean isFirst = true;
- for (String str : strs) {
- if (!isFirst) {
- pw.print(',');
- }
- isFirst = false;
-
- if (str != null) {
- pw.print('"');
- pw.print(str.replaceAll("\"", "\"\""));
- pw.print('"');
- }
- }
- pw.println();
- }
-
- @Override
- public void writeCloseSegment() {
- segmentIdx++;
- }
-
- @Override
- public void writeEndTrack(Location lastPoint) {
- // Do nothing
+ writeCommaSeparatedLine(context.getString(R.string.track_detail_track_name),
+ context.getString(R.string.track_detail_activity_type_hint),
+ context.getString(R.string.track_detail_track_description));
+ writeCommaSeparatedLine(track.getName(), track.getCategory(), track.getDescription());
+ writeCommaSeparatedLine();
}
@Override
@@ -174,7 +100,144 @@ public class CsvTrackWriter implements TrackFormatWriter {
}
@Override
- public void close() {
- pw.close();
+ public void writeBeginWaypoints() {
+ writeCommaSeparatedLine(context.getString(R.string.marker_detail_marker_name),
+ context.getString(R.string.marker_detail_marker_type_hint),
+ context.getString(R.string.marker_detail_marker_description),
+ context.getString(R.string.description_location_latitude),
+ context.getString(R.string.description_location_longitude),
+ context.getString(R.string.description_location_altitude),
+ context.getString(R.string.description_location_bearing),
+ context.getString(R.string.description_location_accuracy),
+ context.getString(R.string.description_location_speed),
+ context.getString(R.string.description_location_time));
+ }
+
+ @Override
+ public void writeEndWaypoints() {
+ writeCommaSeparatedLine();
+ }
+
+ @Override
+ public void writeWaypoint(Waypoint waypoint) {
+ Location location = waypoint.getLocation();
+ writeCommaSeparatedLine(waypoint.getName(),
+ waypoint.getCategory(),
+ waypoint.getDescription(),
+ Double.toString(location.getLatitude()),
+ Double.toString(location.getLongitude()),
+ Double.toString(location.getAltitude()),
+ Double.toString(location.getBearing()),
+ SHORT_FORMAT.format(location.getAccuracy()),
+ SHORT_FORMAT.format(location.getSpeed()),
+ StringUtils.formatDateTimeIso8601(location.getTime()));
+ }
+
+ @Override
+ public void writeBeginTrack(Location firstPoint) {
+ writeCommaSeparatedLine(context.getString(R.string.description_track_segment),
+ context.getString(R.string.description_track_point),
+ context.getString(R.string.description_location_latitude),
+ context.getString(R.string.description_location_longitude),
+ context.getString(R.string.description_location_altitude),
+ context.getString(R.string.description_location_bearing),
+ context.getString(R.string.description_location_accuracy),
+ context.getString(R.string.description_location_speed),
+ context.getString(R.string.description_location_time),
+ context.getString(R.string.description_sensor_power),
+ context.getString(R.string.description_sensor_cadence),
+ context.getString(R.string.description_sensor_heart_rate),
+ context.getString(R.string.description_sensor_battery_level));
+ }
+
+ @Override
+ public void writeEndTrack(Location lastPoint) {
+ // Do nothing
+ }
+
+ @Override
+ public void writeOpenSegment() {
+ segmentIndex++;
+ pointIndex = 0;
+ }
+
+ @Override
+ public void writeCloseSegment() {
+ // Do nothing
+ }
+
+ @Override
+ public void writeLocation(Location location) {
+ String power = null;
+ String cadence = null;
+ String heartRate = null;
+ String batteryLevel = null;
+ if (location instanceof MyTracksLocation) {
+ SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
+
+ if (sensorDataSet != null) {
+ if (sensorDataSet.hasPower()) {
+ SensorData sensorData = sensorDataSet.getPower();
+ if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
+ power = Double.toString(sensorData.getValue());
+ }
+ }
+ if (sensorDataSet.hasCadence()) {
+ SensorData sensorData = sensorDataSet.getCadence();
+ if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
+ cadence = Double.toString(sensorData.getValue());
+ }
+ }
+ if (sensorDataSet.hasHeartRate()) {
+ SensorData sensorData = sensorDataSet.getHeartRate();
+ if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
+ heartRate = Double.toString(sensorData.getValue());
+ }
+ }
+ if (sensorDataSet.hasBatteryLevel()) {
+ SensorData sensorData = sensorDataSet.getBatteryLevel();
+ if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
+ batteryLevel = Double.toString(sensorData.getValue());
+ }
+ }
+ }
+ }
+ pointIndex++;
+ writeCommaSeparatedLine(Integer.toString(segmentIndex),
+ Integer.toString(pointIndex),
+ Double.toString(location.getLatitude()),
+ Double.toString(location.getLongitude()),
+ Double.toString(location.getAltitude()),
+ Double.toString(location.getBearing()),
+ SHORT_FORMAT.format(location.getAccuracy()),
+ SHORT_FORMAT.format(location.getSpeed()),
+ StringUtils.formatDateTimeIso8601(location.getTime()),
+ power,
+ cadence,
+ heartRate,
+ batteryLevel);
+ }
+
+ /**
+ * Writes a single line of a CSV file.
+ *
+ * @param values the values to be written as CSV
+ */
+ private void writeCommaSeparatedLine(String... values) {
+ StringBuilder builder = new StringBuilder();
+ boolean isFirst = true;
+ for (String value : values) {
+ if (!isFirst) {
+ builder.append(',');
+ }
+ isFirst = false;
+
+ if (value != null) {
+ builder.append('"');
+ builder.append(value.replaceAll("\"", "\"\""));
+ builder.append('"');
+ }
+ }
+ printWriter.println(builder.toString());
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java
index 66afa09e1..0092dcb36 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java
@@ -450,7 +450,7 @@ public class GpxImporter extends DefaultHandler {
// Parse the time
long time;
try {
- time = StringUtils.parseXmlDateTime(content.trim());
+ time = StringUtils.getTime(content.trim());
} catch (IllegalArgumentException e) {
String msg = createErrorMessage("Unable to parse time: " + content);
throw new SAXException(msg, e);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java
index ebdf1ce9c..c04cd20d3 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java
@@ -18,7 +18,6 @@ package com.google.android.apps.mytracks.io.file;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
-import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -94,8 +93,8 @@ public class GpxTrackWriter implements TrackFormatWriter {
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1"
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd\">");
printWriter.println("");
- printWriter.println("" + StringUtils.stringAsCData(track.getName()) + "");
- printWriter.println("" + StringUtils.stringAsCData(track.getDescription()) + "");
+ printWriter.println("" + StringUtils.formatCData(track.getName()) + "");
+ printWriter.println("" + StringUtils.formatCData(track.getDescription()) + "");
printWriter.println("");
}
}
@@ -111,8 +110,8 @@ public class GpxTrackWriter implements TrackFormatWriter {
public void writeBeginTrack(Location firstLocation) {
if (printWriter != null) {
printWriter.println("");
- printWriter.println("" + StringUtils.stringAsCData(track.getName()) + "");
- printWriter.println("" + StringUtils.stringAsCData(track.getDescription()) + "");
+ printWriter.println("" + StringUtils.formatCData(track.getName()) + "");
+ printWriter.println("" + StringUtils.formatCData(track.getDescription()) + "");
printWriter.println("c0c0c0");
}
}
@@ -139,8 +138,7 @@ public class GpxTrackWriter implements TrackFormatWriter {
if (printWriter != null) {
printWriter.println("");
printWriter.println("" + ELEVATION_FORMAT.format(location.getAltitude()) + "");
- printWriter.println(
- "");
+ printWriter.println("");
printWriter.println("");
}
}
@@ -162,11 +160,10 @@ public class GpxTrackWriter implements TrackFormatWriter {
if (location != null) {
printWriter.println("");
printWriter.println("" + ELEVATION_FORMAT.format(location.getAltitude()) + "");
+ printWriter.println("");
+ printWriter.println("" + StringUtils.formatCData(waypoint.getName()) + "");
printWriter.println(
- "");
- printWriter.println("" + StringUtils.stringAsCData(waypoint.getName()) + "");
- printWriter.println(
- "" + StringUtils.stringAsCData(waypoint.getDescription()) + "");
+ "" + StringUtils.formatCData(waypoint.getDescription()) + "");
printWriter.println("");
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java
index 019c23dc8..1b8cfcb8e 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java
@@ -24,7 +24,6 @@ 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.io.file.TrackWriterFactory.TrackFileFormat;
-import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
@@ -43,6 +42,11 @@ import java.util.ArrayList;
*/
public class KmlTrackWriter implements TrackFormatWriter {
+ /**
+ * ID of the KML feature to play a tour.
+ */
+ public static final String TOUR_FEATURE_ID = "tour";
+
private static final String WAYPOINT_STYLE = "waypoint";
private static final String STATISTICS_STYLE = "statistics";
private static final String START_STYLE = "start";
@@ -54,6 +58,17 @@ public class KmlTrackWriter implements TrackFormatWriter {
private static final String POWER = "power";
private static final String BATTER_LEVEL = "battery_level";
+ private static final String WAYPOINT_ICON =
+ "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
+ private static final String STATISTICS_ICON =
+ "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";
+ private static final String START_ICON =
+ "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
+ private static final String END_ICON =
+ "http://maps.google.com/mapfiles/kml/paddle/red-circle.png";
+ private static final String TRACK_ICON =
+ "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
+
private final Context context;
private final DescriptionGenerator descriptionGenerator;
private Track track;
@@ -107,20 +122,16 @@ public class KmlTrackWriter implements TrackFormatWriter {
printWriter.println("1");
printWriter.println("1");
printWriter.println(
- "" + StringUtils.stringAsCData(track.getDescription()) + "");
- printWriter.println("" + StringUtils.stringAsCData(track.getName()) + "");
- printWriter.println("" + StringUtils.stringAsCData(
+ "" + StringUtils.formatCData(track.getDescription()) + "");
+ printWriter.println("" + StringUtils.formatCData(track.getName()) + "");
+ printWriter.println("" + StringUtils.formatCData(
context.getString(R.string.send_google_by_my_tracks, "", ""))
+ "");
writeTrackStyle();
- writePlacemarkerStyle(
- START_STYLE, "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png", 32, 1);
- writePlacemarkerStyle(
- END_STYLE, "http://maps.google.com/mapfiles/kml/paddle/red-circle.png", 32, 1);
- writePlacemarkerStyle(
- STATISTICS_STYLE, "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png", 20, 2);
- writePlacemarkerStyle(
- WAYPOINT_STYLE, "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png", 20, 2);
+ writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1);
+ writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1);
+ writePlacemarkerStyle(STATISTICS_STYLE, STATISTICS_ICON, 20, 2);
+ writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2);
printWriter.println("");
writeSensorStyle(POWER, context.getString(R.string.description_sensor_power));
writeSensorStyle(CADENCE, context.getString(R.string.description_sensor_cadence));
@@ -142,7 +153,7 @@ public class KmlTrackWriter implements TrackFormatWriter {
public void writeBeginWaypoints() {
if (printWriter != null) {
printWriter.println(
- "" + StringUtils.stringAsCData(context.getString(R.string.menu_markers))
+ "" + StringUtils.formatCData(context.getString(R.string.menu_markers))
+ "");
}
}
@@ -169,10 +180,10 @@ public class KmlTrackWriter implements TrackFormatWriter {
if (printWriter != null) {
String name = context.getString(R.string.marker_label_start, track.getName());
writePlacemark(name, track.getDescription(), START_STYLE, firstLocation);
- printWriter.println("");
+ printWriter.println("");
printWriter.println(
- "" + StringUtils.stringAsCData(track.getDescription()) + "");
- printWriter.println("" + StringUtils.stringAsCData(track.getName()) + "");
+ "" + StringUtils.formatCData(track.getDescription()) + "");
+ printWriter.println("" + StringUtils.formatCData(track.getName()) + "");
printWriter.println("#" + TRACK_STYLE + "");
printWriter.println("");
printWriter.println("absolute");
@@ -232,8 +243,7 @@ public class KmlTrackWriter implements TrackFormatWriter {
@Override
public void writeLocation(Location location) {
if (printWriter != null) {
- printWriter.println(
- "" + FileUtils.FILE_TIMESTAMP_FORMAT.format(location.getTime()) + "");
+ printWriter.println("" + StringUtils.formatDateTimeIso8601(location.getTime()) + "");
printWriter.println(
"" + location.getLongitude() + " " + location.getLatitude() + " "
+ location.getAltitude() + "");
@@ -309,8 +319,8 @@ public class KmlTrackWriter implements TrackFormatWriter {
if (location != null) {
printWriter.println("");
printWriter.println(
- "" + StringUtils.stringAsCData(description) + "");
- printWriter.println("" + StringUtils.stringAsCData(name) + "");
+ "" + StringUtils.formatCData(description) + "");
+ printWriter.println("" + StringUtils.formatCData(name) + "");
printWriter.println("#" + styleName + "");
printWriter.println("");
printWriter.println(
@@ -325,9 +335,13 @@ public class KmlTrackWriter implements TrackFormatWriter {
* Writes the track style.
*/
private void writeTrackStyle() {
- printWriter.println("");
+ printWriter.println("");
}
/**
@@ -356,7 +370,7 @@ public class KmlTrackWriter implements TrackFormatWriter {
private void writeSensorStyle(String name, String displayName) {
printWriter.println("");
printWriter.println(
- "" + StringUtils.stringAsCData(displayName) + "");
+ "" + StringUtils.formatCData(displayName) + "");
printWriter.println("");
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java
index e3a3218f3..7af0eef46 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java
@@ -159,9 +159,11 @@ public class SaveActivity extends Activity {
Uri uri = Uri.fromFile(new File(writer.getAbsolutePath()));
Intent intent = new Intent()
+ .setClassName(PlayTrackUtils.GOOGLE_EARTH_PACKAGE, PlayTrackUtils.GOOGLE_EARTH_CLASS)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .setDataAndType(uri, PlayTrackUtils.KML_MIME_TYPE);
+ .setDataAndType(uri, PlayTrackUtils.KML_MIME_TYPE)
+ .putExtra(PlayTrackUtils.TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID);
startActivity(intent);
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java
index 7085a8f79..f1b0ca8d6 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java
@@ -22,86 +22,89 @@ import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.lib.R;
-import com.google.android.apps.mytracks.util.FileUtils;
+import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import android.content.Context;
import android.location.Location;
-import android.os.Build;
import java.io.OutputStream;
import java.io.PrintWriter;
-import java.nio.charset.Charset;
-import java.util.Date;
import java.util.Locale;
/**
- * Write out a a track in the Garmin training center database, tcx format.
- * 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.
+ * Write track as TCX to a file. See http://developer.garmin.com/schemas/tcx/v2/
+ * for info on TCX.
+ * The TCX file output is verified by uploading the file to
+ * http://connect.garmin.com/.
*
* @author Sandor Dornbush
* @author Dominik RĀttsches
*/
public class TcxTrackWriter implements TrackFormatWriter {
- // "Biking" related string IDs
+ /**
+ * TCX sport type. See the TCX spec.
+ *
+ * @author Jimmy Shih
+ */
+ private enum SportType {
+ RUNNING("Running"),
+ BIKING("Biking"),
+ OTHER("Other");
+
+ private final String name;
+
+ private SportType(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets the name of the sport type
+ */
+ public String getName() {
+ return name;
+ }
+ }
+
+ // My Tracks categories that are considered as TCX biking sport type.
private static final int TCX_SPORT_BIKING_IDS[] = {
R.string.activity_type_cycling,
R.string.activity_type_dirt_bike,
R.string.activity_type_mountain_biking,
R.string.activity_type_road_biking,
- R.string.activity_type_track_cycling,
- };
+ R.string.activity_type_track_cycling };
- // "Running" related string IDs
+ // My Tracks categories that are considered as TCX running sport type.
private static final int TCX_SPORT_RUNNING_IDS[] = {
R.string.activity_type_running,
R.string.activity_type_speed_walking,
R.string.activity_type_street_running,
R.string.activity_type_track_running,
R.string.activity_type_trail_running,
- R.string.activity_type_walking,
- };
-
- // 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";
+ R.string.activity_type_walking };
private final Context context;
-
- private PrintWriter pw = null;
private Track track;
-
- // Determines whether to encode cadence value as running or cycling cadence.
- private boolean sportIsCycling;
+ private PrintWriter printWriter;
+ private SportType sportType;
public TcxTrackWriter(Context context) {
this.context = context;
}
- @SuppressWarnings("hiding")
@Override
- public void prepare(Track track, OutputStream out) {
- this.track = track;
- this.pw = new PrintWriter(out);
- this.sportIsCycling = categoryToTcxSport(track.getCategory()).equals(TCX_SPORT_BIKING);
+ public void prepare(Track aTrack, OutputStream out) {
+ this.track = aTrack;
+ this.printWriter = new PrintWriter(out);
+ this.sportType = getSportType(track.getCategory());
}
@Override
public void close() {
- if (pw != null) {
- pw.close();
- pw = null;
+ if (printWriter != null) {
+ printWriter.close();
+ printWriter = null;
}
}
@@ -112,246 +115,214 @@ public class TcxTrackWriter implements TrackFormatWriter {
@Override
public void writeHeader() {
- if (pw == null) {
- return;
+ if (printWriter != null) {
+ printWriter.println("");
+ printWriter.println("");
+ }
+ }
+
+ @Override
+ public void writeFooter() {
+ if (printWriter != null) {
+ printWriter.println("");
+ printWriter.println(""
+ + StringUtils.formatCData(context.getString(R.string.send_google_by_my_tracks, "", ""))
+ + "");
+ // , , and are required by type=Application_t.
+ printWriter.println("");
+ writeVersion();
+ printWriter.println("");
+ printWriter.println("" + Locale.getDefault().getLanguage() + "");
+ printWriter.println("000-00000-00");
+ printWriter.println("");
+ printWriter.println("");
}
- pw.format("\n",
- Charset.defaultCharset().name());
- pw.print("");
- pw.println();
}
@Override
public void writeBeginTrack(Location firstPoint) {
- if (pw == null) {
- return;
- }
+ if (printWriter != null) {
+ String startTime = StringUtils.formatDateTimeIso8601(track.getStatistics().getStartTime());
+ long totalTimeInSeconds = track.getStatistics().getTotalTime() / 1000;
- String startTime = FileUtils.FILE_TIMESTAMP_FORMAT.format(track.getStatistics().getStartTime());
-
- 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("");
- pw.print(" ");
- pw.print(track.getStatistics().getTotalDistance());
- pw.println("");
- // TODO max speed etc.
- // Calories are a required element just put in 0.
- pw.print("0");
- pw.println("Active");
- pw.println("Manual");
- }
-
- @Override
- public void writeOpenSegment() {
- if (pw != null) {
- pw.println(" ");
+ printWriter.println("");
+ printWriter.println("");
+ printWriter.println("" + startTime + "");
+ printWriter.println("");
+ printWriter.println("" + totalTimeInSeconds + "");
+ printWriter.println("" + track.getStatistics().getTotalDistance()
+ + "");
+ // is required, just put in 0.
+ printWriter.println("0");
+ printWriter.println("Active");
+ printWriter.println("Manual");
}
}
@Override
public void writeEndTrack(Location lastPoint) {
- if (pw == null) {
- return;
+ if (printWriter != null) {
+ printWriter.println("");
+ printWriter.println("");
+ printWriter.println(""
+ + StringUtils.formatCData(context.getString(R.string.send_google_by_my_tracks, "", ""))
+ + "");
+ // , , and are required for type=Device_t.
+ printWriter.println("0");
+ printWriter.println("0");
+ writeVersion();
+ printWriter.println("");
+ printWriter.println("");
+ printWriter.println("");
}
- pw.println(" ");
- pw.print(" ");
- pw.format("My Tracks running on %s\n", Build.MODEL);
-
- // The following code is correct. ID is inconsistently capitalized in the
- // TCX schema.
- pw.println("0");
- pw.println("0");
-
- writeVersion();
-
- pw.println("");
- pw.println(" ");
- pw.println(" ");
}
@Override
- public void writeFooter() {
- if (pw == null) {
- return;
+ public void writeOpenSegment() {
+ if (printWriter != null) {
+ printWriter.println("");
+ }
+ }
- pw.println("");
+ @Override
+ public void writeLocation(Location location) {
+ if (printWriter != null) {
+ printWriter.println("");
+ printWriter.println("");
+ printWriter.println("");
+ printWriter.println("" + location.getLatitude() + "");
+ printWriter.println("" + location.getLongitude() + "");
+ printWriter.println("");
+ printWriter.println("" + location.getAltitude() + "");
- writeVersion();
+ if (location instanceof MyTracksLocation) {
+ SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
+ if (sensorDataSet != null) {
+ boolean heartRateAvailable = sensorDataSet.hasHeartRate()
+ && sensorDataSet.getHeartRate().hasValue()
+ && sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING;
+ boolean cadenceAvailable = sensorDataSet.hasCadence()
+ && sensorDataSet.getCadence().hasValue()
+ && sensorDataSet.getCadence().getState() == Sensor.SensorState.SENDING;
+ boolean powerAvailable = sensorDataSet.hasPower()
+ && sensorDataSet.getPower().hasValue()
+ && sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING;
+
+ if (heartRateAvailable) {
+ printWriter.println("");
+ printWriter.println("" + sensorDataSet.getHeartRate().getValue() + "");
+ printWriter.println("");
+ }
- pw.format("%s\n", SystemUtils.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("");
+ // needs to be put before .
+ // According to the TCX spec, is only for the biking sport
+ // type. For others, use in .
+ if (cadenceAvailable && sportType == SportType.BIKING) {
+ // The spec requires the max value be 254.
+ printWriter.println(
+ "" + Math.min(254, sensorDataSet.getCadence().getValue()) + "");
+ }
+
+ if ((cadenceAvailable && sportType != SportType.BIKING) || powerAvailable) {
+ printWriter.println("");
+ printWriter.println(
+ "");
+
+ // needs to be put before .
+ if (cadenceAvailable && sportType != SportType.BIKING) {
+ // The spec requires the max value to be 254.
+ printWriter.println(""
+ + Math.min(254, sensorDataSet.getCadence().getValue()) + "");
+ }
+
+ if (powerAvailable) {
+ printWriter.println("" + sensorDataSet.getPower().getValue() + "");
+ }
+ printWriter.println("");
+ printWriter.println("");
+ }
+ }
+ }
+ printWriter.println("");
+ }
}
@Override
public void writeBeginWaypoints() {
- // Do nothing
+ // Do nothing.
}
@Override
public void writeEndWaypoints() {
- // Do nothing
+ // Do nothing.
}
@Override
public void writeWaypoint(Waypoint waypoint) {
- // TODO Write out the waypoints somewhere.
+ // Do nothing.
}
+ /**
+ * Writes the TCX Version.
+ */
private void writeVersion() {
- if (pw == null) {
- return;
- }
+ // Split the My Tracks version code into VersionMajor, VersionMinor, and,
+ // BuildMajor to fit the integer type requirement for these fields in the
+ // TCX spec.
+ String[] versionComponents = SystemUtils.getMyTracksVersion(context).split("\\.");
+ int versionMajor = versionComponents.length > 0 ? Integer.valueOf(versionComponents[0]) : 0;
+ int versionMinor = versionComponents.length > 1 ? Integer.valueOf(versionComponents[1]) : 0;
+ int buildMajor = versionComponents.length > 2 ? Integer.valueOf(versionComponents[2]) : 0;
- // Splitting the myTracks version code into VersionMajor, VersionMinor and BuildMajor
- // to fit the integer type requirement for these fields in the TCX spec.
- // Putting a string like "x.x.x" into VersionMajor breaks XML validation.
- // We also set the BuildMinor version to 1 if this is a development build to
- // signify that this build is newer than the one associated with the
- // version code given in BuildMajor.
-
- String[] myTracksVersionComponents = SystemUtils.getMyTracksVersion(context).split("\\.");
-
- pw.println("");
- pw.format("%d\n", Integer.valueOf(myTracksVersionComponents[0]));
- pw.format("%d\n", Integer.valueOf(myTracksVersionComponents[1]));
- // TCX schema says these are optional but http://connect.garmin.com only accepts
- // the TCX file when they are present.
- pw.format("%d\n", Integer.valueOf(myTracksVersionComponents[2]));
- pw.format("%d\n", SystemUtils.isRelease(context) ? 0 : 1);
- pw.println("");
+ printWriter.println("");
+ printWriter.println("" + versionMajor + "");
+ printWriter.println("" + versionMinor + "");
+ // According to TCX spec, these are optional. But http://connect.garmin.com
+ // requires them.
+ printWriter.println("" + buildMajor + "");
+ printWriter.println("0");
+ printWriter.println("");
}
- private String categoryToTcxSport(String category) {
+ /**
+ * Gets the sport type from the category.
+ *
+ * @param category the category
+ */
+ private SportType getSportType(String category) {
category = category.trim();
- // category is possibly localized here, so compare it to localized string resources
+ // For tracks with localized category.
for (int i : TCX_SPORT_RUNNING_IDS) {
- if (category.equalsIgnoreCase(context.getResources().getString(i))) {
- return TCX_SPORT_RUNNING;
+ if (category.equalsIgnoreCase(context.getString(i))) {
+ return SportType.RUNNING;
}
}
for (int i : TCX_SPORT_BIKING_IDS) {
- if (category.equalsIgnoreCase(context.getResources().getString(i))) {
- return TCX_SPORT_BIKING;
+ if (category.equalsIgnoreCase(context.getString(i))) {
+ return SportType.BIKING;
}
}
- // for tracks without localized activity type
- if (category.equalsIgnoreCase(TCX_SPORT_RUNNING)) {
- return TCX_SPORT_RUNNING;
- } else if (category.equalsIgnoreCase(TCX_SPORT_BIKING)) {
- return TCX_SPORT_BIKING;
+ // For tracks without localized category.
+ if (category.equalsIgnoreCase(SportType.RUNNING.getName())) {
+ return SportType.RUNNING;
+ } else if (category.equalsIgnoreCase(SportType.BIKING.getName())) {
+ return SportType.BIKING;
} else {
- return TCX_SPORT_OTHER;
+ return SportType.OTHER;
}
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java
index 5af558ae0..7df87364f 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java
@@ -50,7 +50,7 @@ public class TrackWriterFactory {
CSV {
@Override
public TrackFormatWriter newFormatWriter(Context context) {
- return new CsvTrackWriter();
+ return new CsvTrackWriter(context);
}
},
TCX {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesActivity.java
index 052561d90..92bac1af4 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesActivity.java
@@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.io.sendtogoogle.AbstractSendAsyncTask;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity;
import com.google.android.maps.mytracks.R;
+import com.google.common.annotations.VisibleForTesting;
import android.content.Intent;
@@ -45,18 +46,23 @@ public class SendFusionTablesActivity extends AbstractSendActivity {
protected void startNextActivity(boolean success, boolean isCancel) {
sendRequest.setFusionTablesSuccess(success);
- Class> next;
- if (isCancel) {
- next = UploadResultActivity.class;
- } else {
- if (sendRequest.isSendDocs()) {
- next = SendDocsActivity.class;
- } else {
- next = UploadResultActivity.class;
- }
- }
+ Class> next = getNextClass(sendRequest, isCancel);
+
Intent intent = new Intent(this, next).putExtra(SendRequest.SEND_REQUEST_KEY, sendRequest);
startActivity(intent);
finish();
}
+
+ @VisibleForTesting
+ Class> getNextClass(SendRequest request, boolean isCancel) {
+ if (isCancel) {
+ return UploadResultActivity.class;
+ } else {
+ if (request.isSendDocs()) {
+ return SendDocsActivity.class;
+ } else {
+ return UploadResultActivity.class;
+ }
+ }
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java
index eb91bc30a..b8600dcdd 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java
@@ -454,7 +454,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
* @param total the number of total locations
*/
private void updateProgress(int uploaded, int total) {
- double totalPercentage = uploaded / total;
+ double totalPercentage = (double) uploaded / total;
double scaledPercentage = totalPercentage
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
publishProgress((int) scaledPercentage);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/ChooseMapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/ChooseMapActivity.java
index 25871fad2..8ba5f3de4 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/maps/ChooseMapActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/maps/ChooseMapActivity.java
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.io.maps;
import com.google.android.apps.mytracks.io.gdata.maps.MapsMapMetadata;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.maps.mytracks.R;
+import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.AlertDialog;
@@ -46,12 +47,14 @@ import java.util.ArrayList;
public class ChooseMapActivity extends Activity {
private static final int PROGRESS_DIALOG = 1;
- private static final int ERROR_DIALOG = 2;
-
+ @VisibleForTesting
+ static final int ERROR_DIALOG = 2;
+
private SendRequest sendRequest;
private ChooseMapAsyncTask asyncTask;
private ProgressDialog progressDialog;
- private ArrayAdapter arrayAdapter;
+ @VisibleForTesting
+ ArrayAdapter arrayAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -166,7 +169,7 @@ public class ChooseMapActivity extends Activity {
*/
public void onAsyncTaskCompleted(
boolean success, ArrayList mapIds, ArrayList mapData) {
- removeDialog(PROGRESS_DIALOG);
+ removeProgressDialog();
if (success) {
arrayAdapter.clear();
// To prevent displaying the emptyView message momentarily before the
@@ -181,7 +184,7 @@ public class ChooseMapActivity extends Activity {
}
}
} else {
- showDialog(ERROR_DIALOG);
+ showErrorDialog();
}
}
@@ -191,6 +194,22 @@ public class ChooseMapActivity extends Activity {
public void showProgressDialog() {
showDialog(PROGRESS_DIALOG);
}
+
+ /**
+ * Shows the error dialog.
+ */
+ @VisibleForTesting
+ void showErrorDialog() {
+ showDialog(ERROR_DIALOG);
+ }
+
+ /**
+ * Remove the progress dialog.
+ */
+ @VisibleForTesting
+ void removeProgressDialog() {
+ removeDialog(PROGRESS_DIALOG);
+ }
/**
* Starts the next activity, {@link SendMapsActivity}.
@@ -210,7 +229,8 @@ public class ChooseMapActivity extends Activity {
*
* @author Jimmy Shih
*/
- private class ListItem {
+ @VisibleForTesting
+ class ListItem {
private String mapId;
private MapsMapMetadata mapData;
@@ -233,4 +253,4 @@ public class ChooseMapActivity extends Activity {
return mapData;
}
}
-}
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsActivity.java
index 9d54efed5..c500c96f6 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsActivity.java
@@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.io.sendtogoogle.AbstractSendAsyncTask;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity;
import com.google.android.maps.mytracks.R;
+import com.google.common.annotations.VisibleForTesting;
import android.content.Intent;
@@ -47,20 +48,25 @@ public class SendMapsActivity extends AbstractSendActivity {
protected void startNextActivity(boolean success, boolean isCancel) {
sendRequest.setMapsSuccess(success);
- Class> next;
- if (isCancel) {
- next = UploadResultActivity.class;
- } else {
- if (sendRequest.isSendFusionTables()) {
- next = SendFusionTablesActivity.class;
- } else if (sendRequest.isSendDocs()) {
- next = SendDocsActivity.class;
- } else {
- next = UploadResultActivity.class;
- }
- }
+ Class> next = getNextClass(sendRequest, isCancel);
+
Intent intent = new Intent(this, next).putExtra(SendRequest.SEND_REQUEST_KEY, sendRequest);
startActivity(intent);
finish();
}
+
+ @VisibleForTesting
+ Class> getNextClass(SendRequest request, boolean isCancel) {
+ if (isCancel) {
+ return UploadResultActivity.class;
+ } else {
+ if (request.isSendFusionTables()) {
+ return SendFusionTablesActivity.class;
+ } else if (request.isSendDocs()) {
+ return SendDocsActivity.class;
+ } else {
+ return UploadResultActivity.class;
+ }
+ }
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java
index f18a3c164..00c866922 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java
@@ -472,8 +472,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
* @param total the number of total locations
*/
private void updateProgress(int uploaded, int total) {
- double totalPercentage = uploaded / total;
- double scaledPercentage = totalPercentage
+ double totalPercentage = (double) uploaded / total;
+ double scaledPercentage = totalPercentage
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
publishProgress((int) scaledPercentage);
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/AccountChooserActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/AccountChooserActivity.java
index 3de566876..626cf3177 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/AccountChooserActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/AccountChooserActivity.java
@@ -109,7 +109,7 @@ public class AccountChooserActivity extends Activity {
super.onResume();
if (accounts.length == 0) {
showDialog(NO_ACCOUNT_DIALOG);
- } else {
+ } else if (accounts.length > 1 ) {
showDialog(CHOOSE_ACCOUNT_DIALOG);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivity.java
index 5e4859c85..5a18b927b 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivity.java
@@ -15,10 +15,9 @@
*/
package com.google.android.apps.mytracks.io.sendtogoogle;
-import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import com.google.android.apps.mytracks.Constants;
+import com.google.android.apps.mytracks.util.AnalyticsUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
-import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
@@ -39,6 +38,8 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.TableRow;
+import java.util.ArrayList;
+
/**
* A chooser to select the Google services to upload a track to.
*
@@ -246,20 +247,17 @@ public class UploadServiceChooserActivity extends Activity {
* Sends stats to Google Analytics.
*/
private void sendStats() {
- GoogleAnalyticsTracker tracker = GoogleAnalyticsTracker.getInstance();
- tracker.start(getString(R.string.my_tracks_analytics_id), getApplicationContext());
- tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(this));
+ ArrayList pages = new ArrayList();
if (sendRequest.isSendMaps()) {
- tracker.trackPageView("/send/maps");
+ pages.add("/send/maps");
}
if (sendRequest.isSendFusionTables()) {
- tracker.trackPageView("/send/fusion_tables");
+ pages.add("/send/fusion_tables");
}
if (sendRequest.isSendDocs()) {
- tracker.trackPageView("/send/docs");
+ pages.add("/send/docs");
}
- tracker.dispatch();
- tracker.stop();
+ AnalyticsUtils.sendPageViews(this, pages.toArray(new String[pages.size()]));
}
@VisibleForTesting
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/AnalyticsUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/AnalyticsUtils.java
new file mode 100644
index 000000000..d0b00dea3
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/AnalyticsUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.android.apps.mytracks.util;
+
+import com.google.android.apps.analytics.GoogleAnalyticsTracker;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Context;
+
+/**
+ * Utitlites for sending pageviews to Google Analytics.
+ *
+ * @author Jimmy Shih
+ */
+public class AnalyticsUtils {
+
+ private AnalyticsUtils() {}
+
+ public static void sendPageViews(Context context, String ... pages) {
+ GoogleAnalyticsTracker tracker = GoogleAnalyticsTracker.getInstance();
+ tracker.start(context.getString(R.string.my_tracks_analytics_id), context);
+ tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(context));
+ for (String page : pages) {
+ tracker.trackPageView(page);
+ }
+ tracker.dispatch();
+ tracker.stop();
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/FileUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/FileUtils.java
index caca3ff32..99ce45838 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/FileUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/FileUtils.java
@@ -21,8 +21,6 @@ import com.google.common.annotations.VisibleForTesting;
import android.os.Environment;
import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.TimeZone;
/**
* Utilities for dealing with files.
@@ -38,16 +36,6 @@ public class FileUtils {
@VisibleForTesting
static final int MAX_FAT32_PATH_LENGTH = 260;
- /**
- * Timestamp format in UTC time zone.
- */
- public static final SimpleDateFormat FILE_TIMESTAMP_FORMAT =
- new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
-
- static {
- FILE_TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
- }
-
/**
* Builds a path inside the My Tracks directory in the SD card.
*
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/PlayTrackUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/PlayTrackUtils.java
index b890de426..09e37656d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/PlayTrackUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/PlayTrackUtils.java
@@ -45,14 +45,17 @@ public class PlayTrackUtils {
* KML mime type.
*/
public static final String KML_MIME_TYPE = "application/vnd.google-earth.kml+xml";
-
- private static final String GOOGLE_EARTH_PACKAGE = "com.google.earth";
+ public static final String TOUR_FEATURE_ID = "com.google.earth.EXTRA.tour_feature_id";
+
+ public static final String GOOGLE_EARTH_PACKAGE = "com.google.earth";
+ public static final String GOOGLE_EARTH_CLASS = "com.google.earth.EarthActivity";
private static final String EARTN_MARKET_URI = "market://details?id=" + GOOGLE_EARTH_PACKAGE;
private PlayTrackUtils() {}
/**
- * Returns true if Google Earth is installed.
+ * Returns true if a Google Earth app that can handle KML mine type is
+ * installed.
*
* @param context the context
*/
@@ -75,6 +78,8 @@ public class PlayTrackUtils {
* @param trackId the track id
*/
public static void playTrack(Context context, long trackId) {
+ AnalyticsUtils.sendPageViews(context, "/action/play");
+
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
Intent intent = new Intent(context, SaveActivity.class)
.putExtra(SaveActivity.EXTRA_FILE_FORMAT, TrackFileFormat.KML.ordinal())
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java
index b77f737c5..c7980a1fb 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java
@@ -18,14 +18,13 @@ package com.google.android.apps.mytracks.util;
import com.google.android.maps.mytracks.R;
import android.content.Context;
+import android.text.TextUtils;
import android.text.format.DateUtils;
-import java.text.DateFormat;
-import java.text.NumberFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.SimpleTimeZone;
+import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -37,19 +36,20 @@ import java.util.regex.Pattern;
*/
public class StringUtils {
- private StringUtils() {}
+ private static final SimpleDateFormat ISO_8601_DATE_TIME_FORMAT = new SimpleDateFormat(
+ "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+ private static final SimpleDateFormat ISO_8601_BASE = new SimpleDateFormat(
+ "yyyy-MM-dd'T'HH:mm:ss");
+ private static final Pattern ISO_8601_EXTRAS = Pattern.compile(
+ "^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$");
- /**
- * Formats the date and time based on user's phone date/time preferences.
- *
- * @param context the context
- * @param time the time in milliseconds
- */
- public static String formatDateTime(Context context, long time) {
- DateFormat dateFormatter = android.text.format.DateFormat.getDateFormat(context);
- return dateFormatter.format(new Date(time)) + " " + formatTime(context, time);
+ static {
+ ISO_8601_DATE_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+ ISO_8601_BASE.setTimeZone(TimeZone.getTimeZone("UTC"));
}
+ private StringUtils() {}
+
/**
* Formats the time based on user's phone date/time preferences.
*
@@ -57,8 +57,28 @@ public class StringUtils {
* @param time the time in milliseconds
*/
public static String formatTime(Context context, long time) {
- DateFormat timeFormatter = android.text.format.DateFormat.getTimeFormat(context);
- return timeFormatter.format(new Date(time));
+ return android.text.format.DateFormat.getTimeFormat(context).format(time);
+ }
+
+ /**
+ * Formats the date and time based on user's phone date/time preferences.
+ *
+ * @param context the context
+ * @param time the time in milliseconds
+ */
+ public static String formatDateTime(Context context, long time) {
+ return android.text.format.DateFormat.getDateFormat(context).format(time) + " "
+ + formatTime(context, time);
+ }
+
+ /**
+ * Formats the time using the ISO 8601 date time format with fractional
+ * seconds in UTC time zone.
+ *
+ * @param time the time in milliseconds
+ */
+ public static String formatDateTimeIso8601(long time) {
+ return ISO_8601_DATE_TIME_FORMAT.format(time);
}
/**
@@ -71,103 +91,117 @@ public class StringUtils {
}
/**
- * Formats the elapsed time and total distance.
+ * Formats the elapsed time in the form "H:MM:SS".
*
- * @param context the current context
- * @param totalDistance the total distance in meters
- * @param totalTime the total time in milliseconds
- * @param metric whether to use metric units
- * @return the formatted string
+ * @param time the time in milliseconds
*/
- public static String formatTimeDistance(Context context, double totalDistance, long totalTime, boolean metric) {
- String distanceUnit;
+ public static String formatElapsedTimeWithHour(long time) {
+ String value = formatElapsedTime(time);
+ return TextUtils.split(value, ":").length == 2 ? "0:" + value : value;
+ }
+
+ /**
+ * Formats the distance.
+ *
+ * @param context the context
+ * @param distance the distance in meters
+ * @param metric true to use metric. False to use imperial
+ */
+ public static String formatDistance(Context context, double distance, boolean metric) {
if (metric) {
- if (totalDistance > 2000.0) {
- totalDistance *= UnitConversions.M_TO_KM;
- distanceUnit = context.getString(R.string.unit_kilometer);
+ if (distance > 2000.0) {
+ distance *= UnitConversions.M_TO_KM;
+ return context.getString(R.string.value_float_kilometer, distance);
} else {
- distanceUnit = context.getString(R.string.unit_meter);
+ return context.getString(R.string.value_float_meter, distance);
}
} else {
- if (totalDistance * UnitConversions.M_TO_MI > 2) {
- totalDistance *= UnitConversions.M_TO_MI;
- distanceUnit = context.getString(R.string.unit_mile);
+ if (distance * UnitConversions.M_TO_MI > 2) {
+ distance *= UnitConversions.M_TO_MI;
+ return context.getString(R.string.value_float_mile, distance);
} else {
- totalDistance *= UnitConversions.M_TO_FT;
- distanceUnit = context.getString(R.string.unit_feet);
+ distance *= UnitConversions.M_TO_FT;
+ return context.getString(R.string.value_float_feet, distance);
}
}
- return String.format("%s %.2f %s",
- formatElapsedTime(totalTime),
- totalDistance,
- distanceUnit);
}
-
- private static final NumberFormat SINGLE_DECIMAL_PLACE_FORMAT = NumberFormat.getNumberInstance();
-
- static {
- SINGLE_DECIMAL_PLACE_FORMAT.setMaximumFractionDigits(1);
- SINGLE_DECIMAL_PLACE_FORMAT.setMinimumFractionDigits(1);
- }
-
+
/**
- * Formats a double precision number as decimal number with a single decimal
- * place.
- *
- * @param number A double precision number
- * @return A string representation of a decimal number, derived from the input
- * double, with a single decimal place
+ * Formats the speed.
+ *
+ * @param context the context
+ * @param speed the speed in meters per second
+ * @param metric true to use metric. False to use imperial
+ * @param reportSpeed true to report as speed. False to report as pace
*/
- public static final String formatSingleDecimalPlace(double number) {
- return SINGLE_DECIMAL_PLACE_FORMAT.format(number);
- }
-
- /**
- * Formats the given text as a CDATA element to be used in a XML file. This
- * includes adding the starting and ending CDATA tags. Please notice that this
- * may result in multiple consecutive CDATA tags.
- *
- * @param unescaped the unescaped text to be formatted
- * @return the formatted text, inside one or more CDATA tags
- */
- public static String stringAsCData(String unescaped) {
- // "]]>" needs to be broken into multiple CDATA segments, like:
- // "Foo]]>Bar" becomes "Bar]]>"
- // (the end of the first CDATA has the "]]", the other has ">")
- String escaped = unescaped.replaceAll("]]>", "]]]]>");
- return "";
- }
-
- private static final SimpleDateFormat BASE_XML_DATE_FORMAT =
- new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
- static {
- BASE_XML_DATE_FORMAT.setTimeZone(new SimpleTimeZone(0, "UTC"));
- }
-
- private static final Pattern XML_DATE_EXTRAS_PATTERN =
- Pattern.compile("^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$");
-
- /**
- * Parses an XML dateTime element as defined by the XML standard.
- *
- * @see dateTime
- */
- public static long parseXmlDateTime(String xmlTime) {
- // Parse the base date (fixed format)
- ParsePosition position = new ParsePosition(0);
- Date date = BASE_XML_DATE_FORMAT.parse(xmlTime, position);
- if (date == null) {
- throw new IllegalArgumentException("Invalid XML dateTime value: '" + xmlTime
- + "' (at position " + position.getErrorIndex() + ")");
+ public static String formatSpeed(
+ Context context, double speed, boolean metric, boolean reportSpeed) {
+ if (Double.isNaN(speed) || Double.isInfinite(speed)) {
+ return context.getString(R.string.value_unknown);
}
+ if (metric) {
+ speed = speed * UnitConversions.MS_TO_KMH;
+ if (reportSpeed) {
+ return context.getString(R.string.value_float_kilometer_hour, speed);
+ } else {
+ double paceInMinute = speed == 0 ? 0.0 : 60 / speed;
+ return context.getString(R.string.value_float_minute_kilometer, paceInMinute);
+ }
+ } else {
+ speed = speed * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI;
+ if (reportSpeed) {
+ return context.getString(R.string.value_float_mile_hour, speed);
+ } else {
+ double paceInMinute = speed == 0 ? 0.0 : 60 / speed;
+ return context.getString(R.string.value_float_minute_mile, paceInMinute);
+ }
+ }
+ }
+
+ /**
+ * Formats the elapsed time and distance.
+ *
+ * @param context the context
+ * @param elapsedTime the elapsed time in milliseconds
+ * @param distance the distance in meters
+ * @param metric true to use metric. False to use imperial
+ */
+ public static String formatTimeDistance(
+ Context context, long elapsedTime, double distance, boolean metric) {
+ return formatElapsedTime(elapsedTime) + " " + formatDistance(context, distance, metric);
+ }
- // Parse the extras
- Matcher matcher =
- XML_DATE_EXTRAS_PATTERN.matcher(xmlTime.substring(position.getIndex()));
+ /**
+ * Formats the given text as a XML CDATA element. This includes adding the
+ * starting and ending CDATA tags. Please notice that this may result in
+ * multiple consecutive CDATA tags.
+ *
+ * @param text the given text
+ */
+ public static String formatCData(String text) {
+ return "", "]]]]>") + "]]>";
+ }
+
+ /**
+ * Gets the time, in milliseconds, from an XML date time string as defined at
+ * http://www.w3.org/TR/xmlschema-2/#dateTime
+ *
+ * @param xmlDateTime the XML date time string
+ */
+ public static long getTime(String xmlDateTime) {
+ // Parse the date time base
+ ParsePosition position = new ParsePosition(0);
+ Date date = ISO_8601_BASE.parse(xmlDateTime, position);
+ if (date == null) {
+ throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime
+ + " (at position " + position.getErrorIndex() + ")"); }
+
+ // Parse the date time extras
+ Matcher matcher = ISO_8601_EXTRAS.matcher(xmlDateTime.substring(position.getIndex()));
if (!matcher.matches()) {
- // This will match even an empty string as all groups are optional,
- // so a non-match means some other garbage was there
- throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlTime);
+ // This will match even an empty string as all groups are optional. Thus a
+ // non-match means invalid content.
+ throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime);
}
long time = date.getTime();
@@ -175,7 +209,7 @@ public class StringUtils {
// Account for fractional seconds
String fractional = matcher.group(1);
if (fractional != null) {
- // Regex ensures fractional part is in (0,1(
+ // Regex ensures fractional part is in (0,1)
float fractionalSeconds = Float.parseFloat(fractional);
long fractionalMillis = (long) (fractionalSeconds * 1000.0f);
time += fractionalMillis;
@@ -193,24 +227,28 @@ public class StringUtils {
// Regex ensures values are >= 0
if (offsetHours > 14 || offsetMins > 59) {
- throw new IllegalArgumentException("Bad timezone in " + xmlTime);
+ throw new IllegalArgumentException("Bad timezone: " + xmlDateTime);
}
long totalOffsetMillis = (offsetMins + offsetHours * 60L) * 60000L;
- // Make time go back to UTC
+ // Convert to UTC
if (plusSign) {
time -= totalOffsetMillis;
} else {
time += totalOffsetMillis;
}
}
-
return time;
}
/**
- * Gets the time as an array of parts.
+ * Gets the time as an array of three integers. Index 0 contains the number of
+ * seconds, index 1 contains the number of minutes, and index 2 contains the
+ * number of hours.
+ *
+ * @param time the time in milliseconds
+ * @return an array of 3 elements.
*/
public static int[] getTimeParts(long time) {
if (time < 0) {
@@ -224,10 +262,9 @@ public class StringUtils {
long seconds = time / 1000;
parts[0] = (int) (seconds % 60);
- int tmp = (int) (seconds / 60);
- parts[1] = tmp % 60;
- parts[2] = tmp / 60;
-
+ int minutes = (int) (seconds / 60);
+ parts[1] = minutes % 60;
+ parts[2] = minutes / 60;
return parts;
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java b/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java
index 49ab1df9c..9d123295d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java
@@ -26,7 +26,6 @@ import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.services.ControlRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.StringUtils;
-import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.PendingIntent;
@@ -68,9 +67,6 @@ public class TrackWidgetProvider
private MyTracksProviderUtils providerUtils;
private Context context;
private String unknown;
- private String distanceLabel;
- private String speedLabel;
- private String paceLabel;
private TrackObserver trackObserver;
private boolean isMetric;
private boolean reportSpeed;
@@ -213,32 +209,10 @@ public class TrackWidgetProvider
}
TripStatistics stats = track.getStatistics();
-
- // TODO replace this with format strings and miles.
- // convert meters to kilometers
- double displayDistance = stats.getTotalDistance() * UnitConversions.M_TO_KM;
- if (!isMetric) {
- displayDistance *= UnitConversions.KM_TO_MI;
- }
- String distance =
- StringUtils.formatSingleDecimalPlace(displayDistance) + " " + this.distanceLabel;
-
- // convert ms to minutes
+ String distance = StringUtils.formatDistance(context, stats.getTotalDistance(), isMetric);
String time = StringUtils.formatElapsedTime(stats.getMovingTime());
- String speed = unknown;
- if (!Double.isNaN(stats.getAverageMovingSpeed())) {
- // Convert m/s to km/h
- double displaySpeed = stats.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
- if (!isMetric) {
- displaySpeed *= UnitConversions.KM_TO_MI;
- }
- if (reportSpeed) {
- speed = StringUtils.formatSingleDecimalPlace(displaySpeed) + " " + this.speedLabel;
- } else {
- long displayPace = (long) (3600000.0 / displaySpeed);
- speed = StringUtils.formatElapsedTime(displayPace) + " " + paceLabel;
- }
- }
+ String speed = StringUtils.formatSpeed(
+ context, stats.getAverageMovingSpeed(), isMetric, reportSpeed);
views.setTextViewText(R.id.appwidget_distance_text, distance);
views.setTextViewText(R.id.appwidget_time_text, time);
@@ -250,11 +224,6 @@ public class TrackWidgetProvider
String metricUnitsKey = context.getString(R.string.metric_units_key);
if (key == null || key.equals(metricUnitsKey)) {
isMetric = prefs.getBoolean(metricUnitsKey, true);
- distanceLabel = context.getString(isMetric ? R.string.unit_kilometer : R.string.unit_mile);
- speedLabel = context.getString(
- isMetric ? R.string.unit_kilometer_per_hour : R.string.unit_mile_per_hour);
- paceLabel = context.getString(
- isMetric ? R.string.unit_minute_per_kilometer : R.string.unit_minute_per_mile);
}
String reportSpeedKey = context.getString(R.string.report_speed_key);
diff --git a/MyTracksTest/.classpath b/MyTracksTest/.classpath
index d91442653..6044ff11f 100644
--- a/MyTracksTest/.classpath
+++ b/MyTracksTest/.classpath
@@ -10,6 +10,6 @@
-
+
diff --git a/MyTracksTest/libs/robotium-solo-2.1.jar b/MyTracksTest/libs/robotium-solo-2.1.jar
deleted file mode 100644
index 73af311f6..000000000
Binary files a/MyTracksTest/libs/robotium-solo-2.1.jar and /dev/null differ
diff --git a/MyTracksTest/libs/robotium-solo-3.1.jar b/MyTracksTest/libs/robotium-solo-3.1.jar
new file mode 100644
index 000000000..0ba05859b
Binary files /dev/null and b/MyTracksTest/libs/robotium-solo-3.1.jar differ
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java
index 966bcd470..ffd516c4b 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java
@@ -43,13 +43,6 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2 segmentTags = getChildElements(trackTag, "trkseg", 2);
List segPointTags = getChildElements(segmentTags.get(0), "trkpt", 2);
- assertTagMatchesLocation(segPointTags.get(0), "0", "0", "1970-01-01T02:46:40Z", "0");
- assertTagMatchesLocation(segPointTags.get(1), "1", "-1", "1970-01-01T02:46:41Z", "5000000");
+ assertTagMatchesLocation(segPointTags.get(0), "0", "0", "1970-01-01T00:00:00Z", "0");
+ assertTagMatchesLocation(segPointTags.get(1), "1", "-1", "1970-01-01T00:01:40Z", "10");
segPointTags = getChildElements(segmentTags.get(1), "trkpt", 2);
- assertTagMatchesLocation(segPointTags.get(0), "2", "-2", "1970-01-01T02:46:42Z", "10000000");
- assertTagMatchesLocation(segPointTags.get(1), "3", "-3", "1970-01-01T02:46:43Z", "15000000");
+ assertTagMatchesLocation(segPointTags.get(0), "2", "-2", "1970-01-01T00:03:20Z", "20");
+ assertTagMatchesLocation(segPointTags.get(1), "3", "-3", "1970-01-01T00:05:00Z", "30");
List waypointTags = getChildElements(gpxTag, "wpt", 2);
Element wptTag = waypointTags.get(0);
assertEquals(WAYPOINT1_NAME, getChildTextValue(wptTag, "name"));
assertEquals(WAYPOINT1_DESCRIPTION, getChildTextValue(wptTag, "desc"));
- assertTagMatchesLocation(wptTag, "1", "-1", "1970-01-01T02:46:41Z", "5000000");
+ assertTagMatchesLocation(wptTag, "1", "-1", "1970-01-01T00:01:40Z", "10");
wptTag = waypointTags.get(1);
assertEquals(WAYPOINT2_NAME, getChildTextValue(wptTag, "name"));
assertEquals(WAYPOINT2_DESCRIPTION, getChildTextValue(wptTag, "desc"));
- assertTagMatchesLocation(wptTag, "2", "-2", "1970-01-01T02:46:42Z", "10000000");
+ assertTagMatchesLocation(wptTag, "2", "-2", "1970-01-01T00:03:20Z", "20");
}
/**
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlTrackWriterTest.java
index 9d23eb5e0..93c5a9aa2 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlTrackWriterTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlTrackWriterTest.java
@@ -63,14 +63,19 @@ public class KmlTrackWriterTest extends TrackFormatWriterTest {
assertEquals(TRACK_NAME, getChildTextValue(docTag, "name"));
assertEquals(TRACK_DESCRIPTION, getChildTextValue(docTag, "description"));
- // There are 5 placemarks - start, segments, end, waypoint1, waypoint2
- List placemarkTags = getChildElements(docTag, "Placemark", 5);
+ // There are 3 placemarks - start, track, and end
+ List placemarkTags = getChildElements(docTag, "Placemark", 3);
assertTagIsPlacemark(
placemarkTags.get(0), TRACK_NAME + " (Start)", TRACK_DESCRIPTION, location1);
assertTagIsPlacemark(
placemarkTags.get(2), TRACK_NAME + " (End)", FULL_TRACK_DESCRIPTION, location4);
- assertTagIsPlacemark(placemarkTags.get(3), WAYPOINT1_NAME, WAYPOINT1_DESCRIPTION, location2);
- assertTagIsPlacemark(placemarkTags.get(4), WAYPOINT2_NAME, WAYPOINT2_DESCRIPTION, location3);
+
+ List folderTag = getChildElements(docTag, "Folder", 1);
+ List folderPlacemarkTags = getChildElements(folderTag.get(0), "Placemark", 2);
+ assertTagIsPlacemark(
+ folderPlacemarkTags.get(0), WAYPOINT1_NAME, WAYPOINT1_DESCRIPTION, location2);
+ assertTagIsPlacemark(
+ folderPlacemarkTags.get(1), WAYPOINT2_NAME, WAYPOINT2_DESCRIPTION, location3);
Element trackPlacemarkTag = placemarkTags.get(1);
assertEquals(TRACK_NAME, getChildTextValue(trackPlacemarkTag, "name"));
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TcxTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TcxTrackWriterTest.java
index 6594cf51c..547229a4e 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TcxTrackWriterTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TcxTrackWriterTest.java
@@ -1,9 +1,23 @@
-// Copyright 2010 Google Inc. All Rights Reserved.
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
package com.google.android.apps.mytracks.io.file;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
-import com.google.android.apps.mytracks.util.FileUtils;
+import com.google.android.apps.mytracks.util.StringUtils;
import java.util.List;
@@ -11,7 +25,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
- * Tests for the GPX track exporter.
+ * Tests for {@link TcxTrackWriter}.
*
* @author Sandor Dornbush
*/
@@ -37,42 +51,46 @@ public class TcxTrackWriterTest extends TrackFormatWriterTest {
}
/**
- * Asserts that the given tags describe the given points, in the same order.
+ * Asserts that the given tags describe the given locations in the same order.
+ *
+ * @param tags list of tags
+ * @param locations list of locations
*/
- private void assertTagsMatchPoints(List tags, MyTracksLocation... locs) {
- assertEquals(locs.length, tags.size());
- for (int i = 0; i < locs.length; i++) {
- Element tag = tags.get(i);
- MyTracksLocation loc = locs[i];
-
- assertTagMatchesLocation(tag, loc);
+ private void assertTagsMatchPoints(List tags, MyTracksLocation... locations) {
+ assertEquals(locations.length, tags.size());
+ for (int i = 0; i < locations.length; i++) {
+ assertTagMatchesLocation(tags.get(i), locations[i]);
}
}
/**
* Asserts that the given tag describes the given location.
+ *
+ * @param tag the tag
+ * @param location the location
*/
- private void assertTagMatchesLocation(Element tag, MyTracksLocation loc) {
- Element posTag = getChildElement(tag, "Position");
- assertEquals(Double.toString(loc.getLatitude()),
- getChildTextValue(posTag, "LatitudeDegrees"));
- assertEquals(Double.toString(loc.getLongitude()),
- getChildTextValue(posTag, "LongitudeDegrees"));
+ private void assertTagMatchesLocation(Element tag, MyTracksLocation location) {
+ assertEquals(StringUtils.formatDateTimeIso8601(location.getTime()), getChildTextValue(tag, "Time"));
- assertEquals(FileUtils.FILE_TIMESTAMP_FORMAT.format(loc.getTime()),
- getChildTextValue(tag, "Time"));
- assertEquals(Double.toString(loc.getAltitude()),
- getChildTextValue(tag, "AltitudeMeters"));
- assertTrue(loc.getSensorDataSet() != null);
- Sensor.SensorDataSet sds = loc.getSensorDataSet();
+ Element positionTag = getChildElement(tag, "Position");
+ assertEquals(
+ Double.toString(location.getLatitude()), getChildTextValue(positionTag, "LatitudeDegrees"));
+ assertEquals(Double.toString(location.getLongitude()),
+ getChildTextValue(positionTag, "LongitudeDegrees"));
- List bpm = getChildElements(tag, "HeartRateBpm", 1);
+ assertEquals(Double.toString(location.getAltitude()), getChildTextValue(tag, "AltitudeMeters"));
+ assertTrue(location.getSensorDataSet() != null);
+ Sensor.SensorDataSet sds = location.getSensorDataSet();
+
+ List heartRate = getChildElements(tag, "HeartRateBpm", 1);
assertEquals(Integer.toString(sds.getHeartRate().getValue()),
- getChildTextValue(bpm.get(0), "Value"));
+ getChildTextValue(heartRate.get(0), "Value"));
- List ext = getChildElements(tag, "Extensions", 1);
- List tpx = getChildElements(ext.get(0), "TPX", 1);
- assertEquals(Integer.toString(sds.getPower().getValue()),
- getChildTextValue(tpx.get(0), "Watts"));
+ List extensions = getChildElements(tag, "Extensions", 1);
+ List tpx = getChildElements(extensions.get(0), "TPX", 1);
+ assertEquals(
+ Integer.toString(sds.getCadence().getValue()), getChildTextValue(tpx.get(0), "RunCadence"));
+ assertEquals(
+ Integer.toString(sds.getPower().getValue()), getChildTextValue(tpx.get(0), "Watts"));
}
}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackFormatWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackFormatWriterTest.java
index bba7f8df7..4afb7465e 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackFormatWriterTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackFormatWriterTest.java
@@ -39,10 +39,13 @@ public abstract class TrackFormatWriterTest extends AndroidTestCase {
// All the user-provided strings have "]]>" to ensure that proper escaping is
// being done.
protected static final String TRACK_NAME = "Home]]>";
+ protected static final String TRACK_CATEGORY = "Hiking";
protected static final String TRACK_DESCRIPTION = "The long ]]> journey home";
protected static final String WAYPOINT1_NAME = "point]]>1";
+ protected static final String WAYPOINT1_CATEGORY = "Statistics";
protected static final String WAYPOINT1_DESCRIPTION = "point 1]]>description";
protected static final String WAYPOINT2_NAME = "point]]>2";
+ protected static final String WAYPOINT2_CATEGORY = "Waypoint";
protected static final String WAYPOINT2_DESCRIPTION = "point 2]]>description";
private static final int BUFFER_SIZE = 10240;
protected Track track;
@@ -55,6 +58,7 @@ public abstract class TrackFormatWriterTest extends AndroidTestCase {
track = new Track();
track.setName(TRACK_NAME);
+ track.setCategory(TRACK_CATEGORY);
track.setDescription(TRACK_DESCRIPTION);
location1 = new MyTracksLocation("mock");
@@ -67,33 +71,40 @@ public abstract class TrackFormatWriterTest extends AndroidTestCase {
wp2 = new Waypoint();
wp1.setLocation(location2);
wp1.setName(WAYPOINT1_NAME);
+ wp1.setCategory(WAYPOINT1_CATEGORY);
wp1.setDescription(WAYPOINT1_DESCRIPTION);
wp2.setLocation(location3);
wp2.setName(WAYPOINT2_NAME);
+ wp2.setCategory(WAYPOINT2_CATEGORY);
wp2.setDescription(WAYPOINT2_DESCRIPTION);
}
/**
- * Populates the given locations with coordinates and time.
+ * Populates a list of locations with values.
+ *
+ * @param locations a list of locations
*/
- protected void populateLocations(MyTracksLocation... locs) {
- for (int i = 0; i < locs.length; i++) {
- MyTracksLocation loc = locs[i];
- loc.setAltitude(i * 5000000);
- loc.setLatitude(i);
- loc.setLongitude(-i);
- loc.setTime(10000000 + i * 1000);
- Sensor.SensorData.Builder hr = Sensor.SensorData.newBuilder()
- .setValue(100 + i)
- .setState(Sensor.SensorState.SENDING);
- Sensor.SensorData.Builder power = Sensor.SensorData.newBuilder()
- .setValue(400 + i)
- .setState(Sensor.SensorState.SENDING);
- Sensor.SensorDataSet sds =
- Sensor.SensorDataSet.newBuilder().setHeartRate(hr.build())
- .setPower(power)
- .build();
- loc.setSensorData(sds);
+ private void populateLocations(MyTracksLocation... locations) {
+ for (int i = 0; i < locations.length; i++) {
+ MyTracksLocation location = locations[i];
+ location.setLatitude(i);
+ location.setLongitude(-i);
+ location.setAltitude(i * 10);
+ location.setBearing(i * 100);
+ location.setAccuracy(i * 1000);
+ location.setSpeed(i * 10000);
+ location.setTime(i * 100000);
+ Sensor.SensorData.Builder power = Sensor.SensorData.newBuilder().setValue(100 + i)
+ .setState(Sensor.SensorState.SENDING);
+ Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder().setValue(200 + i)
+ .setState(Sensor.SensorState.SENDING);
+ Sensor.SensorData.Builder heartRate = Sensor.SensorData.newBuilder().setValue(300 + i)
+ .setState(Sensor.SensorState.SENDING);
+ Sensor.SensorData.Builder batteryLevel = Sensor.SensorData.newBuilder().setValue(400 + i)
+ .setState(Sensor.SensorState.SENDING);
+ Sensor.SensorDataSet sensorDataSet = Sensor.SensorDataSet.newBuilder().setPower(power)
+ .setCadence(cadence).setHeartRate(heartRate).setBatteryLevel(batteryLevel).build();
+ location.setSensorData(sensorDataSet);
}
}
@@ -108,6 +119,10 @@ public abstract class TrackFormatWriterTest extends AndroidTestCase {
OutputStream output = new ByteArrayOutputStream(BUFFER_SIZE);
writer.prepare(track, output);
writer.writeHeader();
+ writer.writeBeginWaypoints();
+ writer.writeWaypoint(wp1);
+ writer.writeWaypoint(wp2);
+ writer.writeEndWaypoints();
writer.writeBeginTrack(location1);
writer.writeOpenSegment();
writer.writeLocation(location1);
@@ -118,8 +133,6 @@ public abstract class TrackFormatWriterTest extends AndroidTestCase {
writer.writeLocation(location4);
writer.writeCloseSegment();
writer.writeEndTrack(location4);
- writer.writeWaypoint(wp1);
- writer.writeWaypoint(wp2);
writer.writeFooter();
writer.close();
return output.toString();
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesActivityTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesActivityTest.java
new file mode 100644
index 000000000..09b095a5f
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesActivityTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.io.fusiontables;
+
+import com.google.android.apps.mytracks.io.docs.SendDocsActivity;
+import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
+import com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity;
+
+import android.test.AndroidTestCase;
+
+/**
+ * Tests the {@link SendFusionTablesActivity}.
+ *
+ * @author Youtao Liu
+ */
+public class SendFusionTablesActivityTest extends AndroidTestCase {
+
+ private SendFusionTablesActivity sendFusionTablesActivity;
+ private SendRequest sendRequest;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ sendRequest = new SendRequest(1L, true, false, true);
+ sendFusionTablesActivity = new SendFusionTablesActivity();
+ }
+
+ /**
+ * Tests the method
+ * {@link SendFusionTablesActivity#getNextClass(SendRequest, boolean)}. Sets
+ * the flags of "sendDocs" and "cancel" to false and false.
+ */
+ public void testGetNextClass_notCancelNotSendDocs() {
+ sendRequest.setSendDocs(false);
+ Class> next = sendFusionTablesActivity.getNextClass(sendRequest, false);
+ assertEquals(UploadResultActivity.class, next);
+ }
+
+ /**
+ * Tests the method
+ * {@link SendFusionTablesActivity#getNextClass(SendRequest, boolean)}. Sets
+ * the flags of "sendDocs" and "cancel" to true and false.
+ */
+ public void testGetNextClass_notCancelSendDocs() {
+ sendRequest.setSendDocs(true);
+ Class> next = sendFusionTablesActivity.getNextClass(sendRequest, false);
+ assertEquals(SendDocsActivity.class, next);
+ }
+
+ /**
+ * Tests the method
+ * {@link SendFusionTablesActivity#getNextClass(SendRequest,boolean)}. Sets
+ * the flags of "sendDocs" and "cancel" to true and true.
+ */
+ public void testGetNextClass_cancelSendDocs() {
+ sendRequest.setSendDocs(true);
+ Class> next = sendFusionTablesActivity.getNextClass(sendRequest, true);
+ assertEquals(UploadResultActivity.class, next);
+ }
+
+ /**
+ * Tests the method
+ * {@link SendFusionTablesActivity#getNextClass(SendRequest,boolean)}. Sets
+ * the flags of "sendDocs" and "cancel" to false and true.
+ */
+ public void testGetNextClass_cancelNotSendDocs() {
+ sendRequest.setSendDocs(false);
+ Class> next = sendFusionTablesActivity.getNextClass(sendRequest, true);
+ assertEquals(UploadResultActivity.class, next);
+ }
+
+}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/maps/ChooseMapActivityTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/maps/ChooseMapActivityTest.java
new file mode 100644
index 000000000..8c6ba5d13
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/maps/ChooseMapActivityTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2012 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.io.maps;
+
+import com.google.android.apps.mytracks.io.gdata.maps.MapsMapMetadata;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Intent;
+import android.test.AndroidTestCase;
+import android.widget.ArrayAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * Tests the {@link ChooseMapActivity}.
+ *
+ * @author Youtao Liu
+ */
+public class ChooseMapActivityTest extends AndroidTestCase {
+
+ private static final String MAP_ID = "mapid";
+ private static final String MAP_TITLE = "title";
+ private static final String MAP_DESC = "desc";
+
+ private ArrayList mapIds = new ArrayList();
+ private ArrayList mapDatas = new ArrayList();
+ private boolean errorDialogShown = false;
+ private boolean progressDialogRemoved = false;
+
+ /**
+ * Creates a class to override some methods of {@link ChooseMapActivity} to
+ * makes it testable.
+ *
+ * @author youtaol
+ */
+ public class ChooseMapActivityMock extends ChooseMapActivity {
+ /**
+ * By overriding this method, avoids to start next activity.
+ */
+ @Override
+ public void startActivity(Intent intent) {}
+
+ /**
+ * By overriding this method, avoids to show an error dialog and set the
+ * show flag to true.
+ */
+ @Override
+ public void showErrorDialog() {
+ errorDialogShown = true;
+ }
+
+ /**
+ * By overriding this method, avoids to show an error dialog and set the
+ * show flag to true.
+ */
+ @Override
+ public void removeProgressDialog() {
+ progressDialogRemoved = true;
+ }
+ }
+
+ /**
+ * Tests the method
+ * {@link ChooseMapActivity#onAsyncTaskCompleted(boolean, ArrayList, ArrayList)}
+ * . An alert dialog should be shown when there is no map.
+ */
+ public void testOnAsyncTaskCompleted_fail() {
+ ChooseMapActivityMock chooseMapActivityMock = new ChooseMapActivityMock();
+ errorDialogShown = false;
+ progressDialogRemoved = false;
+ chooseMapActivityMock.onAsyncTaskCompleted(false, null, null);
+ assertTrue(progressDialogRemoved);
+ assertTrue(errorDialogShown);
+ }
+
+ /**
+ * Tests the method
+ * {@link ChooseMapActivity#onAsyncTaskCompleted(boolean, ArrayList, ArrayList)}
+ * . Check the logic when there is only map.
+ */
+ public void testOnAsyncTaskCompleted_success_oneMap() {
+ ChooseMapActivityMock chooseMapActivityMock = new ChooseMapActivityMock();
+ chooseMapActivityMock.arrayAdapter = new ArrayAdapter(getContext(),
+ R.layout.choose_map_item);
+ simulateMaps(1);
+ chooseMapActivityMock.onAsyncTaskCompleted(true, mapIds, mapDatas);
+ assertEquals(1, chooseMapActivityMock.arrayAdapter.getCount());
+ assertEquals(MAP_ID + "0", chooseMapActivityMock.arrayAdapter.getItem(0).getMapId());
+ assertEquals(MAP_TITLE + "0", chooseMapActivityMock.arrayAdapter.getItem(0).getMapData()
+ .getTitle());
+ assertEquals(MAP_DESC + "0", chooseMapActivityMock.arrayAdapter.getItem(0).getMapData()
+ .getDescription());
+ }
+
+ /**
+ * Tests the method
+ * {@link ChooseMapActivity#onAsyncTaskCompleted(boolean, ArrayList, ArrayList)}
+ * . Check the logic when there are 10 maps.
+ */
+ public void testOnAsyncTaskCompleted_success_twoMaps() {
+ ChooseMapActivityMock chooseMapActivityMock = new ChooseMapActivityMock();
+ chooseMapActivityMock.arrayAdapter = new ArrayAdapter(getContext(),
+ R.layout.choose_map_item);
+ simulateMaps(10);
+ chooseMapActivityMock.onAsyncTaskCompleted(true, mapIds, mapDatas);
+ assertEquals(10, chooseMapActivityMock.arrayAdapter.getCount());
+ assertEquals(MAP_ID + "9", chooseMapActivityMock.arrayAdapter.getItem(9).getMapId());
+ assertEquals(MAP_TITLE + "9", chooseMapActivityMock.arrayAdapter.getItem(9).getMapData()
+ .getTitle());
+ assertEquals(MAP_DESC + "9", chooseMapActivityMock.arrayAdapter.getItem(9).getMapData()
+ .getDescription());
+ }
+
+ /**
+ * Simulates map data for the test.
+ *
+ * @param number of data should be simulated.
+ */
+ private void simulateMaps(int number) {
+ mapIds = new ArrayList();
+ mapDatas = new ArrayList();
+ for (int i = 0; i < number; i++) {
+ mapIds.add(MAP_ID + i);
+ MapsMapMetadata metaData = new MapsMapMetadata();
+ metaData.setTitle(MAP_TITLE + i);
+ metaData.setDescription(MAP_DESC + i);
+ metaData.setSearchable(true);
+ mapDatas.add(metaData);
+ }
+ }
+}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/maps/SendMapsActivityTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/maps/SendMapsActivityTest.java
new file mode 100644
index 000000000..d2646609a
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/maps/SendMapsActivityTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.io.maps;
+
+import com.google.android.apps.mytracks.io.docs.SendDocsActivity;
+import com.google.android.apps.mytracks.io.fusiontables.SendFusionTablesActivity;
+import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
+import com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity;
+
+import android.test.AndroidTestCase;
+
+/**
+ * Tests the {@link SendMapsActivity}.
+ *
+ * @author Youtao Liu
+ */
+public class SendMapsActivityTest extends AndroidTestCase {
+
+ private SendMapsActivity sendMapsActivity;
+ private SendRequest sendRequest;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ sendRequest = new SendRequest(1L, true, false, true);
+ sendMapsActivity = new SendMapsActivity();
+ }
+
+ /**
+ * Tests the method
+ * {@link SendMapsActivity#getNextClass(SendRequest, boolean)}. Sets the flags
+ * of "sendFusionTables","sendDocs" and "cancel" to true, true and false.
+ */
+ public void testGetNextClass_notCancelSendFusionTables() {
+ sendRequest.setSendFusionTables(true);
+ sendRequest.setSendDocs(true);
+ Class> next = sendMapsActivity.getNextClass(sendRequest, false);
+ assertEquals(SendFusionTablesActivity.class, next);
+ }
+
+ /**
+ * Tests the method
+ * {@link SendMapsActivity#getNextClass(SendRequest, boolean)}. Sets the flags
+ * of "sendFusionTables","sendDocs" and "cancel" to false, true and false.
+ */
+ public void testGetNextClass_notCancelSendDocs() {
+ sendRequest.setSendFusionTables(false);
+ sendRequest.setSendDocs(true);
+ Class> next = sendMapsActivity.getNextClass(sendRequest, false);
+ assertEquals(SendDocsActivity.class, next);
+ }
+
+ /**
+ * Tests the method
+ * {@link SendMapsActivity#getNextClass(SendRequest, boolean)}. Sets the flags
+ * of "sendFusionTables","sendDocs" and "cancel" to false, false and false.
+ */
+ public void testGetNextClass_notCancelNotSend() {
+ sendRequest.setSendFusionTables(false);
+ sendRequest.setSendDocs(false);
+ Class> next = sendMapsActivity.getNextClass(sendRequest, false);
+ assertEquals(UploadResultActivity.class, next);
+ }
+
+ /**
+ * Tests the method
+ * {@link SendMapsActivity#getNextClass(SendRequest, boolean)}. Sets the flags
+ * of "sendFusionTables","sendDocs" and "cancel" to true, true and true.
+ */
+ public void testGetNextClass_cancelSendDocs() {
+ sendRequest.setSendFusionTables(true);
+ sendRequest.setSendDocs(true);
+ Class> next = sendMapsActivity.getNextClass(sendRequest, true);
+ assertEquals(UploadResultActivity.class, next);
+ }
+
+}
\ No newline at end of file
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/sendtogoogle/SendRequestTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/sendtogoogle/SendRequestTest.java
index 1a8f8a7e2..cbac258a2 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/sendtogoogle/SendRequestTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/sendtogoogle/SendRequestTest.java
@@ -245,7 +245,7 @@ public class SendRequestTest extends AndroidTestCase {
}
/**
- * Tests {@link SendRequest#writeToParcel(Parcel in)} when all input values
+ * Tests {@link SendRequest#writeToParcel(Parcel, int)} when all input values
* are true or affirmative.
*/
public void testWriteToParcel_allTrue() {
@@ -283,7 +283,7 @@ public class SendRequestTest extends AndroidTestCase {
}
/**
- * Tests {@link SendRequest#writeToParcel(Parcel in)} when all input values
+ * Tests {@link SendRequest#writeToParcel(Parcel, int)} when all input values
* are false or negative.
*/
public void testWriteToParcel_allFalse() {
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivityTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivityTest.java
index 785f08a87..de378c3e8 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivityTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivityTest.java
@@ -51,7 +51,7 @@ public class UploadServiceChooserActivityTest extends
/**
* Tests the logic to control display all send options. This test cover code
- * in method {@link UploadServiceChooserActivity#onCreateDialog()},
+ * in method {@link UploadServiceChooserActivity#onCreateDialog(int)},
* {@link UploadServiceChooserActivity#updateStateBySendRequest()} and
* {@link UploadServiceChooserActivity#updateStateBySelection()}.
*/
@@ -91,7 +91,7 @@ public class UploadServiceChooserActivityTest extends
/**
* Tests the logic to check the send to Google Maps option. This test cover
- * code in method {@link UploadServiceChooserActivity#onCreateDialog()},
+ * code in method {@link UploadServiceChooserActivity#onCreateDialog(int)},
* {@link UploadServiceChooserActivity#updateStateBySendRequest()} and
* {@link UploadServiceChooserActivity#updateStateBySelection()}.
*/
@@ -120,7 +120,7 @@ public class UploadServiceChooserActivityTest extends
/**
* Tests the logic to control display none. This test cover code in method
- * {@link UploadServiceChooserActivity#onCreateDialog()},
+ * {@link UploadServiceChooserActivity#onCreateDialog(int)},
* {@link UploadServiceChooserActivity#updateStateBySendRequest()} and
* {@link UploadServiceChooserActivity#updateStateBySelection()}.
*/
@@ -135,7 +135,7 @@ public class UploadServiceChooserActivityTest extends
/**
* Tests the logic to initial state of check box to unchecked. This test cover
- * code in method {@link UploadServiceChooserActivity#onCreateDialog()},
+ * code in method {@link UploadServiceChooserActivity#onCreateDialog(int)},
* {@link UploadServiceChooserActivity#initState()}.
*/
public void testOnCreateDialog_initStateUnchecked() {
@@ -162,7 +162,7 @@ public class UploadServiceChooserActivityTest extends
/**
* Tests the logic to initial state of check box to checked. This test cover
- * code in method {@link UploadServiceChooserActivity#onCreateDialog()},
+ * code in method {@link UploadServiceChooserActivity#onCreateDialog(int)},
* {@link UploadServiceChooserActivity#initState()}.
*/
public void testOnCreateDialog_initStateChecked() {
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/util/StringUtilsTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/util/StringUtilsTest.java
index adbe6bbf4..cb698913c 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/util/StringUtilsTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/util/StringUtilsTest.java
@@ -15,68 +15,217 @@
*/
package com.google.android.apps.mytracks.util;
+import android.test.AndroidTestCase;
+
import java.util.GregorianCalendar;
import java.util.TimeZone;
-import junit.framework.TestCase;
-
/**
* Tests for {@link StringUtils}.
*
* @author Rodrigo Damazio
*/
-public class StringUtilsTest extends TestCase {
- public void testParseXmlDateTime() {
- assertParseXmlDateTime("2010-05-04T03:02:01",
- 2010, 5, 4, 3, 2, 1, 0);
+public class StringUtilsTest extends AndroidTestCase {
+
+ /**
+ * Tests {@link StringUtils#formatDateTime(android.content.Context, long)}.
+ */
+ public void testFormatTime() {
+ // Unix time 0 in UTC is "4:00 PM" in Pacific Standard time zone.
+ // This test can break if run on a different time zone or with a different
+ // time format preference.
+ assertEquals("4:00 PM", StringUtils.formatTime(getContext(), 0L));
}
- public void testParseXmlDateTime_fractional() {
- assertParseXmlDateTime("2010-05-04T03:02:01.3",
- 2010, 5, 4, 3, 2, 1, 300);
- assertParseXmlDateTime("2010-05-04T03:02:01.35",
- 2010, 5, 4, 3, 2, 1, 350);
- assertParseXmlDateTime("2010-05-04T03:02:01.352",
- 2010, 5, 4, 3, 2, 1, 352);
- assertParseXmlDateTime("2010-05-04T03:02:01.3525",
- 2010, 5, 4, 3, 2, 1, 352);
+ /**
+ * Tests {@link StringUtils#formatDateTime(android.content.Context, long)}.
+ */
+ public void testFormatDateTime() {
+ // Unix time 0 in UTC is "12/31/1969 4:00 PM" in Pacific Standard time zone.
+ // This test can break if run on a different time zone or with a different
+ // date/time format preference.
+ assertEquals("12/31/1969 4:00 PM", StringUtils.formatDateTime(getContext(), 0L));
+ }
+
+ /**
+ * Tests {@link StringUtils#formatDateTimeIso8601(long)}.
+ */
+ public void testFormatDateTimeIso8601() {
+ assertEquals("1970-01-01T00:00:12.345Z", StringUtils.formatDateTimeIso8601(12345));
}
- public void testParseXmlDateTime_timezone() {
- assertParseXmlDateTime("2010-05-04T03:02:01Z",
- 2010, 5, 4, 3, 2, 1, 0);
- assertParseXmlDateTime("2010-05-04T03:02:01+00:00",
- 2010, 5, 4, 3, 2, 1, 0);
- assertParseXmlDateTime("2010-05-04T03:02:01-00:00",
- 2010, 5, 4, 3, 2, 1, 0);
- assertParseXmlDateTime("2010-05-04T03:02:01+01:00",
- 2010, 5, 4, 2, 2, 1, 0);
- assertParseXmlDateTime("2010-05-04T03:02:01+10:30",
- 2010, 5, 3, 16, 32, 1, 0);
- assertParseXmlDateTime("2010-05-04T03:02:01-09:30",
- 2010, 5, 4, 12, 32, 1, 0);
- assertParseXmlDateTime("2010-05-04T03:02:01-05:00",
- 2010, 5, 4, 8, 2, 1, 0);
+ /**
+ * Tests {@link StringUtils#formatElapsedTime(long)}.
+ */
+ public void testformatElapsedTime() {
+ // 1 second
+ assertEquals("00:01", StringUtils.formatElapsedTime(1000));
+ // 10 seconds
+ assertEquals("00:10", StringUtils.formatElapsedTime(10000));
+ // 1 minute
+ assertEquals("01:00", StringUtils.formatElapsedTime(60000));
+ // 10 minutes
+ assertEquals("10:00", StringUtils.formatElapsedTime(600000));
+ // 1 hour
+ assertEquals("1:00:00", StringUtils.formatElapsedTime(3600000));
+ // 10 hours
+ assertEquals("10:00:00", StringUtils.formatElapsedTime(36000000));
+ // 100 hours
+ assertEquals("100:00:00", StringUtils.formatElapsedTime(360000000));
}
- public void testParseXmlDateTime_fractionalAndTimezone() {
- assertParseXmlDateTime("2010-05-04T03:02:01.352Z",
- 2010, 5, 4, 3, 2, 1, 352);
- assertParseXmlDateTime("2010-05-04T03:02:01.47+00:00",
- 2010, 5, 4, 3, 2, 1, 470);
- assertParseXmlDateTime("2010-05-04T03:02:01.5791+03:00",
- 2010, 5, 4, 0, 2, 1, 579);
- assertParseXmlDateTime("2010-05-04T03:02:01.8-05:30",
- 2010, 5, 4, 8, 32, 1, 800);
+ /**
+ * Tests {@link StringUtils#formatElapsedTimeWithHour(long)}.
+ */
+ public void testformatElapsedTimeWithHour() {
+ // 1 second
+ assertEquals("0:00:01", StringUtils.formatElapsedTimeWithHour(1000));
+ // 10 seconds
+ assertEquals("0:00:10", StringUtils.formatElapsedTimeWithHour(10000));
+ // 1 minute
+ assertEquals("0:01:00", StringUtils.formatElapsedTimeWithHour(60000));
+ // 10 minutes
+ assertEquals("0:10:00", StringUtils.formatElapsedTimeWithHour(600000));
+ // 1 hour
+ assertEquals("1:00:00", StringUtils.formatElapsedTimeWithHour(3600000));
+ // 10 hours
+ assertEquals("10:00:00", StringUtils.formatElapsedTimeWithHour(36000000));
+ // 100 hours
+ assertEquals("100:00:00", StringUtils.formatElapsedTimeWithHour(360000000));
}
- private void assertParseXmlDateTime(String dateTime,
- int year, int month, int day, int hour, int min, int second, int millis) {
- long timestamp = StringUtils.parseXmlDateTime(dateTime);
- GregorianCalendar calendar =
- new GregorianCalendar(TimeZone.getTimeZone("UTC"));
- calendar.set(year, month - 1, day, hour, min, second);
- calendar.set(GregorianCalendar.MILLISECOND, millis);
- assertEquals(calendar.getTimeInMillis(), timestamp);
+ /**
+ * Tests {@link StringUtils#formatDistance(android.content.Context, double,
+ * boolean)}.
+ */
+ public void testFormatDistance() {
+ // A large number in metric
+ assertEquals("5.00 km", StringUtils.formatDistance(getContext(), 5000, true));
+ // A large number in imperial
+ assertEquals("3.11 mi", StringUtils.formatDistance(getContext(), 5000, false));
+ // A small number in metric
+ assertEquals("100.00 m", StringUtils.formatDistance(getContext(), 100, true));
+ // A small number in imperial
+ assertEquals("328.08 ft", StringUtils.formatDistance(getContext(), 100, false));
+ }
+
+ /**
+ * Tests {@link StringUtils#formatSpeed(android.content.Context, double,
+ * boolean, boolean)}.
+ */
+ public void testFormatSpeed() {
+ // Speed in metric
+ assertEquals("36.00 km/h", StringUtils.formatSpeed(getContext(), 10, true, true));
+ // Speed in imperial
+ assertEquals("22.37 mi/h", StringUtils.formatSpeed(getContext(), 10, false, true));
+ // Pace in metric
+ assertEquals("1.67 min/km", StringUtils.formatSpeed(getContext(), 10, true, false));
+ // Pace in imperial
+ assertEquals("2.68 min/mi", StringUtils.formatSpeed(getContext(), 10, false, false));
+ // zero pace
+ assertEquals("0.00 min/km", StringUtils.formatSpeed(getContext(), 0, true, false));
+ assertEquals("0.00 min/mi", StringUtils.formatSpeed(getContext(), 0, false, false));
+ // speed is NaN
+ assertEquals("-", StringUtils.formatSpeed(getContext(), Double.NaN, true, true));
+ // speed is infinite
+ assertEquals("-", StringUtils.formatSpeed(getContext(), Double.NEGATIVE_INFINITY, true, true));
+ }
+
+ /**
+ * Tests {@link StringUtils#formatTimeDistance(android.content.Context, long, double, boolean)}.
+ */
+ public void testFormatTimeDistance() {
+ assertEquals("00:10 5.00 km", StringUtils.formatTimeDistance(getContext(), 10000, 5000, true));
+ }
+
+ /**
+ * Tests {@link StringUtils#formatCData(String)}.
+ */
+ public void testFormatCData() {
+ assertEquals("", StringUtils.formatCData("hello"));
+ assertEquals("there]]>", StringUtils.formatCData("hello]]>there"));
+ }
+
+ /**
+ * Tests {@link StringUtils#getTime(String)}.
+ */
+ public void testGetTime() {
+ assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
+ assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
+ }
+
+ /**
+ * Tests {@link StringUtils#getTime(String)} with fractional seconds.
+ */
+ public void testGetTime_fractional() {
+ assertGetTime("2010-05-04T03:02:01.3", 2010, 5, 4, 3, 2, 1, 300);
+ assertGetTime("2010-05-04T03:02:01.35", 2010, 5, 4, 3, 2, 1, 350);
+ assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
+ assertGetTime("2010-05-04T03:02:01.3525Z", 2010, 5, 4, 3, 2, 1, 352);
+ }
+
+ /**
+ * Tests {@link StringUtils#getTime(String)} with time zone.
+ */
+ public void testGetTime_timezone() {
+ assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
+ assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0);
+ assertGetTime("2010-05-04T03:02:01-00:00", 2010, 5, 4, 3, 2, 1, 0);
+ assertGetTime("2010-05-04T03:02:01+01:00", 2010, 5, 4, 2, 2, 1, 0);
+ assertGetTime("2010-05-04T03:02:01+10:30", 2010, 5, 3, 16, 32, 1, 0);
+ assertGetTime("2010-05-04T03:02:01-09:30", 2010, 5, 4, 12, 32, 1, 0);
+ assertGetTime("2010-05-04T03:02:01-05:00", 2010, 5, 4, 8, 2, 1, 0);
+ }
+
+ /**
+ * Tests {@link StringUtils#getTime(String)} with fractional seconds and time
+ * zone.
+ */
+ public void testGetTime_fractionalAndTimezone() {
+ assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
+ assertGetTime("2010-05-04T03:02:01.47+00:00", 2010, 5, 4, 3, 2, 1, 470);
+ assertGetTime("2010-05-04T03:02:01.5791+03:00", 2010, 5, 4, 0, 2, 1, 579);
+ assertGetTime("2010-05-04T03:02:01.8-05:30", 2010, 5, 4, 8, 32, 1, 800);
+ }
+
+ /**
+ * Asserts the {@link StringUtils#getTime(String)} returns the expected
+ * values.
+ *
+ * @param xmlDateTime the xml date time string
+ * @param year the expected year
+ * @param month the expected month
+ * @param day the expected day
+ * @param hour the expected hour
+ * @param minute the expected minute
+ * @param second the expected second
+ * @param millisecond the expected milliseconds
+ */
+ private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute,
+ int second, int millisecond) {
+ GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ calendar.set(year, month - 1, day, hour, minute, second);
+ calendar.set(GregorianCalendar.MILLISECOND, millisecond);
+ assertEquals(calendar.getTimeInMillis(), StringUtils.getTime(xmlDateTime));
+ }
+
+ /**
+ * Tests {@link StringUtils#getTimeParts(long)} with a positive number.
+ */
+ public void testGetTimeParts_postive() {
+ int parts[] = StringUtils.getTimeParts(61000);
+ assertEquals(1, parts[0]);
+ assertEquals(1, parts[1]);
+ assertEquals(0, parts[2]);
+ }
+
+ /**
+ * Tests {@link StringUtils#getTimeParts(long)} with a negative number.
+ */
+ public void testGetTimeParts_negative() {
+ int parts[] = StringUtils.getTimeParts(-61000);
+ assertEquals(-1, parts[0]);
+ assertEquals(-1, parts[1]);
+ assertEquals(0, parts[2]);
}
}