Changes according to comments

This commit is contained in:
Steffen Horlacher
2010-05-30 18:10:56 +02:00
parent 61884e67ec
commit 477ba7c081
6 changed files with 295 additions and 408 deletions
@@ -15,16 +15,24 @@
*/ */
package com.google.android.apps.mytracks; package com.google.android.apps.mytracks;
import java.io.File; import com.google.android.accounts.Account;
import java.io.FileInputStream; import com.google.android.apps.mymaps.MyMapsConstants;
import java.io.IOException; import com.google.android.apps.mymaps.MyMapsList;
import java.io.InputStream; import com.google.android.apps.mymaps.VersionChecker;
import java.util.HashMap; import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import java.util.Random; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import javax.xml.parsers.ParserConfigurationException; import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
import org.xml.sax.SAXException; import com.google.android.apps.mytracks.io.GpxImporter;
import com.google.android.apps.mytracks.io.SendToDocs;
import com.google.android.apps.mytracks.io.SendToMyMaps;
import com.google.android.apps.mytracks.io.TrackWriter;
import com.google.android.apps.mytracks.io.TrackWriterFactory;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.maps.mytracks.R;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
@@ -62,24 +70,16 @@ import android.widget.RelativeLayout;
import android.widget.TabHost; import android.widget.TabHost;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.accounts.Account; import java.io.File;
import com.google.android.apps.mymaps.MyMapsConstants; import java.io.FileInputStream;
import com.google.android.apps.mymaps.MyMapsList; import java.io.IOException;
import com.google.android.apps.mymaps.VersionChecker; import java.io.InputStream;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import java.util.HashMap;
import com.google.android.apps.mytracks.content.Track; import java.util.Random;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.AuthManager; import javax.xml.parsers.ParserConfigurationException;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.apps.mytracks.io.GpxSaxImporter; import org.xml.sax.SAXException;
import com.google.android.apps.mytracks.io.SendToDocs;
import com.google.android.apps.mytracks.io.SendToMyMaps;
import com.google.android.apps.mytracks.io.TrackWriter;
import com.google.android.apps.mytracks.io.TrackWriterFactory;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.maps.mytracks.R;
/** /**
* The super activity that embeds our sub activities. * The super activity that embeds our sub activities.
@@ -958,7 +958,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
try { try {
try { try {
InputStream is = new FileInputStream(fileName); InputStream is = new FileInputStream(fileName);
trackIdsImported = GpxSaxImporter.importGPXFile(is, providerUtils); trackIdsImported = GpxImporter.importGPXFile(is, providerUtils);
} catch (SAXException e) { } catch (SAXException e) {
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e); Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
message = R.string.error_generic; message = R.string.error_generic;
@@ -1,237 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import android.location.Location;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* Helper class to parse a GPX file or string and convert it into a track
* object.
*
* TODO: See if we can use a SAX style parser as the DOM style
* parsing uses too much memory and will not allow import of very large GPX
* files (limit currently set to 500KB).
*
* @author Leif Hendrik Wilden
*/
public class GpxImport {
private static final SimpleDateFormat DATE_FORMAT1 =
new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ");
private static final SimpleDateFormat DATE_FORMAT2 =
new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
private static final SimpleDateFormat DATE_FORMAT3 =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
/**
* Reads GPS tracks from a GPX file and append tracks and their coordinates to
* the given list of tracks.
*
* @param tracks a list of tracks
* @param filename a file name
* @throws SAXException a parsing error
* @throws ParserConfigurationException internal error
* @throws IOException a file reading problem
*/
public static void importGPXFile(
final String filename, final ArrayList<Track> tracks)
throws SAXException, ParserConfigurationException, IOException,
OutOfMemoryError {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
File file = new File(filename);
if (file.length() > 500 * 1024) {
// Better to throw an error now then, to let the VM do it. If the VM does
// it, it will throw another exception while throwing the OutOfMemoryError
// and that will kill out app with a nasty error message. In that way we
// can at least display a reasonable message to the user and let her know
// what the problem is.
throw new OutOfMemoryError();
}
Document doc = builder.parse(file);
importGPXDocument(tracks, doc);
}
/**
* This is a utility class with only static members.
*/
private GpxImport() {
}
/**
* Reads GPS tracks from a GPX document and append then to the given list of
* tracks.
*
* TODO: Break this method down into smaller ones
*
* @param tracks an array list of tracks
* @param doc a document
*/
public static void importGPXDocument(
final ArrayList<Track> tracks, final Document doc) {
NodeList trackNodes = doc.getElementsByTagName("trk");
for (int n = 0; n < trackNodes.getLength(); n++) {
Track track = new Track();
tracks.add(track);
ArrayList<Location> locations = track.getLocations();
Location lastLocation = null;
Node trkNode = trackNodes.item(n);
NodeList segmentNodes = trkNode.getChildNodes();
ArrayList<Node> nodes = new ArrayList<Node>();
int nSegments = 0;
for (int k = 0; k < segmentNodes.getLength(); k++) {
Node segmentNode = segmentNodes.item(k);
if (segmentNode.getNodeName().equals("name")) {
track.setName(segmentNode.getFirstChild().getNodeValue());
} else if (segmentNode.getNodeName().equals("description")) {
track.setDescription(segmentNode.getFirstChild().getNodeValue());
} else if (segmentNode.getNodeName().equals("trkseg")) {
if (nSegments > 0) {
// Add a segment separator:
Location location = new Location("gps");
location.setLatitude(100.0);
location.setLongitude(100.0);
location.setAltitude(0);
if (locations.size() > 0) {
long pointTime = locations.get(locations.size() - 1).getTime();
location.setTime(pointTime);
}
track.addLocation(location);
lastLocation = null;
}
nSegments++;
NodeList segmentChildren = segmentNode.getChildNodes();
nodes.clear();
for (int j = 0; j < segmentChildren.getLength(); j++) {
Node child = segmentChildren.item(j);
if (child.getNodeName().equals("trkpt")) {
nodes.add(child);
}
}
for (int i = 0; i < nodes.size(); i++) {
NamedNodeMap namedNodes = nodes.get(i).getAttributes();
double lat = Double.parseDouble(namedNodes.getNamedItem("lat")
.getNodeValue());
double lon = Double.parseDouble(namedNodes.getNamedItem("lon")
.getNodeValue());
NodeList children = nodes.get(i).getChildNodes();
Node elementNode = null;
Node timeNode = null;
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if (child.getNodeName().equals("ele")) {
elementNode = child;
} else if (child.getNodeName().equals("time")) {
timeNode = child;
}
}
String altitudeStr = null;
if (elementNode != null) {
altitudeStr = elementNode.getFirstChild().getNodeValue();
} else {
altitudeStr = "0";
}
String timeContents = null;
if (timeNode != null) {
timeContents = timeNode.getFirstChild().getNodeValue();
} else {
timeContents = "";
}
double altitude = Double.parseDouble(altitudeStr);
long t = -1;
try {
// 1st try with time zone at end a la "+0000"
t = DATE_FORMAT1.parse(timeContents).getTime();
} catch (ParseException e) {
// if that fails, try with a literal "Z" at the end (this is not
// according to xml standard, but some gpx files are like that):
try {
t = DATE_FORMAT2.parse(timeContents).getTime();
} catch (ParseException ex) {
// some gpx timestamps have 3 additional digits at the end.
try {
t = DATE_FORMAT3.parse(timeContents).getTime();
} catch (ParseException exc) {
t = 0;
}
}
}
Location location = new Location("gps");
location.setLatitude(lat);
location.setLongitude(lon);
location.setAltitude(altitude);
location.setTime(t);
// We don't have a speed and bearing in GPX, make something up from
// the last two points:
if (lastLocation != null) {
final long dt = location.getTime() - lastLocation.getTime();
if (dt > 0) {
final float speed =
location.distanceTo(lastLocation) / (dt / 1000);
location.setSpeed(speed);
}
location.setBearing(lastLocation.bearingTo(location));
}
lastLocation = location;
if (MyTracksUtils.isValidLocation(location)) {
track.addLocation(location);
}
}
if (locations.size() > 0) {
long startTime = locations.get(0).getTime();
track.setStartTime(startTime);
// Calculate statistics for the imported track
TripStatistics stats = new TripStatistics(startTime);
for (Location location : locations) {
if (MyTracksUtils.isValidLocation(location)) {
/* Any time works here. The totalTime will be set by "pauseAt" later: */
stats.addLocation(location, location.getTime());
}
}
long lastPointTime = locations.get(locations.size() - 1).getTime();
stats.pauseAt(lastPointTime);
track.setStopTime(lastPointTime);
stats.fillStatisticsForTrack(track);
}
}
}
}
}
}
@@ -15,48 +15,51 @@
*/ */
package com.google.android.apps.mytracks.io; package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.SimpleTimeZone;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.DefaultHandler;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.MyTracksUtils;
/** /**
* Imports GPX XML files to the my tracks provider * Imports GPX XML files to the my tracks provider
* *
* @author Leif Hendrik Wilden * @author Leif Hendrik Wilden
* @author Steffen (steffen.horlacher@gmail.com) * @author Steffen Horlacher
*/ */
public class GpxSaxImporter extends DefaultHandler { public class GpxImporter extends DefaultHandler {
/** /**
* Different data formats used in GPX files * Different date formats used in GPX files
*/ */
private static final SimpleDateFormat DATE_FORMAT1 = new SimpleDateFormat( static final SimpleDateFormat DATE_FORMAT1 = new SimpleDateFormat(
"yyyy-MM-dd'T'hh:mm:ssZ"); "yyyy-MM-dd'T'hh:mm:ssZ");
private static final SimpleDateFormat DATE_FORMAT2 = new SimpleDateFormat( static final SimpleDateFormat DATE_FORMAT2 = new SimpleDateFormat(
"yyyy-MM-dd'T'hh:mm:ss'Z'"); "yyyy-MM-dd'T'hh:mm:ss'Z'");
private static final SimpleDateFormat DATE_FORMAT3 = new SimpleDateFormat( static final SimpleDateFormat DATE_FORMAT3 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSSZ"); "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
static final SimpleTimeZone UTC_TIMEZONE = new SimpleTimeZone(0, "UTC");
/** /**
* GPX-XML tag names and attributes * GPX-XML tag names and attributes
*/ */
@@ -69,6 +72,14 @@ public class GpxSaxImporter extends DefaultHandler {
private static final String ATT_LAT = "lat"; private static final String ATT_LAT = "lat";
private static final String ATT_LON = "lon"; private static final String ATT_LON = "lon";
final private MyTracksProviderUtils providerUtils;
/**
* List of track ids written in the database. Only contains successfully
* written tracks.
*/
final private List<Long> tracksWritten;
/** /**
* Contains the current elements content * Contains the current elements content
*/ */
@@ -100,33 +111,31 @@ public class GpxSaxImporter extends DefaultHandler {
private int numberOfLocations; private int numberOfLocations;
/** /**
* List of track ids written in the database does only contain successful * Used to identify if a track was written to the database but not yet
* finished ones * finished successfully.
*/
private List<Long> tracksWritten;
/**
* used to identify if a track was written to the database but not yet finish
* successfully
*/ */
private boolean isCurrentTrackRollbackable; private boolean isCurrentTrackRollbackable;
private MyTracksProviderUtils providerUtils;
/** /**
* flag to indicate if we in a track xml element some sub elements like name * Flag to indicate if we in a track xml element some sub elements like name
* may be used in other parts of the gpx file - ignore them * may be used in other parts of the gpx file - ignore them
*/ */
private boolean isInTrackElement; private boolean isInTrackElement;
/** /**
* Reads GPS tracks from a GPX file and append tracks and their coordinates to * SAX-Locator to get current line information
* the given list of tracks. */
private Locator locator;
/**
* Reads GPS tracks from a GPX file and writes tracks and their coordinates to
* the database.
* *
* @param tracks * @param tracks
* a list of tracks * a list of tracks
* @param is * @param is
* a input steam with gpx-xml data * a input steam with gpx-xml data
* @return long[] array of track ids written in the database
* @throws SAXException * @throws SAXException
* a parsing error * a parsing error
* @throws ParserConfigurationException * @throws ParserConfigurationException
@@ -135,11 +144,11 @@ public class GpxSaxImporter extends DefaultHandler {
* a file reading problem * a file reading problem
*/ */
public static long[] importGPXFile(final InputStream is, public static long[] importGPXFile(final InputStream is,
final MyTracksProviderUtils providerUtils) final MyTracksProviderUtils providerUtils)
throws ParserConfigurationException, SAXException, IOException { throws ParserConfigurationException, SAXException, IOException {
SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParserFactory factory = SAXParserFactory.newInstance();
GpxSaxImporter handler = new GpxSaxImporter(providerUtils); GpxImporter handler = new GpxImporter(providerUtils);
SAXParser parser = factory.newSAXParser(); SAXParser parser = factory.newSAXParser();
long[] trackIds = null; long[] trackIds = null;
@@ -157,7 +166,7 @@ public class GpxSaxImporter extends DefaultHandler {
/** /**
* Constructor, requires providerUtils for writing tracks the database. * Constructor, requires providerUtils for writing tracks the database.
*/ */
public GpxSaxImporter(MyTracksProviderUtils providerUtils) { public GpxImporter(MyTracksProviderUtils providerUtils) {
this.providerUtils = providerUtils; this.providerUtils = providerUtils;
tracksWritten = new ArrayList<Long>(); tracksWritten = new ArrayList<Long>();
content = new StringBuilder(); content = new StringBuilder();
@@ -170,9 +179,19 @@ public class GpxSaxImporter extends DefaultHandler {
@Override @Override
public void startElement(String uri, String localName, String name, public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException { Attributes attributes) throws SAXException {
// reset element content
content.setLength(0);
if (localName.equalsIgnoreCase(TAG_TRACK)) { if (localName.equalsIgnoreCase(TAG_TRACK)) {
// test if we are already in a track element - abort in this case
if (isInTrackElement) {
String msg = createErrorMessage("Invalid GPX-XML detected");
throw new SAXException(msg);
}
isInTrackElement = true; isInTrackElement = true;
onTrackElementStart(); onTrackElementStart();
@@ -187,7 +206,7 @@ public class GpxSaxImporter extends DefaultHandler {
@Override @Override
public void endElement(String uri, String localName, String name) public void endElement(String uri, String localName, String name)
throws SAXException { throws SAXException {
if (localName.equalsIgnoreCase(TAG_TRACK)) { if (localName.equalsIgnoreCase(TAG_TRACK)) {
onTrackElementEnd(); onTrackElementEnd();
@@ -213,6 +232,11 @@ public class GpxSaxImporter extends DefaultHandler {
content.setLength(0); content.setLength(0);
} }
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
/** /**
* Create a new Track object and insert empty track in database. Track will be * Create a new Track object and insert empty track in database. Track will be
* updated with missing values later. * updated with missing values later.
@@ -267,8 +291,11 @@ public class GpxSaxImporter extends DefaultHandler {
/** /**
* Track point finished, write in database * Track point finished, write in database
*
* @throws SAXException
* - thrown if track point is invalid
*/ */
private void onTrackPointElementEnd() { private void onTrackPointElementEnd() throws SAXException {
if (MyTracksUtils.isValidLocation(location)) { if (MyTracksUtils.isValidLocation(location)) {
@@ -276,7 +303,7 @@ public class GpxSaxImporter extends DefaultHandler {
// insert in db // insert in db
Uri trackPointIdUri = providerUtils.insertTrackPoint(location, track Uri trackPointIdUri = providerUtils.insertTrackPoint(location, track
.getId()); .getId());
// set start and stop id for track // set start and stop id for track
long trackPointId = Long.parseLong(trackPointIdUri.getLastPathSegment()); long trackPointId = Long.parseLong(trackPointIdUri.getLastPathSegment());
@@ -291,6 +318,11 @@ public class GpxSaxImporter extends DefaultHandler {
lastLocation = location; lastLocation = location;
numberOfLocations++; numberOfLocations++;
} else {
// invalid location - abort import
String msg = createErrorMessage("Invalid location detected: " + location);
throw new SAXException(msg);
} }
} }
@@ -307,8 +339,9 @@ public class GpxSaxImporter extends DefaultHandler {
track.setNumberOfPoints(numberOfLocations); track.setNumberOfPoints(numberOfLocations);
stats.fillStatisticsForTrack(track); stats.fillStatisticsForTrack(track);
providerUtils.updateTrack(track); providerUtils.updateTrack(track);
tracksWritten.add(new Long(track.getId())); tracksWritten.add(track.getId());
isCurrentTrackRollbackable = false; isCurrentTrackRollbackable = false;
lastLocation = null;
} else { } else {
@@ -320,11 +353,14 @@ public class GpxSaxImporter extends DefaultHandler {
} }
/** /**
* setting time and doing additional calculations as this is the last value * Setting time and doing additional calculations as this is the last value
* required. Also sets the start time for track and statistics as there is no * required. Also sets the start time for track and statistics as there is no
* start time in the track root element * start time in the track root element
*
* @throws SAXException
* on parsing errors
*/ */
private void onTimeElementEnd() { private void onTimeElementEnd() throws SAXException {
long time = parseTimeForAllFormats(content.toString().trim()); long time = parseTimeForAllFormats(content.toString().trim());
@@ -337,9 +373,11 @@ public class GpxSaxImporter extends DefaultHandler {
track.setStartTime(time); track.setStartTime(time);
} }
// We don't have a speed and bearing in GPX, make // We don't have a speed and bearing in GPX, make something up from
// something up from // the last two points.
// the last two points: // TODO GPS points tend to have some inherent imprecision,
// speed and bearing will likely be off, so the statistics for things like
// max speed will also be off.
if (lastLocation != null) { if (lastLocation != null) {
final long dt = location.getTime() - lastLocation.getTime(); final long dt = location.getTime() - lastLocation.getTime();
if (dt > 0) { if (dt > 0) {
@@ -354,10 +392,7 @@ public class GpxSaxImporter extends DefaultHandler {
private void onAltitudeElementEnd() { private void onAltitudeElementEnd() {
if (location != null) { if (location != null) {
String altitude = content.toString().trim(); String altitude = content.toString().trim();
// make altitude optional location.setAltitude(Double.parseDouble(altitude));
if (altitude != null) {
location.setAltitude(Double.parseDouble(altitude));
}
} }
} }
@@ -391,33 +426,67 @@ public class GpxSaxImporter extends DefaultHandler {
* Parse time trying different formats used in GPX files * Parse time trying different formats used in GPX files
* *
* @param timeContents * @param timeContents
* string with time infomation * string with time information
* @return time as long * @return time as long
* @throws SAXException
* on time parsing errors
*/ */
private long parseTimeForAllFormats(String timeContents) { private long parseTimeForAllFormats(String timeContents) throws SAXException {
long t = -1; long time = -1;
try { // 1st try with time zone at end a la "+0000"
// 1st try with time zone at end a la "+0000" time = parseTime(timeContents, DATE_FORMAT1);
t = DATE_FORMAT1.parse(timeContents).getTime(); if(time > -1) {
} catch (ParseException e) { return time;
// if that fails, try with a literal "Z" at the end
// (this is not
// according to xml standard, but some gpx files are
// like that):
try {
t = DATE_FORMAT2.parse(timeContents).getTime();
} catch (ParseException ex) {
// some gpx timestamps have 3 additional digits
// at the end.
try {
t = DATE_FORMAT3.parse(timeContents).getTime();
} catch (ParseException exc) {
t = 0;
}
}
} }
return t;
// if that fails, try with a literal "Z" at the end
// (this is not according to xml standard, but some gpx files are like
// that):
time = parseTime(timeContents, DATE_FORMAT2);
if(time > -1) {
return time;
}
// some gpx timestamps have 3 additional digits at the end.
time = parseTime(timeContents, DATE_FORMAT3);
if(time > -1) {
return time;
}
// everything failed - abort the import
String msg = createErrorMessage("Invalid time format: " + timeContents);
throw new SAXException(msg);
} }
private long parseTime(String timeContents, SimpleDateFormat formatParam) {
SimpleDateFormat format = formatParam;
try {
format.setTimeZone(UTC_TIMEZONE);
return format.parse(timeContents).getTime();
} catch (ParseException ex) {
// do nothing
}
return -1;
}
/**
* Builds an parsing error message with current line information
*
* @param details
* details about the error, will be appended
* @return error message string with current line information
*/
private String createErrorMessage(String details) {
StringBuffer msg = new StringBuffer();
msg.append("Parsing error at line: ");
msg.append(locator.getLineNumber());
msg.append(" column: ");
msg.append(locator.getColumnNumber());
msg.append(". ");
msg.append(details);
return msg.toString();
}
} }
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,18 @@
package com.google.android.apps.mytracks.io; package com.google.android.apps.mytracks.io;
import static org.easymock.EasyMock.capture; import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import static org.easymock.EasyMock.expect; import com.google.android.apps.mytracks.content.Track;
import static org.easymock.EasyMock.expectLastCall; import com.google.android.apps.mytracks.content.TrackPointsColumns;
import static org.easymock.EasyMock.replay; import com.google.android.apps.mytracks.content.TracksColumns;
import static org.easymock.EasyMock.verify; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import android.content.ContentUris;
import android.location.Location;
import android.net.Uri;
import android.test.AndroidTestCase;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
@@ -14,33 +22,16 @@ import java.text.SimpleDateFormat;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import org.easymock.Capture; import org.easymock.Capture;
import org.easymock.EasyMock;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import android.content.ContentUris;
import android.location.Location;
import android.net.Uri;
import android.test.AndroidTestCase;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackPointsColumns;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
/** /**
* Tests for the GPX importer. * Tests for the GPX importer.
* *
* @author Steffen (steffen.horlacher@gmail.com) * @author Steffen Horlacher
*/ */
public class GpxImporterTest extends AndroidTestCase { public class GpxImporterTest extends AndroidTestCase {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
"yyyy-MM-dd'T'hh:mm:ss'Z'");
private static final String TRACK_NAME = "blablub"; private static final String TRACK_NAME = "blablub";
private static final String TRACK_DESC = "s'Laebe isch koi Schlotzer"; private static final String TRACK_DESC = "s'Laebe isch koi Schlotzer";
private static final String TRACK_LAT_1 = "48.768364"; private static final String TRACK_LAT_1 = "48.768364";
@@ -55,35 +46,44 @@ public class GpxImporterTest extends AndroidTestCase {
// TODO use real files from different sources with more track points // TODO use real files from different sources with more track points
private static final String VALID_TEST_GPX = "<gpx><trk><name><![CDATA[" private static final String VALID_TEST_GPX = "<gpx><trk><name><![CDATA["
+ TRACK_NAME + "]]></name><desc><![CDATA[" + TRACK_DESC + TRACK_NAME + "]]></name><desc><![CDATA[" + TRACK_DESC
+ "]]></desc><trkseg>" + "<trkpt lat=\"" + TRACK_LAT_1 + "\" lon=\"" + "]]></desc><trkseg>" + "<trkpt lat=\"" + TRACK_LAT_1 + "\" lon=\""
+ TRACK_LON_1 + "\"><ele>" + TRACK_ELE_1 + "</ele><time>" + TRACK_TIME_1 + TRACK_LON_1 + "\"><ele>" + TRACK_ELE_1 + "</ele><time>"
+ "</time></trkpt> +" + "<trkpt lat=\"" + TRACK_LAT_2 + "\" lon=\"" + TRACK_TIME_1 + "</time></trkpt> +" + "<trkpt lat=\"" + TRACK_LAT_2
+ TRACK_LON_2 + "\"><ele>" + TRACK_ELE_2 + "</ele><time>" + TRACK_TIME_2 + "\" lon=\"" + TRACK_LON_2 + "\"><ele>" + TRACK_ELE_2
+ "</time></trkpt>" + "</trkseg></trk></gpx>"; + "</ele><time>" + TRACK_TIME_2 + "</time></trkpt>"
+ "</trkseg></trk></gpx>";
// invalid xml // invalid xml
private static final String INVALID_TEST_GPX = VALID_TEST_GPX.substring(0, private static final String INVALID_XML_TEST_GPX = VALID_TEST_GPX
VALID_TEST_GPX.length() - 50); .substring(0, VALID_TEST_GPX.length() - 50);
private static final String INVALID_LOCATION_TEST_GPX = VALID_TEST_GPX
.replaceAll(TRACK_LAT_1, "1000.0");
private static final String INVALID_TIME_TEST_GPX = VALID_TEST_GPX
.replaceAll(TRACK_TIME_1, "invalid");
private static final long TRACK_ID = 1; private static final long TRACK_ID = 1;
private static final long TRACK_POINT_ID = 1; private static final long TRACK_POINT_ID_1 = 1;
private static final long TRACK_POINT_ID_2 = 1;
private static final Uri TRACK_ID_URI = ContentUris.appendId( private static final Uri TRACK_ID_URI = ContentUris.appendId(
TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID).build(); TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID).build();
private static final Uri TRACK_POINT_ID_URI = ContentUris.appendId( private static final Uri TRACK_POINT_ID_URI_1 = ContentUris.appendId(
TrackPointsColumns.CONTENT_URI.buildUpon(), TRACK_POINT_ID).build(); TrackPointsColumns.CONTENT_URI.buildUpon(), TRACK_POINT_ID_1).build();
private static final Uri TRACK_POINT_ID_URI_2 = ContentUris.appendId(
TrackPointsColumns.CONTENT_URI.buildUpon(), TRACK_POINT_ID_2).build();
private MyTracksProviderUtils providerUtils; private MyTracksProviderUtils providerUtils;
private Factory oldProviderUtilsFactory; private Factory oldProviderUtilsFactory;
@UsesMocks(MyTracksProviderUtils.class)
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
providerUtils = EasyMock.createMock(MyTracksProviderUtils.class); providerUtils = AndroidMock.createMock(MyTracksProviderUtils.class);
oldProviderUtilsFactory = TestingProviderUtilsFactory oldProviderUtilsFactory = TestingProviderUtilsFactory
.installWithInstance(providerUtils); .installWithInstance(providerUtils);
} }
@Override @Override
@@ -98,68 +98,123 @@ public class GpxImporterTest extends AndroidTestCase {
public void testImportSuccess() throws Exception { public void testImportSuccess() throws Exception {
Capture<Track> trackParam = new Capture<Track>(); Capture<Track> trackParam = new Capture<Track>();
Capture<Location> locParam = new Capture<Location>(); Capture<Location> locParam1 = new MyLocationCaptue();
Capture<Long> idParam = new Capture<Long>(); Capture<Location> locParam2 = new MyLocationCaptue();
expect(providerUtils.insertTrack(capture(trackParam))) AndroidMock.expect(
.andReturn(TRACK_ID_URI); providerUtils.insertTrack(AndroidMock.capture(trackParam)))
.andReturn(TRACK_ID_URI);
expect(providerUtils.insertTrackPoint(capture(locParam), capture(idParam))) AndroidMock.expect(
.andReturn(TRACK_POINT_ID_URI); providerUtils.insertTrackPoint(AndroidMock.capture(locParam1),
expectLastCall().times(2); AndroidMock.anyLong())).andReturn(TRACK_POINT_ID_URI_1);
providerUtils.updateTrack(capture(trackParam)); AndroidMock.expect(
providerUtils.insertTrackPoint(AndroidMock.capture(locParam2),
AndroidMock.anyLong())).andReturn(TRACK_POINT_ID_URI_2);
replay(providerUtils); providerUtils.updateTrack(AndroidMock.capture(trackParam));
AndroidMock.replay(providerUtils);
InputStream is = new ByteArrayInputStream(VALID_TEST_GPX.getBytes()); InputStream is = new ByteArrayInputStream(VALID_TEST_GPX.getBytes());
GpxSaxImporter.importGPXFile(is, providerUtils); GpxImporter.importGPXFile(is, providerUtils);
verify(); AndroidMock.verify();
SimpleDateFormat format = GpxImporter.DATE_FORMAT2;
// verify track parameter // verify track parameter
Track track = trackParam.getValue(); Track track = trackParam.getValue();
assertEquals(TRACK_NAME, track.getName()); assertEquals(TRACK_NAME, track.getName());
assertEquals(TRACK_DESC, track.getDescription()); assertEquals(TRACK_DESC, track.getDescription());
assertEquals(DATE_FORMAT.parse(TRACK_TIME_1).getTime(), track.getStartTime()); assertEquals(format.parse(TRACK_TIME_1).getTime(), track.getStartTime());
assertNotSame(-1, track.getStartId()); assertNotSame(-1, track.getStartId());
assertNotSame(-1, track.getStopId()); assertNotSame(-1, track.getStopId());
// verify last location parameter // verify last location parameter
Location loc = locParam.getValue(); Location loc1 = locParam1.getValue();
assertEquals(Double.parseDouble(TRACK_LAT_2), loc.getLatitude()); assertEquals(Double.parseDouble(TRACK_LAT_1), loc1.getLatitude());
assertEquals(Double.parseDouble(TRACK_LON_2), loc.getLongitude()); assertEquals(Double.parseDouble(TRACK_LON_1), loc1.getLongitude());
assertEquals(Double.parseDouble(TRACK_ELE_2), loc.getAltitude()); assertEquals(Double.parseDouble(TRACK_ELE_1), loc1.getAltitude());
assertEquals(DATE_FORMAT.parse(TRACK_TIME_2).getTime(), loc.getTime()); assertEquals(format.parse(TRACK_TIME_1).getTime(), loc1.getTime());
Location loc2 = locParam2.getValue();
assertEquals(Double.parseDouble(TRACK_LAT_2), loc2.getLatitude());
assertEquals(Double.parseDouble(TRACK_LON_2), loc2.getLongitude());
assertEquals(Double.parseDouble(TRACK_ELE_2), loc2.getAltitude());
assertEquals(format.parse(TRACK_TIME_2).getTime(), loc2.getTime());
} }
/** /**
* Test if created track will be deleted on parsing errors * Test with invalid location - track should be deleted
*/ */
public void testImportFailure() throws ParserConfigurationException, public void testImportLocationFailure() throws ParserConfigurationException,
SAXException, IOException { SAXException, IOException {
expect( testInvalidXML(INVALID_LOCATION_TEST_GPX);
providerUtils.insertTrack((Track) EasyMock.anyObject())).andReturn(TRACK_ID_URI); }
expect(
providerUtils.insertTrackPoint((Location) EasyMock.anyObject(),
EasyMock.anyLong())).andReturn(TRACK_POINT_ID_URI); /**
* Test with invalid time - track should be deleted
*/
public void testImportTimeFailure() throws ParserConfigurationException,
SAXException, IOException {
testInvalidXML(INVALID_TIME_TEST_GPX);
}
/**
* Test with invalid xml - track should be deleted
*/
public void testImportXMLFailure() throws ParserConfigurationException,
SAXException, IOException {
testInvalidXML(INVALID_XML_TEST_GPX);
}
private void testInvalidXML(String xml) throws ParserConfigurationException,
IOException {
AndroidMock.expect(
providerUtils.insertTrack((Track) AndroidMock.anyObject()))
.andReturn(TRACK_ID_URI);
AndroidMock.expect(
providerUtils.insertTrackPoint((Location) AndroidMock.anyObject(),
AndroidMock.anyLong())).andStubReturn(TRACK_POINT_ID_URI_1);
expectLastCall().anyTimes();
providerUtils.deleteTrack(TRACK_ID); providerUtils.deleteTrack(TRACK_ID);
replay(providerUtils); AndroidMock.replay(providerUtils);
InputStream is = new ByteArrayInputStream(INVALID_TEST_GPX.getBytes()); InputStream is = new ByteArrayInputStream(xml.getBytes());
try { try {
GpxSaxImporter.importGPXFile(is, providerUtils); GpxImporter.importGPXFile(is, providerUtils);
} catch (SAXException e) { } catch (SAXException e) {
// expected exception // expected exception
} }
verify(); AndroidMock.verify();
}
/**
* Workaround because of capture bug 2617107 in easymock:
*
* http://sourceforge.net/tracker/?func=detail&aid=2617107&group_id=82958&atid=567837
*/
@SuppressWarnings("serial")
class MyLocationCaptue extends Capture<Location> {
@Override
public void setValue(Location value) {
if (!hasCaptured()) {
super.setValue(value);
}
}
} }