Merge heads

This commit is contained in:
youtaol
2013-09-04 10:11:32 +08:00
39 changed files with 888 additions and 457 deletions
@@ -54,12 +54,12 @@ import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Abstract class for various importers like {@link GpxImporter} and
* {@link KmlImporter}.
* Abstract class for various file track importers like {@link GpxFileTrackImporter} and
* {@link KmlFileTrackImporter}.
*
* @author Jimmy Shih
*/
abstract class AbstractImporter extends DefaultHandler {
abstract class AbstractFileTrackImporter extends DefaultHandler implements TrackImporter {
/**
* Data for the current track.
@@ -131,12 +131,12 @@ abstract class AbstractImporter extends DefaultHandler {
* @param importTrackId the track id to import to. -1L to import to a new
* track.
*/
protected AbstractImporter(Context context, long importTrackId) {
protected AbstractFileTrackImporter(Context context, long importTrackId) {
this(context, importTrackId, MyTracksProviderUtils.Factory.get(context));
}
@VisibleForTesting
protected AbstractImporter(
protected AbstractFileTrackImporter(
Context context, long importTrackId, MyTracksProviderUtils myTracksProviderUtils) {
this.context = context;
this.importTrackId = importTrackId;
@@ -31,7 +31,7 @@ import org.xml.sax.SAXException;
*
* @author Jimmy Shih
*/
public class GpxImporter extends AbstractImporter {
public class GpxFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG_DESCRIPTION = "desc";
private static final String TAG_COMMENT = "cmt";
@@ -54,12 +54,12 @@ public class GpxImporter extends AbstractImporter {
* @param context the context
* @param importTrackId track id to import to. -1L to import to a new track.
*/
public GpxImporter(Context context, long importTrackId) {
public GpxFileTrackImporter(Context context, long importTrackId) {
super(context, importTrackId);
}
@VisibleForTesting
public GpxImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
public GpxFileTrackImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
super(context, -1L, myTracksProviderUtils);
}
@@ -158,10 +158,10 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
*/
private boolean importFile(final File file) {
try {
AbstractImporter importer = trackFileFormat == TrackFileFormat.KML ? new KmlImporter(
TrackImporter trackImporter = trackFileFormat == TrackFileFormat.KML ? new KmlFileTrackImporter(
context, -1L)
: new GpxImporter(context, -1L);
long trackIds[] = importer.importFile(new FileInputStream(file));
: new GpxFileTrackImporter(context, -1L);
long trackIds[] = trackImporter.importFile(new FileInputStream(file));
int length = trackIds.length;
if (length > 0) {
trackId = trackIds[length - 1];
@@ -36,7 +36,7 @@ import org.xml.sax.SAXException;
*
* @author Jimmy Shih
*/
public class KmlImporter extends AbstractImporter {
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private static final String CADENCE = "cadence";
private static final String HEART_RATE = "heart_rate";
@@ -61,6 +61,7 @@ public class KmlImporter extends AbstractImporter {
private static final String ATTRIBUTE_NAME = "name";
private boolean trackStarted = false;
private String sensorName;
private ArrayList<Location> locationList;
private ArrayList<Integer> cadenceList;
@@ -73,12 +74,12 @@ public class KmlImporter extends AbstractImporter {
* @param context the context
* @param importTrackId track id to import to. -1L to import to a new track.
*/
public KmlImporter(Context context, long importTrackId) {
public KmlFileTrackImporter(Context context, long importTrackId) {
super(context, importTrackId);
}
@VisibleForTesting
public KmlImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
public KmlFileTrackImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
super(context, -1L, myTracksProviderUtils);
}
@@ -88,8 +89,12 @@ public class KmlImporter extends AbstractImporter {
if (tag.equals(TAG_PLACEMARK)) {
onWaypointStart();
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
trackStarted = true;
onTrackStart();
} else if (tag.equals(TAG_GX_TRACK)) {
if (!trackStarted) {
throw new SAXException("No " + TAG_GX_MULTI_TRACK);
}
onTrackSegmentStart();
} else if (tag.equals(TAG_GX_SIMPLE_ARRAY_DATA)) {
onSensorDataStart(attributes);
@@ -1,5 +1,11 @@
package com.google.android.apps.mytracks.io.file;
import com.google.android.apps.mytracks.io.file.export.CsvTrackWriter;
import com.google.android.apps.mytracks.io.file.export.GpxTrackWriter;
import com.google.android.apps.mytracks.io.file.export.KmlTrackWriter;
import com.google.android.apps.mytracks.io.file.export.TcxTrackWriter;
import com.google.android.apps.mytracks.io.file.export.TrackWriter;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
@@ -12,25 +18,25 @@ import java.util.Locale;
public enum TrackFileFormat implements Parcelable {
KML {
@Override
TrackFormatWriter newFormatWriter(Context context) {
return new KmlTrackWriter(context);
public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new KmlTrackWriter(context, inZip);
}
},
GPX {
@Override
TrackFormatWriter newFormatWriter(Context context) {
public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new GpxTrackWriter(context);
}
},
CSV {
@Override
public TrackFormatWriter newFormatWriter(Context context) {
public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new CsvTrackWriter(context);
}
},
TCX {
@Override
public TrackFormatWriter newFormatWriter(Context context) {
public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new TcxTrackWriter(context);
}
};
@@ -58,9 +64,9 @@ public enum TrackFileFormat implements Parcelable {
};
/**
* Creates and returns a new format writer for each format.
* Creates a new track writer for the format.
*/
abstract TrackFormatWriter newFormatWriter(Context context);
public abstract TrackWriter newTrackWriter(Context context, boolean inZip);
/**
* Returns the mime type for each format.
@@ -0,0 +1,34 @@
/*
* Copyright 2013 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 java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* Interface for a track importer.
*
* @author Jimmy Shih
*/
public interface TrackImporter {
public long[] importFile(InputStream inputStream)
throws SAXException, IOException, ParserConfigurationException;
}
@@ -0,0 +1,68 @@
/*
* Copyright 2013 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.export;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
/**
* Abstract class for {@link TrackExporter}.
*
* @author Jimmy Shih
*/
abstract class AbstractTrackExporter implements TrackExporter {
private static final String TAG = AbstractTrackExporter.class.getSimpleName();
private Thread writeThread;
private boolean success = false;
@Override
public void writeTrack(final OutputStream outputStream) {
writeThread = new Thread() {
@Override
public void run() {
try {
performWrite(outputStream);
success = true;
} catch (InterruptedException e) {
Log.e(TAG, "Unable to perform write", e);
success = false;
} catch (IOException e) {
Log.e(TAG, "Unable to perform write", e);
success = false;
}
}
};
writeThread.start();
try {
writeThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for write thread to finish", e);
success = false;
}
}
@Override
public boolean isSuccess() {
return success;
}
abstract void performWrite(OutputStream outputStream) throws InterruptedException, IOException;
}
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
@@ -21,6 +21,7 @@ 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.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -48,7 +49,7 @@ import java.text.NumberFormat;
*
* @author Rodrigo Damazio
*/
public class CsvTrackWriter implements TrackFormatWriter {
public class CsvTrackWriter implements TrackWriter {
private static final NumberFormat SHORT_FORMAT = NumberFormat.getInstance();
@@ -77,11 +78,6 @@ public class CsvTrackWriter implements TrackFormatWriter {
pointIndex = 0;
}
@Override
public void close() {
printWriter.close();
}
@Override
public void writeHeader(Track track) {
writeCommaSeparatedLine(context.getString(R.string.generic_name),
@@ -14,7 +14,7 @@
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksLocation;
@@ -22,13 +22,13 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import java.io.OutputStream;
@@ -38,32 +38,12 @@ import java.io.OutputStream;
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class TrackWriter {
/**
* Listener for when a track location is written.
*/
public interface OnWriteListener {
/**
* When a track location is written.
*
* @param number the location number
* @param max the maximum number of locations in a track, for calculation of completion
* percentage
*/
public void onWrite(int number, int max);
}
private final static String TAG = TrackWriter.class.getSimpleName();
public class FileTrackExporter extends AbstractTrackExporter {
private final MyTracksProviderUtils myTracksProviderUtils;
private final Track[] tracks;
private final TrackFormatWriter trackFormatWriter;
private final OnWriteListener onWriteListener;
private Thread writeThread;
private boolean success = false;
private final TrackWriter trackWriter;
private final TrackExporterListener trackExporterListener;
/**
* Constructor.
@@ -72,86 +52,41 @@ public class TrackWriter {
* @param myTracksProviderUtils the my tracks provider utils
* @param tracks the tracks
* @param trackFileFormat the track file format
* @param onWriteListener the on write listener
* @param trackExporterListener the track export listener
*/
public TrackWriter(Context context, MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
TrackFileFormat trackFileFormat, OnWriteListener onWriteListener) {
this(myTracksProviderUtils, tracks, trackFileFormat.newFormatWriter(context), onWriteListener);
public FileTrackExporter(MyTracksProviderUtils myTracksProviderUtils,
Track[] tracks, TrackFileFormat trackFileFormat, Context context, boolean inZip,
TrackExporterListener trackExporterListener) {
this(myTracksProviderUtils, tracks, trackFileFormat.newTrackWriter(context, inZip),
trackExporterListener);
}
@VisibleForTesting
public TrackWriter(MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
TrackFormatWriter trackFormatWriter, OnWriteListener onWriteListener) {
public FileTrackExporter(MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
TrackWriter trackWriter, TrackExporterListener trackExporterListener) {
this.myTracksProviderUtils = myTracksProviderUtils;
this.tracks = tracks;
this.trackFormatWriter = trackFormatWriter;
this.onWriteListener = onWriteListener;
}
/**
* Returns true if the write completed successfully.
*/
public boolean wasSuccess() {
return success;
this.trackWriter = trackWriter;
this.trackExporterListener = trackExporterListener;
}
/**
* Writes the given track to the output stream.
*
* @param outputStream the output stream.
*/
public void writeTrack(final OutputStream outputStream) {
writeThread = new Thread() {
@Override
public void run() {
try {
trackFormatWriter.prepare(outputStream);
trackFormatWriter.writeHeader(tracks[0]);
long startTime = tracks[0].getTripStatistics().getStartTime();
for (int i = 0; i < tracks.length; i++) {
writeWaypoints(tracks[i]);
long offset = tracks[i].getTripStatistics().getStartTime() - startTime;
writeLocations(tracks[i], offset);
}
trackFormatWriter.writeFooter();
trackFormatWriter.close();
success = true;
} catch (InterruptedException e) {
success = false;
}
}
};
writeThread.start();
try {
writeThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for write to complete", e);
success = false;
}
}
/**
* Stops any in-progress writes.
*/
public void stopWriteTrack() {
if (writeThread != null && writeThread.isAlive()) {
Log.i(TAG, "Attempting to stop track write");
writeThread.interrupt();
try {
writeThread.join();
Log.i(TAG, "Track write stopped");
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for writer to stop", e);
success = false;
}
@Override
void performWrite(OutputStream outputStream) throws InterruptedException {
trackWriter.prepare(outputStream);
trackWriter.writeHeader(tracks[0]);
long startTime = tracks[0].getTripStatistics().getStartTime();
for (int i = 0; i < tracks.length; i++) {
writeWaypoints(tracks[i]);
long offset = tracks[i].getTripStatistics().getStartTime() - startTime;
writeLocations(tracks[i], offset);
}
trackWriter.writeFooter();
}
/**
* Writes the waypoints.
*/
private void writeWaypoints(Track track) {
private void writeWaypoints(Track track) throws InterruptedException {
/*
* TODO: Stream through the waypoints in chunks. I am leaving the number of
* waypoints very high which should not be a problem because we don't try to
@@ -168,12 +103,15 @@ public class TrackWriter {
* first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (!hasWaypoints) {
trackFormatWriter.writeBeginWaypoints();
trackWriter.writeBeginWaypoints();
hasWaypoints = true;
}
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
trackFormatWriter.writeWaypoint(waypoint);
trackWriter.writeWaypoint(waypoint);
}
}
} finally {
@@ -182,7 +120,7 @@ public class TrackWriter {
}
}
if (hasWaypoints) {
trackFormatWriter.writeEndWaypoints();
trackWriter.writeEndWaypoints();
}
}
@@ -196,62 +134,63 @@ public class TrackWriter {
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
try {
int locationNumber = 0;
while (iterator.hasNext()) {
Location location = iterator.next();
setLocationTime(location, offset);
if (Thread.interrupted()) {
throw new InterruptedException();
}
Location location = iterator.next();
setLocationTime(location, offset);
locationNumber++;
boolean isLocationValid = LocationUtils.isValidLocation(location);
boolean isSegmentValid = isLocationValid && isLastLocationValid;
if (!wroteTrack && isSegmentValid) {
// Found the first two consecutive locations that are valid
trackFormatWriter.writeBeginTrack(track, locationFactory.lastLocation);
// Found the first two consecutive locations that are valid
trackWriter.writeBeginTrack(track, locationFactory.lastLocation);
wroteTrack = true;
}
if (isSegmentValid) {
if (!wroteSegment) {
// Start a segment
trackFormatWriter.writeOpenSegment();
trackWriter.writeOpenSegment();
wroteSegment = true;
// Write the previous location, which we had previously skipped
trackFormatWriter.writeLocation(locationFactory.lastLocation);
trackWriter.writeLocation(locationFactory.lastLocation);
}
// Write the current location
trackFormatWriter.writeLocation(location);
if (onWriteListener != null) {
onWriteListener.onWrite(locationNumber, track.getNumberOfPoints());
trackWriter.writeLocation(location);
if (trackExporterListener != null) {
trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints());
}
} else {
if (wroteSegment) {
trackFormatWriter.writeCloseSegment();
trackWriter.writeCloseSegment();
wroteSegment = false;
}
}
locationFactory.swapLocations();
isLastLocationValid = isLocationValid;
}
if (wroteSegment) {
trackFormatWriter.writeCloseSegment();
trackWriter.writeCloseSegment();
wroteSegment = false;
}
if (wroteTrack) {
Location lastValidTrackPoint = myTracksProviderUtils.getLastValidTrackPoint(track.getId());
setLocationTime(lastValidTrackPoint, offset);
trackFormatWriter.writeEndTrack(track, lastValidTrackPoint);
trackWriter.writeEndTrack(track, lastValidTrackPoint);
} else {
// Write an empty track
trackFormatWriter.writeBeginTrack(track, null);
trackFormatWriter.writeEndTrack(track, null);
trackWriter.writeBeginTrack(track, null);
trackWriter.writeEndTrack(track, null);
}
} finally {
iterator.close();
@@ -269,7 +208,7 @@ public class TrackWriter {
location.setTime(location.getTime() - offset);
}
}
/**
* Track writer location factory. Keeping the last two locations.
*
@@ -14,10 +14,11 @@
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -34,7 +35,7 @@ import java.util.Locale;
*
* @author Sandor Dornbush
*/
public class GpxTrackWriter implements TrackFormatWriter {
public class GpxTrackWriter implements TrackWriter {
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US);
@@ -68,14 +69,6 @@ public class GpxTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(outputStream);
}
@Override
public void close() {
if (printWriter != null) {
printWriter.close();
printWriter = null;
}
}
@Override
public void writeHeader(Track track) {
if (printWriter != null) {
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
@@ -25,6 +25,7 @@ import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.GoogleEarthUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -35,6 +36,7 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import java.io.File;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -44,7 +46,7 @@ import java.util.ArrayList;
*
* @author Leif Hendrik Wilden
*/
public class KmlTrackWriter implements TrackFormatWriter {
public class KmlTrackWriter implements TrackWriter {
private static final String WAYPOINT_STYLE = "waypoint";
private static final String STATISTICS_STYLE = "statistics";
@@ -68,8 +70,10 @@ public class KmlTrackWriter implements TrackFormatWriter {
TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private final Context context;
private final boolean inKmz;
private final DescriptionGenerator descriptionGenerator;
private final MyTracksProviderUtils myTracksProviderUtils;
private PrintWriter printWriter;
private ArrayList<Integer> powerList = new ArrayList<Integer>();
private ArrayList<Integer> cadenceList = new ArrayList<Integer>();
@@ -78,13 +82,14 @@ public class KmlTrackWriter implements TrackFormatWriter {
private boolean hasCadence;
private boolean hasHeartRate;
public KmlTrackWriter(Context context) {
this(context, new DescriptionGeneratorImpl(context));
public KmlTrackWriter(Context context, boolean inKmz) {
this(context, inKmz, new DescriptionGeneratorImpl(context));
}
@VisibleForTesting
KmlTrackWriter(Context context, DescriptionGenerator descriptionGenerator) {
KmlTrackWriter(Context context, boolean inKmz, DescriptionGenerator descriptionGenerator) {
this.context = context;
this.inKmz = inKmz;
this.descriptionGenerator = descriptionGenerator;
this.myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
}
@@ -99,14 +104,6 @@ public class KmlTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(outputStream);
}
@Override
public void close() {
if (printWriter != null) {
printWriter.close();
printWriter = null;
}
}
@Override
public void writeHeader(Track track) {
if (printWriter != null) {
@@ -335,9 +332,8 @@ public class KmlTrackWriter implements TrackFormatWriter {
if (location != null) {
printWriter.println("<PhotoOverlay>");
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
printWriter.println("<description>"
+ StringUtils.formatCData(StringUtils.getCategoryDescription(category, description))
+ "</description>");
printWriter.println(
"<description>" + StringUtils.formatCData(description) + "</description>");
printWriter.print("<Camera>");
printWriter.print("<longitude>" + location.getLongitude() + "</longitude>");
printWriter.print("<latitude>" + location.getLatitude() + "</latitude>");
@@ -345,8 +341,18 @@ public class KmlTrackWriter implements TrackFormatWriter {
printWriter.print("<heading>" + heading + "</heading>");
printWriter.print("<tilt>90</tilt>");
printWriter.println("</Camera>");
printWriter.println("<TimeStamp><when>"
+ StringUtils.formatDateTimeIso8601(location.getTime()) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
printWriter.println("<Icon><href>" + Uri.decode(photoUrl) + "</href></Icon>");
writeCategory(category);
if (inKmz) {
Uri uri = Uri.parse(photoUrl);
printWriter.println("<Icon><href>" + KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar
+ uri.getLastPathSegment() + "</href></Icon>");
} else {
printWriter.println("<Icon><href>" + Uri.decode(photoUrl) + "</href></Icon>");
}
printWriter.print("<ViewVolume>");
printWriter.print("<near>10</near>");
printWriter.print("<leftFov>-60</leftFov>");
@@ -0,0 +1,143 @@
/*
* Copyright 2013 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.export;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import android.database.Cursor;
import android.net.Uri;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* KMZ track exporter.
*
* @author Jimmy Shih
*/
public class KmzTrackExporter extends AbstractTrackExporter {
public static final String KMZ_IMAGES_DIR = "images";
public static final String KMZ_EXTENSION = "kmz";
public static final int BUFFER_SIZE = 4096;
public static final String KML_FILE_NAME = "doc.kml";
private final MyTracksProviderUtils myTracksProviderUtils;
private final FileTrackExporter fileTrackExporter;
private final Track[] tracks;
/**
* Constructor.
*
* @param myTracksProviderUtils the my tracks provider utils
* @param fileTrackExporter the file track exporter
* @param tracks the tracks to export
*/
public KmzTrackExporter(MyTracksProviderUtils myTracksProviderUtils,
FileTrackExporter fileTrackExporter, Track[] tracks) {
this.myTracksProviderUtils = myTracksProviderUtils;
this.fileTrackExporter = fileTrackExporter;
this.tracks = tracks;
}
@Override
public boolean isSuccess() {
return fileTrackExporter.isSuccess() && super.isSuccess();
}
@Override
void performWrite(OutputStream outputStream) throws InterruptedException, IOException {
ZipOutputStream zipOutputStream = null;
try {
zipOutputStream = new ZipOutputStream(outputStream);
// Add kml file
ZipEntry zipEntry = new ZipEntry(KML_FILE_NAME);
zipOutputStream.putNextEntry(zipEntry);
fileTrackExporter.writeTrack(zipOutputStream);
zipOutputStream.closeEntry();
if (!fileTrackExporter.isSuccess()) {
throw new IOException();
}
// Add photos
addImages(zipOutputStream);
} finally {
if (zipOutputStream != null) {
zipOutputStream.close();
}
}
}
private void addImages(ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
for (Track track : tracks) {
Cursor cursor = null;
try {
cursor = myTracksProviderUtils.getWaypointCursor(track.getId(), -1L, -1);
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as
* the first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
String photoUrl = waypoint.getPhotoUrl();
if (photoUrl != null && !photoUrl.equals("")) {
addImage(zipOutputStream, photoUrl);
}
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}
private void addImage(ZipOutputStream zipOutputStream, String photoUrl) throws IOException {
Uri uri = Uri.parse(photoUrl);
ZipEntry zipEntry = new ZipEntry(
KMZ_IMAGES_DIR + File.separatorChar + uri.getLastPathSegment());
zipOutputStream.putNextEntry(zipEntry);
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(new File(uri.getPath()));
byte[] buffer = new byte[BUFFER_SIZE];
int byteCount = 0;
while ((byteCount = fileInputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, byteCount);
}
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
}
zipOutputStream.closeEntry();
}
}
@@ -14,8 +14,9 @@
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.GoogleEarthUtils;
@@ -99,7 +100,7 @@ public class SaveActivity extends Activity {
saveAsyncTask = (SaveAsyncTask) retained;
saveAsyncTask.setActivity(this);
} else {
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackIds, directory);
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackIds, directory, playTrack);
saveAsyncTask.execute();
}
}
@@ -121,7 +122,6 @@ public class SaveActivity extends Activity {
public void onCancel(DialogInterface dialog) {
saveAsyncTask.cancel(true);
dialog.dismiss();
finish();
}
}, directoryDisplayName);
return progressDialog;
@@ -14,11 +14,12 @@
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
@@ -33,7 +34,7 @@ import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
/**
* Async Task to save tracks to the external storage.
@@ -48,11 +49,12 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private final TrackFileFormat trackFileFormat;
private final long[] trackIds;
private final File directory;
private final boolean playTrack;
private final Context context;
private final MyTracksProviderUtils myTracksProviderUtils;
private WakeLock wakeLock;
private TrackWriter trackWriter;
private TrackExporter trackExporter;
// true if the AsyncTask has completed
private boolean completed;
@@ -74,13 +76,15 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* @param trackIds the track ids to save. To save all, set to size 1 with
* trackIds[0] == -1L
* @param directory the directory to save to
* @param playTrack true to play track
*/
public SaveAsyncTask(
SaveActivity saveActivity, TrackFileFormat trackFileFormat, long[] trackIds, File directory) {
public SaveAsyncTask(SaveActivity saveActivity, TrackFileFormat trackFileFormat, long[] trackIds,
File directory, boolean playTrack) {
this.saveActivity = saveActivity;
this.trackFileFormat = trackFileFormat;
this.trackIds = trackIds;
this.directory = directory;
this.playTrack = playTrack;
context = saveActivity.getApplicationContext();
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
@@ -163,8 +167,9 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onCancelled() {
if (trackWriter != null) {
trackWriter.stopWriteTrack();
completed = true;
if (saveActivity != null) {
saveActivity.onAsyncTaskCompleted(successCount, totalCount, null);
}
}
@@ -178,18 +183,21 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
return false;
}
Track track = tracks[0];
boolean useKmz = !playTrack && trackFileFormat == TrackFileFormat.KML;
String extension = useKmz ? KmzTrackExporter.KMZ_EXTENSION : trackFileFormat.getExtension();
// Make sure the file doesn't exist yet (possibly by changing the filename)
String fileName = FileUtils.buildUniqueFileName(
directory, track.getName(), trackFileFormat.getExtension());
String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), extension);
if (fileName == null) {
Log.d(TAG, "Unable to get a unique filename for " + track.getName());
return false;
}
trackWriter = new TrackWriter(
context, myTracksProviderUtils, tracks, trackFileFormat, new TrackWriter.OnWriteListener() {
FileTrackExporter fileTrackExporter = new FileTrackExporter(myTracksProviderUtils, tracks,
trackFileFormat, context, useKmz, new TrackExporterListener() {
@Override
public void onWrite(int number, int max) {
public void onProgressUpdate(int number, int max) {
/*
* If only saving one track, update the progress dialog once every
* 500 points
@@ -200,24 +208,36 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
});
File file = null;
try {
file = new File(directory, fileName);
OutputStream outputStream = new FileOutputStream(file);
trackWriter.writeTrack(outputStream);
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found " + fileName, e);
return false;
}
trackExporter = useKmz ? new KmzTrackExporter(myTracksProviderUtils, fileTrackExporter, tracks)
: fileTrackExporter;
if (trackWriter.wasSuccess()) {
savedPath = file.getAbsolutePath();
} else {
if (!file.delete()) {
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
File file = new File(directory, fileName);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
trackExporter.writeTrack(fileOutputStream);
if (trackExporter.isSuccess()) {
savedPath = file.getAbsolutePath();
return true;
} else {
if (!file.delete()) {
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
}
return false;
}
} catch (FileNotFoundException e) {
Log.e(TAG, "Unable to open file " + file.getName(), e);
return false;
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close file output stream", e);
}
}
}
return trackWriter.wasSuccess();
}
/**
@@ -13,13 +13,14 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
@@ -40,7 +41,7 @@ import java.util.Locale;
* @author Sandor Dornbush
* @author Dominik Ršttsches
*/
public class TcxTrackWriter implements TrackFormatWriter {
public class TcxTrackWriter implements TrackWriter {
/**
* TCX sport type. See the TCX spec.
@@ -96,14 +97,6 @@ public class TcxTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(out);
}
@Override
public void close() {
if (printWriter != null) {
printWriter.close();
printWriter = null;
}
}
@Override
public String getExtension() {
return TrackFileFormat.TCX.getExtension();
@@ -0,0 +1,40 @@
/*
* Copyright 2011 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.export;
import java.io.OutputStream;
/**
* Track exporting for exporting track to an {@link OutputStream}.
*
* @author Jimmy Shih
*/
public interface TrackExporter {
/**
* Write track to an output stream
*
* @param outputStream the output stream
*/
void writeTrack(OutputStream outputStream);
/**
* Returns true if {@link #writeTrack(OutputStream)} completed successfully.
*/
boolean isSuccess();
}
@@ -0,0 +1,33 @@
/*
* 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.export;
/**
* Listener for {@link TrackExporter} progress.
*
* @author Jimmy Shih
*/
public interface TrackExporterListener {
/**
* Called to update progress.
*
* @param number the number of locations written
* @param max the maximum number of locations in a track, for calculation of
* completion percentage
*/
public void onProgressUpdate(int number, int max);
}
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
@@ -50,7 +50,7 @@ import java.io.OutputStream;
*
* @author Rodrigo Damazio
*/
public interface TrackFormatWriter {
public interface TrackWriter {
/**
* Gets the file extension (i.e. gpx, kml, ...)
@@ -64,11 +64,6 @@ public interface TrackFormatWriter {
*/
public void prepare(OutputStream outputStream);
/**
* Closes the underlying file handler.
*/
public void close();
/**
* Writes the header for a file
*
@@ -18,7 +18,7 @@ package com.google.android.apps.mytracks.io.sync;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.file.KmlImporter;
import com.google.android.apps.mytracks.io.file.KmlFileTrackImporter;
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.gms.auth.GoogleAuthException;
@@ -348,9 +348,9 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
if (driveFile != null) {
InputStream inputStream = downloadDriveFile(driveFile, true);
if (inputStream != null) {
KmlImporter kmlImporter = new KmlImporter(context, -1L);
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, -1L);
try {
long[] trackIds = kmlImporter.importFile(inputStream);
long[] trackIds = kmlFileTrackImporter.importFile(inputStream);
if (trackIds.length == 1) {
Track track = myTracksProviderUtils.getTrack(trackIds[0]);
if (track == null) {
@@ -494,9 +494,9 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
Log.e(TAG, "Unable to update track. Input stream is null for track " + track.getName());
return false;
}
KmlImporter kmlImporter = new KmlImporter(context, track.getId());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, track.getId());
try {
long[] trackIds = kmlImporter.importFile(inputStream);
long[] trackIds = kmlFileTrackImporter.importFile(inputStream);
if (trackIds.length == 1) {
Track newTrack = myTracksProviderUtils.getTrack(trackIds[0]);
if (newTrack == null) {
@@ -21,7 +21,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.io.file.TrackWriter;
import com.google.android.apps.mytracks.io.file.export.FileTrackExporter;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
@@ -441,15 +441,31 @@ public class SyncUtils {
java.io.File file = new java.io.File(directory,
FileUtils.buildUniqueFileName(directory, track.getName(), trackFileFormat.getExtension()));
TrackWriter trackWriter = new TrackWriter(
context, myTracksProviderUtils, new Track[] {track}, trackFileFormat, null);
FileTrackExporter fileTrackExporter = new FileTrackExporter(
myTracksProviderUtils, new Track[] { track }, trackFileFormat, context, false, null);
trackWriter.writeTrack(new FileOutputStream(file));
if (trackWriter.wasSuccess()) {
return file;
}
Log.d(TAG, "Unable to get file for track " + track.getName());
return null;
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
fileTrackExporter.writeTrack(fileOutputStream);
if (fileTrackExporter.isSuccess()) {
return file;
} else {
if (!file.delete()) {
Log.d(TAG, "Unable to delete file for track " + track.getName());
}
Log.d(TAG, "Unable to get file for track " + track.getName());
return null;
}
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close file output stream", e);
}
}
}
}
/**