forked from upstream-mirrors/OpenTracks
Rebranding to OpenTracks (code).
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package de.dennisguse.opentracks.io.file;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackWriter;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Definition of all possible track formats.
|
||||
*/
|
||||
public enum TrackFileFormat implements Parcelable {
|
||||
KML {
|
||||
@Override
|
||||
public TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack) {
|
||||
return new KmlTrackWriter(context, multiple, playTrack);
|
||||
}
|
||||
},
|
||||
GPX {
|
||||
@Override
|
||||
public TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack) {
|
||||
return new GpxTrackWriter(context.getString(R.string.app_name));
|
||||
}
|
||||
};
|
||||
|
||||
public static final Creator<TrackFileFormat> CREATOR = new Creator<TrackFileFormat>() {
|
||||
@Override
|
||||
public TrackFileFormat createFromParcel(final Parcel source) {
|
||||
return TrackFileFormat.values()[source.readInt()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackFileFormat[] newArray(final int size) {
|
||||
return new TrackFileFormat[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeInt(ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new track writer for the format.
|
||||
*
|
||||
* @param context the context
|
||||
* @param multiple true for writing multiple tracks
|
||||
* @param playTrack true to play track
|
||||
*/
|
||||
public abstract TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack);
|
||||
|
||||
/**
|
||||
* Returns the mime type for each format.
|
||||
*/
|
||||
public String getMimeType() {
|
||||
return "application/" + getExtension() + "+xml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension for each format.
|
||||
*/
|
||||
public String getExtension() {
|
||||
return this.name().toLowerCase(Locale.US);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.Constants;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Track Writer for writing tracks to an {@link OutputStream}.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class FileTrackExporter implements TrackExporter {
|
||||
|
||||
private static final String TAG = FileTrackExporter.class.getSimpleName();
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final Track[] tracks;
|
||||
private final TrackWriter trackWriter;
|
||||
private final TrackExporterListener trackExporterListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param contentProviderUtils the content provider utils
|
||||
* @param tracks the tracks
|
||||
* @param trackWriter the track writer
|
||||
* @param trackExporterListener the track export listener
|
||||
*/
|
||||
public FileTrackExporter(ContentProviderUtils contentProviderUtils, Track[] tracks,
|
||||
TrackWriter trackWriter, TrackExporterListener trackExporterListener) {
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.tracks = tracks;
|
||||
this.trackWriter = trackWriter;
|
||||
this.trackExporterListener = trackExporterListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(OutputStream outputStream) {
|
||||
try {
|
||||
trackWriter.prepare(outputStream);
|
||||
trackWriter.writeHeader(tracks);
|
||||
for (Track track1 : tracks) {
|
||||
writeWaypoints(track1);
|
||||
}
|
||||
trackWriter.writeBeginTracks();
|
||||
long startTime = tracks[0].getTripStatistics().getStartTime();
|
||||
for (Track track : tracks) {
|
||||
long offset = track.getTripStatistics().getStartTime() - startTime;
|
||||
writeLocations(track, offset);
|
||||
}
|
||||
trackWriter.writeEndTracks();
|
||||
trackWriter.writeFooter();
|
||||
trackWriter.close();
|
||||
return true;
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Thread interrupted", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the waypoints.
|
||||
*/
|
||||
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
|
||||
* load them into objects all at the same time.
|
||||
*/
|
||||
boolean hasWaypoints = false;
|
||||
try (Cursor cursor = contentProviderUtils.getWaypointCursor(
|
||||
track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS)) {
|
||||
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();
|
||||
}
|
||||
if (!hasWaypoints) {
|
||||
trackWriter.writeBeginWaypoints(track);
|
||||
hasWaypoints = true;
|
||||
}
|
||||
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
|
||||
trackWriter.writeWaypoint(waypoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasWaypoints) {
|
||||
trackWriter.writeEndWaypoints();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the locations.
|
||||
*/
|
||||
private void writeLocations(Track track, long offset) throws InterruptedException {
|
||||
boolean wroteTrack = false;
|
||||
boolean wroteSegment = false;
|
||||
boolean isLastLocationValid = false;
|
||||
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
|
||||
int locationNumber = 0;
|
||||
LocationIterator locationIterator = null;
|
||||
|
||||
try {
|
||||
locationIterator = contentProviderUtils.getTrackPointLocationIterator(
|
||||
track.getId(), -1L, false, locationFactory);
|
||||
|
||||
while (locationIterator.hasNext()) {
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
Location location = locationIterator.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
|
||||
trackWriter.writeBeginTrack(track, locationFactory.lastLocation);
|
||||
wroteTrack = true;
|
||||
}
|
||||
|
||||
if (isSegmentValid) {
|
||||
if (!wroteSegment) {
|
||||
// Start a segment
|
||||
trackWriter.writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
|
||||
// Write the previous location, which we had previously skipped
|
||||
trackWriter.writeLocation(locationFactory.lastLocation);
|
||||
}
|
||||
|
||||
// Write the current location
|
||||
trackWriter.writeLocation(location);
|
||||
if (trackExporterListener != null) {
|
||||
trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints());
|
||||
}
|
||||
} else {
|
||||
if (wroteSegment) {
|
||||
trackWriter.writeCloseSegment();
|
||||
wroteSegment = false;
|
||||
}
|
||||
}
|
||||
locationFactory.swapLocations();
|
||||
isLastLocationValid = isLocationValid;
|
||||
}
|
||||
|
||||
if (wroteTrack) {
|
||||
Location lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId());
|
||||
setLocationTime(lastValidTrackPoint, offset);
|
||||
trackWriter.writeEndTrack(track, lastValidTrackPoint);
|
||||
} else {
|
||||
// Write an empty track
|
||||
trackWriter.writeBeginTrack(track, null);
|
||||
trackWriter.writeEndTrack(track, null);
|
||||
}
|
||||
} finally {
|
||||
if (locationIterator != null) {
|
||||
locationIterator.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a location time.
|
||||
*
|
||||
* @param location the location
|
||||
* @param offset the time offset
|
||||
*/
|
||||
private void setLocationTime(Location location, long offset) {
|
||||
if (location != null) {
|
||||
location.setTime(location.getTime() - offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track writer location factory. Keeping the last two locations.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private class TrackWriterLocationFactory implements ContentProviderUtils.LocationFactory {
|
||||
Location currentLocation;
|
||||
Location lastLocation;
|
||||
|
||||
@Override
|
||||
public Location createLocation() {
|
||||
if (currentLocation == null) {
|
||||
currentLocation = new SensorDataSetLocation("");
|
||||
}
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
public void swapLocations() {
|
||||
Location tempLocation = lastLocation;
|
||||
lastLocation = currentLocation;
|
||||
currentLocation = tempLocation;
|
||||
if (currentLocation != null) {
|
||||
currentLocation.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Write track as GPX to a file.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
//TODO Can we export Sensor.Data in GPX?
|
||||
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);
|
||||
|
||||
static {
|
||||
/*
|
||||
* GPX readers expect to see fractional numbers with US-style punctuation.
|
||||
* That is, they want periods for decimal points, rather than commas.
|
||||
*/
|
||||
ELEVATION_FORMAT.setMaximumFractionDigits(1);
|
||||
ELEVATION_FORMAT.setGroupingUsed(false);
|
||||
|
||||
COORDINATE_FORMAT.setMaximumFractionDigits(6);
|
||||
COORDINATE_FORMAT.setMaximumIntegerDigits(3);
|
||||
COORDINATE_FORMAT.setGroupingUsed(false);
|
||||
}
|
||||
|
||||
private final String creator;
|
||||
private PrintWriter printWriter;
|
||||
|
||||
public GpxTrackWriter(String creator) {
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return TrackFileFormat.GPX.getExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(OutputStream outputStream) {
|
||||
this.printWriter = new PrintWriter(outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (printWriter != null) {
|
||||
printWriter.flush();
|
||||
printWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeHeader(Track[] tracks) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
printWriter.println("<gpx");
|
||||
printWriter.println("version=\"1.1\"");
|
||||
printWriter.println(
|
||||
"creator=\"" + creator + "\"");
|
||||
printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\"");
|
||||
printWriter.println(
|
||||
"xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\"");
|
||||
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
||||
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1"
|
||||
+ " http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd\">");
|
||||
printWriter.println("<metadata>");
|
||||
Track track = tracks[0];
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||
printWriter.println("</metadata>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFooter() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</gpx>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginWaypoints(Track track) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndWaypoints() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeWaypoint(Waypoint waypoint) {
|
||||
if (printWriter != null) {
|
||||
Location location = waypoint.getLocation();
|
||||
if (location != null) {
|
||||
printWriter.println("<wpt " + formatLocation(location) + ">");
|
||||
if (location.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
|
||||
}
|
||||
printWriter.println(
|
||||
"<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(waypoint.getName()) + "</name>");
|
||||
printWriter.println("<cmt>" + StringUtils.formatCData(waypoint.getType().name()) + "</cmt>");
|
||||
printWriter.println(
|
||||
"<desc>" + StringUtils.formatCData(waypoint.getDescription()) + "</desc>");
|
||||
printWriter.println("<type>" + StringUtils.formatCData(waypoint.getCategory()) + "</type>");
|
||||
printWriter.println("</wpt>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTracks() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTracks() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTrack(Track track, Location startLocation) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<trk>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||
printWriter.println("<type>" + StringUtils.formatCData(track.getCategory()) + "</type>");
|
||||
printWriter.println("<extensions><topografix:color>c0c0c0</topografix:color></extensions>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTrack(Track track, Location endLocation) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</trk>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeOpenSegment() {
|
||||
printWriter.println("<trkseg>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeCloseSegment() {
|
||||
printWriter.println("</trkseg>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLocation(Location location) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<trkpt " + formatLocation(location) + ">");
|
||||
if (location.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
|
||||
}
|
||||
printWriter.println(
|
||||
"<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
|
||||
printWriter.println("</trkpt>");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a location with latitude and longitude coordinates.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
private String formatLocation(Location location) {
|
||||
return "lat=\"" + COORDINATE_FORMAT.format(location.getLatitude()) + "\" lon=\""
|
||||
+ COORDINATE_FORMAT.format(location.getLongitude()) + "\"";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.DescriptionGenerator;
|
||||
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Write track as KML to a file.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class KmlTrackWriter implements TrackWriter {
|
||||
|
||||
private static final String WAYPOINT_STYLE = "waypoint";
|
||||
private static final String STATISTICS_STYLE = "statistics";
|
||||
private static final String START_STYLE = "start";
|
||||
private static final String END_STYLE = "end";
|
||||
private static final String TRACK_STYLE = "track";
|
||||
private static final String SCHEMA_ID = "schema";
|
||||
private static final String CADENCE = "cadence";
|
||||
private static final String HEART_RATE = "heart_rate";
|
||||
private static final String POWER = "power";
|
||||
|
||||
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 boolean multiple;
|
||||
private final boolean playTrack;
|
||||
private final DescriptionGenerator descriptionGenerator;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private PrintWriter printWriter;
|
||||
private ArrayList<Float> powerList = new ArrayList<>();
|
||||
private ArrayList<Float> cadenceList = new ArrayList<>();
|
||||
private ArrayList<Float> heartRateList = new ArrayList<>();
|
||||
private boolean hasPower;
|
||||
private boolean hasCadence;
|
||||
private boolean hasHeartRate;
|
||||
|
||||
public KmlTrackWriter(Context context, boolean multiple, boolean playTrack) {
|
||||
this(context, multiple, playTrack, new DescriptionGeneratorImpl(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
KmlTrackWriter(Context context, boolean multiple, boolean playTrack, DescriptionGenerator descriptionGenerator) {
|
||||
this.context = context;
|
||||
this.multiple = multiple;
|
||||
this.playTrack = playTrack;
|
||||
this.descriptionGenerator = descriptionGenerator;
|
||||
this.contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return TrackFileFormat.KML.getExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(OutputStream outputStream) {
|
||||
this.printWriter = new PrintWriter(outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (printWriter != null) {
|
||||
printWriter.flush();
|
||||
printWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeHeader(Track[] tracks) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.2\"");
|
||||
printWriter.println("xmlns:gx=\"http://www.google.com/kml/ext/2.2\"");
|
||||
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\">");
|
||||
printWriter.println("<Document>");
|
||||
printWriter.println("<open>1</open>");
|
||||
printWriter.println("<visibility>1</visibility>");
|
||||
Track track = tracks[0];
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<atom:author><atom:name>"
|
||||
+ StringUtils.formatCData(context.getString(R.string.app_name))
|
||||
+ "</atom:name></atom:author>");
|
||||
writeTrackStyle();
|
||||
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("<Schema id=\"" + SCHEMA_ID + "\">");
|
||||
writeSensorStyle(POWER, context.getString(R.string.description_sensor_power));
|
||||
writeSensorStyle(CADENCE, context.getString(R.string.description_sensor_cadence));
|
||||
writeSensorStyle(HEART_RATE, context.getString(R.string.description_sensor_heart_rate));
|
||||
printWriter.println("</Schema>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFooter() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</Document>");
|
||||
printWriter.println("</kml>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginWaypoints(Track track) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<Folder><name>"
|
||||
+ StringUtils.formatCData(context.getString(R.string.track_markers, track.getName()))
|
||||
+ "</name>");
|
||||
printWriter.println("<open>1</open>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndWaypoints() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</Folder>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeWaypoint(Waypoint waypoint) {
|
||||
if (printWriter != null) {
|
||||
String styleName = waypoint.getType() == WaypointType.STATISTICS ? STATISTICS_STYLE : WAYPOINT_STYLE;
|
||||
if (waypoint.hasPhoto()) {
|
||||
float heading = getHeading(waypoint.getTrackId(), waypoint.getLocation());
|
||||
writePhotoOverlay(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), styleName, waypoint.getLocation(), waypoint.getPhotoUrl(), heading);
|
||||
} else {
|
||||
writePlacemark(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), styleName, waypoint.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTracks() {
|
||||
if (printWriter != null && multiple) {
|
||||
printWriter.println("<Folder id=tour>");
|
||||
printWriter.println("<name>" + context.getString(R.string.generic_tracks) + "</name>");
|
||||
printWriter.println("<open>1</open>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTracks() {
|
||||
if (printWriter != null && multiple) {
|
||||
printWriter.println("</Folder>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTrack(Track track, Location startLocation) {
|
||||
if (printWriter != null) {
|
||||
String name = context.getString(R.string.marker_label_start, track.getName());
|
||||
writePlacemark(name, "", "", START_STYLE, startLocation);
|
||||
printWriter.println("<Placemark>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println(
|
||||
"<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
|
||||
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
|
||||
writeCategory(track.getCategory());
|
||||
printWriter.println("<gx:MultiTrack>");
|
||||
printWriter.println("<altitudeMode>absolute</altitudeMode>");
|
||||
printWriter.println("<gx:interpolate>1</gx:interpolate>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTrack(Track track, Location endLocation) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</gx:MultiTrack>");
|
||||
printWriter.println("</Placemark>");
|
||||
String name = context.getString(R.string.marker_label_end, track.getName());
|
||||
String description = descriptionGenerator.generateTrackDescription(track, null, null, false);
|
||||
writePlacemark(name, "", description, END_STYLE, endLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeOpenSegment() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<gx:Track>");
|
||||
hasPower = false;
|
||||
hasCadence = false;
|
||||
hasHeartRate = false;
|
||||
powerList.clear();
|
||||
cadenceList.clear();
|
||||
heartRateList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeCloseSegment() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<ExtendedData>");
|
||||
printWriter.println("<SchemaData schemaUrl=\"#" + SCHEMA_ID + "\">");
|
||||
if (hasPower) {
|
||||
writeSensorData(powerList, POWER);
|
||||
}
|
||||
if (hasCadence) {
|
||||
writeSensorData(cadenceList, CADENCE);
|
||||
}
|
||||
if (hasHeartRate) {
|
||||
writeSensorData(heartRateList, HEART_RATE);
|
||||
}
|
||||
printWriter.println("</SchemaData>");
|
||||
printWriter.println("</ExtendedData>");
|
||||
printWriter.println("</gx:Track>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLocation(Location location) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println(
|
||||
"<when>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</when>");
|
||||
printWriter.println("<gx:coord>" + getCoordinates(location, " ") + "</gx:coord>");
|
||||
if (location instanceof SensorDataSetLocation) {
|
||||
SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet();
|
||||
if (sensorDataSet != null) {
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
heartRateList.add(sensorDataSet.getHeartRate());
|
||||
}
|
||||
if (sensorDataSet.hasCadence()) {
|
||||
cadenceList.add(sensorDataSet.getCadence());
|
||||
}
|
||||
if (sensorDataSet.hasPower()) {
|
||||
powerList.add(sensorDataSet.getPower());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the sensor data.
|
||||
*
|
||||
* @param list a list of sensor data
|
||||
* @param name the name of the sensor data
|
||||
*/
|
||||
private void writeSensorData(ArrayList<Float> list, String name) {
|
||||
printWriter.println("<gx:SimpleArrayData name=\"" + name + "\">");
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
|
||||
}
|
||||
printWriter.println("</gx:SimpleArrayData>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a placemark.
|
||||
*
|
||||
* @param name the name
|
||||
* @param category the category
|
||||
* @param description the description
|
||||
* @param styleName the style name
|
||||
* @param location the location
|
||||
*/
|
||||
private void writePlacemark(
|
||||
String name, String category, String description, String styleName, Location location) {
|
||||
if (location != null) {
|
||||
printWriter.println("<Placemark>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
||||
printWriter.println(
|
||||
"<description>" + StringUtils.formatCData(description) + "</description>");
|
||||
printWriter.println("<TimeStamp><when>"
|
||||
+ StringUtils.formatDateTimeIso8601(location.getTime()) + "</when></TimeStamp>");
|
||||
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
|
||||
writeCategory(category);
|
||||
printWriter.println("<Point>");
|
||||
printWriter.println("<coordinates>" + getCoordinates(location, ",") + "</coordinates>");
|
||||
printWriter.println("</Point>");
|
||||
printWriter.println("</Placemark>");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a photo overlay.
|
||||
*
|
||||
* @param name the name
|
||||
* @param category the category
|
||||
* @param description the description
|
||||
* @param styleName the style name
|
||||
* @param location the location
|
||||
* @param photoUrl the photo url
|
||||
* @param heading the heading
|
||||
*/
|
||||
private void writePhotoOverlay(String name, String category, String description, String styleName,
|
||||
Location location, String photoUrl, float heading) {
|
||||
if (location != null) {
|
||||
printWriter.println("<PhotoOverlay>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
||||
printWriter.println(
|
||||
"<description>" + StringUtils.formatCData(description) + "</description>");
|
||||
printWriter.print("<Camera>");
|
||||
printWriter.print("<longitude>" + location.getLongitude() + "</longitude>");
|
||||
printWriter.print("<latitude>" + location.getLatitude() + "</latitude>");
|
||||
printWriter.print("<altitude>20</altitude>");
|
||||
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>");
|
||||
writeCategory(category);
|
||||
if (playTrack) {
|
||||
printWriter.println("<Icon><href>" + Uri.decode(photoUrl) + "</href></Icon>");
|
||||
} else {
|
||||
Uri uri = Uri.parse(photoUrl);
|
||||
printWriter.println("<Icon><href>" + KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar
|
||||
+ uri.getLastPathSegment() + "</href></Icon>");
|
||||
}
|
||||
|
||||
printWriter.print("<ViewVolume>");
|
||||
printWriter.print("<near>10</near>");
|
||||
printWriter.print("<leftFov>-60</leftFov>");
|
||||
printWriter.print("<rightFov>60</rightFov>");
|
||||
printWriter.print("<bottomFov>-45</bottomFov>");
|
||||
printWriter.print("<topFov>45</topFov>");
|
||||
printWriter.println("</ViewVolume>");
|
||||
printWriter.println("<Point>");
|
||||
printWriter.println("<coordinates>" + getCoordinates(location, ",") + "</coordinates>");
|
||||
printWriter.println("</Point>");
|
||||
printWriter.println("</PhotoOverlay>");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the heading to a location.
|
||||
*
|
||||
* @param trackId the track id containing the location
|
||||
* @param location the location
|
||||
*/
|
||||
private float getHeading(long trackId, Location location) {
|
||||
long trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
|
||||
if (trackPointId == -1L) {
|
||||
return location.getBearing();
|
||||
}
|
||||
Location viewLocation;
|
||||
try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackPointId, 10, true)) {
|
||||
if (cursor == null || cursor.getCount() == 0) {
|
||||
return location.getBearing();
|
||||
}
|
||||
cursor.moveToPosition(cursor.getCount() - 1);
|
||||
viewLocation = contentProviderUtils.createTrackPoint(cursor);
|
||||
}
|
||||
return viewLocation.bearingTo(location);
|
||||
}
|
||||
|
||||
private String getCoordinates(Location location, String separator) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(location.getLongitude()).append(separator).append(location.getLatitude());
|
||||
if (location.hasAltitude()) {
|
||||
builder.append(separator).append(location.getAltitude());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the category.
|
||||
*
|
||||
* @param category the category
|
||||
*/
|
||||
private void writeCategory(String category) {
|
||||
if (category == null || category.equals("")) {
|
||||
return;
|
||||
}
|
||||
printWriter.println("<ExtendedData>");
|
||||
printWriter.println(
|
||||
"<Data name=\"type\"><value>" + StringUtils.formatCData(category) + "</value></Data>");
|
||||
printWriter.println("</ExtendedData>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the track style.
|
||||
*/
|
||||
private void writeTrackStyle() {
|
||||
printWriter.println("<Style id=\"" + TRACK_STYLE + "\">");
|
||||
printWriter.println("<LineStyle><color>7f0000ff</color><width>4</width></LineStyle>");
|
||||
printWriter.println("<IconStyle>");
|
||||
printWriter.println("<scale>1.3</scale>");
|
||||
printWriter.println("<Icon><href>" + TRACK_ICON + "</href></Icon>");
|
||||
printWriter.println("</IconStyle>");
|
||||
printWriter.println("</Style>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a placemarker style.
|
||||
*
|
||||
* @param name the name of the style
|
||||
* @param url the url of the style icon
|
||||
* @param x the x position of the hotspot
|
||||
* @param y the y position of the hotspot
|
||||
*/
|
||||
private void writePlacemarkerStyle(String name, String url, int x, int y) {
|
||||
printWriter.println("<Style id=\"" + name + "\"><IconStyle>");
|
||||
printWriter.println("<scale>1.3</scale>");
|
||||
printWriter.println("<Icon><href>" + url + "</href></Icon>");
|
||||
printWriter.println(
|
||||
"<hotSpot x=\"" + x + "\" y=\"" + y + "\" xunits=\"pixels\" yunits=\"pixels\"/>");
|
||||
printWriter.println("</IconStyle></Style>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a sensor style.
|
||||
*
|
||||
* @param name the name of the sesnor
|
||||
* @param displayName the sensor display name
|
||||
*/
|
||||
private void writeSensorStyle(String name, String displayName) {
|
||||
printWriter.println("<gx:SimpleArrayField name=\"" + name + "\" type=\"int\">");
|
||||
printWriter.println("<displayName>" + StringUtils.formatCData(displayName) + "</displayName>");
|
||||
printWriter.println("</gx:SimpleArrayField>");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
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 implements TrackExporter {
|
||||
|
||||
public static final String KMZ_EXTENSION = "kmz";
|
||||
public static final String KMZ_IMAGES_DIR = "images";
|
||||
public static final String KMZ_KML_FILE = "doc.kml";
|
||||
|
||||
private static final String TAG = KmzTrackExporter.class.getSimpleName();
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final FileTrackExporter fileTrackExporter;
|
||||
private final Track[] tracks;
|
||||
private final long photoSize;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param contentProviderUtils the content provider utils
|
||||
* @param fileTrackExporter the file track exporter
|
||||
* @param tracks the tracks to export
|
||||
* @param context the context
|
||||
*/
|
||||
public KmzTrackExporter(ContentProviderUtils contentProviderUtils,
|
||||
FileTrackExporter fileTrackExporter, Track[] tracks, Context context) {
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.fileTrackExporter = fileTrackExporter;
|
||||
this.tracks = tracks;
|
||||
this.photoSize = PreferencesUtils.getInt(
|
||||
context, R.string.photo_size_key, PreferencesUtils.PHOTO_SIZE_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(OutputStream outputStream) {
|
||||
ZipOutputStream zipOutputStream = null;
|
||||
try {
|
||||
zipOutputStream = new ZipOutputStream(outputStream);
|
||||
|
||||
// Add kml file
|
||||
ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE);
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
|
||||
boolean success = fileTrackExporter.writeTrack(zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
if (!success) {
|
||||
Log.e(TAG, "Unable to write kml in kmz");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add photos
|
||||
addImages(zipOutputStream);
|
||||
return true;
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Unable to write track", e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to write track", e);
|
||||
return false;
|
||||
} finally {
|
||||
if (zipOutputStream != null) {
|
||||
try {
|
||||
zipOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to close zip input stream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addImages(ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
|
||||
for (Track track : tracks) {
|
||||
try (Cursor cursor = contentProviderUtils.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 = contentProviderUtils.createWaypoint(cursor);
|
||||
if (waypoint.hasPhoto()) {
|
||||
addImage(zipOutputStream, waypoint.getPhotoUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addImage(ZipOutputStream zipOutputStream, String photoUrl) throws IOException {
|
||||
Uri uri = Uri.parse(photoUrl);
|
||||
File file = new File(uri.getPath());
|
||||
if (!file.exists()) {
|
||||
Log.e(TAG, "file not found " + photoUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
ZipEntry zipEntry = new ZipEntry(
|
||||
KMZ_IMAGES_DIR + File.separatorChar + uri.getLastPathSegment());
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
|
||||
int sampleSize;
|
||||
if (photoSize == -1) {
|
||||
sampleSize = 1;
|
||||
} else {
|
||||
long size = file.length();
|
||||
// Convert from kilobytes to bytes.
|
||||
long limit = photoSize * 1024;
|
||||
sampleSize = size > limit ? (int) Math.ceil(size / limit) : 1;
|
||||
}
|
||||
if (sampleSize == 1) {
|
||||
readFromFile(zipOutputStream, uri);
|
||||
} else {
|
||||
readFromScaledBitmap(zipOutputStream, uri, sampleSize);
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
private void readFromScaledBitmap(ZipOutputStream zipOutputStream, Uri uri, int sampleSize) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = sampleSize;
|
||||
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(uri.getPath(), options);
|
||||
if (bitmap == null) {
|
||||
return;
|
||||
}
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, zipOutputStream);
|
||||
bitmap.recycle();
|
||||
}
|
||||
|
||||
private void readFromFile(ZipOutputStream zipOutputStream, Uri uri) throws IOException {
|
||||
try (FileInputStream fileInputStream = new FileInputStream(new File(uri.getPath()))) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int byteCount;
|
||||
while ((byteCount = fileInputStream.read(buffer)) != -1) {
|
||||
zipOutputStream.write(buffer, 0, byteCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* An activity for saving tracks to the external storage. If saving a specific
|
||||
* track, option to save it to a temp directory and play the track afterward.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SaveActivity extends Activity {
|
||||
|
||||
public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format";
|
||||
public static final String EXTRA_TRACK_IDS = "track_ids";
|
||||
|
||||
@Deprecated //TODO Seems to be a left over from Google Earth integration and can be removed.
|
||||
public static final String EXTRA_PLAY_TRACK = "play_track";
|
||||
|
||||
private static final int DIALOG_PROGRESS_ID = 0;
|
||||
private static final int DIALOG_RESULT_ID = 1;
|
||||
|
||||
private TrackFileFormat trackFileFormat;
|
||||
private long[] trackIds;
|
||||
private boolean playTrack;
|
||||
private String directoryDisplayName;
|
||||
|
||||
private SaveAsyncTask saveAsyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
// the number of tracks successfully saved
|
||||
private int successCount;
|
||||
|
||||
// the number of tracks to save
|
||||
private int totalCount;
|
||||
|
||||
// the last successfully saved path
|
||||
private String savedPath;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT);
|
||||
trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS);
|
||||
playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false);
|
||||
|
||||
if (!FileUtils.isExternalStorageWriteable()) {
|
||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
File directory = playTrack ? new File(getCacheDir(), FileUtils.PLAY_TRACKS_DIR)
|
||||
: new File(FileUtils.getPath(trackFileFormat.getExtension()));
|
||||
if (!FileUtils.ensureDirectoryExists(directory)) {
|
||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (playTrack) {
|
||||
for (File file : directory.listFiles()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
directoryDisplayName = playTrack ? directory.getName()
|
||||
: FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
||||
|
||||
Object retained = getLastNonConfigurationInstance();
|
||||
if (retained instanceof SaveAsyncTask) {
|
||||
saveAsyncTask = (SaveAsyncTask) retained;
|
||||
saveAsyncTask.setActivity(this);
|
||||
} else {
|
||||
saveAsyncTask = new SaveAsyncTask(this, trackIds, trackFileFormat, playTrack, directory);
|
||||
saveAsyncTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
saveAsyncTask.setActivity(null);
|
||||
return saveAsyncTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case DIALOG_PROGRESS_ID:
|
||||
progressDialog = DialogUtils.createHorizontalProgressDialog(this,
|
||||
R.string.export_external_storage_progress_message,
|
||||
new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
saveAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}, directoryDisplayName);
|
||||
return progressDialog;
|
||||
case DIALOG_RESULT_ID:
|
||||
int iconId;
|
||||
int titleId;
|
||||
String message;
|
||||
String totalTracks = getResources()
|
||||
.getQuantityString(R.plurals.tracks, totalCount, totalCount);
|
||||
if (successCount == totalCount) {
|
||||
iconId = R.drawable.ic_dialog_success;
|
||||
titleId = R.string.generic_success_title;
|
||||
message = getString(
|
||||
R.string.export_external_storage_success, totalTracks, directoryDisplayName);
|
||||
} else {
|
||||
iconId = android.R.drawable.ic_dialog_alert;
|
||||
titleId = R.string.generic_error_title;
|
||||
message = getString(R.string.export_external_storage_error, successCount, totalTracks,
|
||||
directoryDisplayName);
|
||||
}
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true)
|
||||
.setIcon(iconId).setMessage(message)
|
||||
.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int arg1) {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
}).setTitle(titleId);
|
||||
if (!playTrack && trackIds.length == 1 && trackIds[0] != -1L && successCount == totalCount
|
||||
&& savedPath != null) {
|
||||
builder.setNegativeButton(
|
||||
R.string.share_track_share_file, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = IntentUtils.newShareFileIntent(
|
||||
SaveActivity.this, trackIds[0], savedPath, trackFileFormat);
|
||||
startActivity(
|
||||
Intent.createChooser(intent, getString(R.string.share_track_picker_title)));
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
final Dialog dialog = builder.create();
|
||||
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
|
||||
@Override
|
||||
public void onShow(DialogInterface dialogInterface) {
|
||||
DialogUtils.setDialogTitleDivider(SaveActivity.this, dialog);
|
||||
}
|
||||
});
|
||||
return dialog;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param aSuccessCount the number of tracks successfully saved
|
||||
* @param aTotalCount the number of tracks to save
|
||||
* @param aSavedPath the last successfully saved path
|
||||
*/
|
||||
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, String aSavedPath) {
|
||||
successCount = aSuccessCount;
|
||||
totalCount = aTotalCount;
|
||||
savedPath = aSavedPath;
|
||||
removeDialog(DIALOG_PROGRESS_ID);
|
||||
showDialog(DIALOG_RESULT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress dialog.
|
||||
*/
|
||||
public void showProgressDialog() {
|
||||
showDialog(DIALOG_PROGRESS_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress dialog value.
|
||||
*
|
||||
* @param number the number of points saved
|
||||
* @param max the maximum number of points
|
||||
*/
|
||||
public void setProgressDialogValue(int number, int max) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax(max);
|
||||
progressDialog.setProgress(Math.min(number, max));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.TracksColumns;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Async Task to save tracks to the external storage.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private static final String TAG = SaveAsyncTask.class.getSimpleName();
|
||||
private final long[] trackIds;
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
@Deprecated //TODO Seems to be a left over from Google Earth integration and can be removed.
|
||||
private final boolean playTrack;
|
||||
private final File directory;
|
||||
private final Context context;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private SaveActivity saveActivity;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
// true if the AsyncTask has completed
|
||||
private boolean completed;
|
||||
|
||||
// the number of tracks successfully saved
|
||||
private int successCount;
|
||||
|
||||
// the number of tracks to save
|
||||
private int totalCount;
|
||||
|
||||
// the last successfully saved path
|
||||
private String savedPath;
|
||||
|
||||
/**
|
||||
* Creates an AsyncTask.
|
||||
*
|
||||
* @param saveActivity the activity currently associated with this task
|
||||
* @param trackIds the track ids to save. To save all, set to size 1 with
|
||||
* trackIds[0] == -1L
|
||||
* @param trackFileFormat the track file format
|
||||
* @param playTrack true to play track
|
||||
* @param directory the directory to write the file
|
||||
*/
|
||||
public SaveAsyncTask(SaveActivity saveActivity, long[] trackIds, TrackFileFormat trackFileFormat, boolean playTrack, File directory) {
|
||||
this.saveActivity = saveActivity;
|
||||
this.trackIds = trackIds;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.playTrack = playTrack;
|
||||
this.directory = directory;
|
||||
context = saveActivity.getApplicationContext();
|
||||
contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
||||
|
||||
completed = false;
|
||||
successCount = 0;
|
||||
totalCount = 0;
|
||||
savedPath = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current activity associated with this AsyncTask.
|
||||
*
|
||||
* @param saveActivity the current activity, can be null
|
||||
*/
|
||||
public void setActivity(SaveActivity saveActivity) {
|
||||
this.saveActivity = saveActivity;
|
||||
if (completed && saveActivity != null) {
|
||||
saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (saveActivity != null) {
|
||||
saveActivity.showProgressDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
boolean isRecording = PreferencesUtils.getLong(saveActivity, R.string.recording_track_id_key) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
boolean isPaused = PreferencesUtils.getBoolean(saveActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
// Get the wake lock if not recording or paused
|
||||
if (!isRecording || isPaused) {
|
||||
wakeLock = SystemUtils.acquireWakeLock(saveActivity, wakeLock);
|
||||
}
|
||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||
return saveAllTracks();
|
||||
} else {
|
||||
totalCount = 1;
|
||||
Track[] tracks = new Track[trackIds.length];
|
||||
for (int i = 0; i < trackIds.length; i++) {
|
||||
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
|
||||
if (tracks[i] == null) {
|
||||
Log.d(TAG, "No track for " + trackIds[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (saveTracks(tracks)) {
|
||||
successCount = 1;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (saveActivity != null) {
|
||||
saveActivity.setProgressDialogValue(values[0], values[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
completed = true;
|
||||
if (saveActivity != null) {
|
||||
saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
completed = true;
|
||||
if (saveActivity != null) {
|
||||
saveActivity.onAsyncTaskCompleted(successCount, totalCount, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves tracks to one file.
|
||||
*
|
||||
* @param tracks the tracks
|
||||
*/
|
||||
private Boolean saveTracks(Track[] tracks) {
|
||||
if (tracks.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Track track = tracks[0];
|
||||
boolean useKmz = trackFileFormat == TrackFileFormat.KML && !playTrack;
|
||||
String extension = useKmz ? KmzTrackExporter.KMZ_EXTENSION : trackFileFormat.getExtension();
|
||||
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, tracks,
|
||||
trackFileFormat.newTrackWriter(context, tracks.length > 1, playTrack),
|
||||
new TrackExporterListener() {
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(int number, int max) {
|
||||
/*
|
||||
* If only saving one track, update the progress dialog once every
|
||||
* 500 points
|
||||
*/
|
||||
if (trackIds.length == 1 && trackIds[0] != -1L && number % 500 == 0) {
|
||||
publishProgress(number, max);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TrackExporter trackExporter = useKmz ? new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, context) : fileTrackExporter;
|
||||
|
||||
String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), extension);
|
||||
File file = new File(directory, fileName);
|
||||
FileOutputStream fileOutputStream = null;
|
||||
try {
|
||||
fileOutputStream = new FileOutputStream(file);
|
||||
if (trackExporter.writeTrack(fileOutputStream)) {
|
||||
savedPath = file.getAbsolutePath();
|
||||
return true;
|
||||
} else {
|
||||
if (!file.delete()) {
|
||||
Log.d(TAG, "Unable to delete file");
|
||||
}
|
||||
Log.e(TAG, "Unable to export track");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all the tracks.
|
||||
*/
|
||||
private Boolean saveAllTracks() {
|
||||
try (Cursor cursor = contentProviderUtils.getTrackCursor(null, null, TracksColumns._ID)) {
|
||||
if (cursor == null) {
|
||||
return false;
|
||||
}
|
||||
totalCount = cursor.getCount();
|
||||
for (int i = 0; i < totalCount; i++) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
cursor.moveToPosition(i);
|
||||
Track track = contentProviderUtils.createTrack(cursor);
|
||||
if (track != null && saveTracks(new Track[]{track})) {
|
||||
successCount++;
|
||||
}
|
||||
publishProgress(i + 1, totalCount);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
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
|
||||
*/
|
||||
boolean writeTrack(OutputStream outputStream);
|
||||
}
|
||||
@@ -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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
void onProgressUpdate(int number, int max);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Interface for writing tracks to a file. The expected sequence of calls is:
|
||||
*
|
||||
* <pre>
|
||||
* {@link #prepare(OutputStream)}
|
||||
* {@link #writeHeader(Track[])}
|
||||
* For each track:
|
||||
* {@link #writeBeginWaypoints(Track)}
|
||||
* For each waypoint:
|
||||
* {@link #writeWaypoint(Waypoint)}
|
||||
* {@link #writeEndWaypoints()}
|
||||
* {@link #writeBeginTracks()}
|
||||
* For each track:
|
||||
* {@link #writeBeginTrack(Track, Location)}
|
||||
* For each segment:
|
||||
* {@link #writeOpenSegment()}
|
||||
* For each location in the segment:
|
||||
* {@link #writeLocation(Location)}
|
||||
* {@link #writeCloseSegment()}
|
||||
* {@link #writeEndTrack(Track, Location)}
|
||||
* {@link #writeEndTracks()}
|
||||
* {@link #writeFooter()}
|
||||
* {@link #close()}
|
||||
* </pre>
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public interface TrackWriter {
|
||||
|
||||
/**
|
||||
* Gets the file extension (e.g, gpx, kml, ...).
|
||||
*/
|
||||
String getExtension();
|
||||
|
||||
/**
|
||||
* Prepares the output stream.
|
||||
*
|
||||
* @param outputStream the output stream
|
||||
*/
|
||||
void prepare(OutputStream outputStream);
|
||||
|
||||
/**
|
||||
* Closes the output stream.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Writes the header
|
||||
*
|
||||
* @param tracks the tracks
|
||||
*/
|
||||
void writeHeader(Track[] tracks);
|
||||
|
||||
/**
|
||||
* Writes the footer.
|
||||
*/
|
||||
void writeFooter();
|
||||
|
||||
/**
|
||||
* Writes the beginning of the waypoints.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
void writeBeginWaypoints(Track track);
|
||||
|
||||
/**
|
||||
* Writes the end of the waypoints.
|
||||
*/
|
||||
void writeEndWaypoints();
|
||||
|
||||
/**
|
||||
* Writes a waypoint.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
*/
|
||||
void writeWaypoint(Waypoint waypoint);
|
||||
|
||||
/**
|
||||
* Writes the beginning of the tracks.
|
||||
*/
|
||||
void writeBeginTracks();
|
||||
|
||||
/**
|
||||
* Writes the end of the tracks,
|
||||
*/
|
||||
void writeEndTracks();
|
||||
|
||||
/**
|
||||
* Writes the beginning of a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @param startLocation the start location
|
||||
*/
|
||||
void writeBeginTrack(Track track, Location startLocation);
|
||||
|
||||
/**
|
||||
* Writes the end of a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @param endLocation the end location
|
||||
*/
|
||||
void writeEndTrack(Track track, Location endLocation);
|
||||
|
||||
/**
|
||||
* Writes open segment.
|
||||
*/
|
||||
void writeOpenSegment();
|
||||
|
||||
/**
|
||||
* Writes close segment.
|
||||
*/
|
||||
void writeCloseSegment();
|
||||
|
||||
/**
|
||||
* Writes a location.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
void writeLocation(Location location);
|
||||
}
|
||||
+612
@@ -0,0 +1,612 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
||||
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
/**
|
||||
* Abstract class for various file track importers like {@link GpxFileTrackImporter} and
|
||||
* {@link KmlFileTrackImporter}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
abstract class AbstractFileTrackImporter extends DefaultHandler implements TrackImporter {
|
||||
|
||||
private static final String TAG = AbstractFileTrackImporter.class.getSimpleName();
|
||||
// The maximum number of buffered locations for bulk-insertion
|
||||
private static final int MAX_BUFFERED_LOCATIONS = 512;
|
||||
private final Context context;
|
||||
private final long importTrackId;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final int recordingDistanceInterval;
|
||||
private final List<Long> trackIds;
|
||||
private final List<Waypoint> waypoints;
|
||||
// The current element content
|
||||
protected String content;
|
||||
protected String name;
|
||||
protected String description;
|
||||
protected String category;
|
||||
protected String latitude;
|
||||
protected String longitude;
|
||||
protected String altitude;
|
||||
protected String time;
|
||||
protected String waypointType;
|
||||
protected String photoUrl;
|
||||
// The current track data
|
||||
private TrackData trackData;
|
||||
// The SAX locator to get the current line information
|
||||
private Locator locator;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param importTrackId the track id to import to. -1L to import to a new
|
||||
* track.
|
||||
*/
|
||||
AbstractFileTrackImporter(
|
||||
Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
|
||||
this.context = context;
|
||||
this.importTrackId = importTrackId;
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.recordingDistanceInterval = PreferencesUtils.getInt(context,
|
||||
R.string.recording_distance_interval_key,
|
||||
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
||||
trackIds = new ArrayList<>();
|
||||
waypoints = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocumentLocator(Locator locator) {
|
||||
this.locator = locator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) {
|
||||
String newContent = new String(ch, start, length);
|
||||
if (content == null) {
|
||||
content = newContent;
|
||||
} else {
|
||||
/*
|
||||
* In 99% of the cases, a single call to this method will be made for each
|
||||
* sequence of characters we're interested in, so we'll rarely be
|
||||
* concatenating strings, thus not justifying the use of a StringBuilder.
|
||||
*/
|
||||
content += newContent;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long importFile(InputStream inputStream) {
|
||||
try {
|
||||
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
saxParser.parse(inputStream, this);
|
||||
Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms");
|
||||
if (trackIds.size() != 1) {
|
||||
Log.d(TAG, trackIds.size() + " tracks imported");
|
||||
cleanImport();
|
||||
return -1L;
|
||||
}
|
||||
return trackIds.get(0);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
cleanImport();
|
||||
return -1L;
|
||||
} catch (ParserConfigurationException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
cleanImport();
|
||||
return -1L;
|
||||
} catch (SAXException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
cleanImport();
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On file end.
|
||||
*/
|
||||
protected void onFileEnd() {
|
||||
// Add waypoints to the last imported track
|
||||
int size = trackIds.size();
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
long trackId = trackIds.get(size - 1);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int waypointPosition = -1;
|
||||
Waypoint waypoint = null;
|
||||
Location location = null;
|
||||
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(
|
||||
track.getTripStatistics().getStartTime());
|
||||
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
|
||||
track.getTripStatistics().getStartTime());
|
||||
LocationIterator locationIterator = null;
|
||||
|
||||
try {
|
||||
locationIterator = contentProviderUtils.getTrackPointLocationIterator(
|
||||
track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
|
||||
while (true) {
|
||||
if (waypoint == null) {
|
||||
waypointPosition++;
|
||||
waypoint = waypointPosition < waypoints.size() ? waypoints.get(waypointPosition) : null;
|
||||
if (waypoint == null) {
|
||||
// No more waypoints
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (location == null) {
|
||||
if (!locationIterator.hasNext()) {
|
||||
// No more track points. Ignore the rest of the waypoints.
|
||||
return;
|
||||
}
|
||||
location = locationIterator.next();
|
||||
trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval);
|
||||
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
}
|
||||
if (waypoint.getLocation().getTime() > location.getTime()) {
|
||||
location = null;
|
||||
} else if (waypoint.getLocation().getTime() < location.getTime()) {
|
||||
waypoint = null;
|
||||
} else {
|
||||
// The waypoint location time matches the track point time
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
// Invalid location, load the next location
|
||||
location = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Valid location
|
||||
if (location.getLatitude() == waypoint.getLocation().getLatitude()
|
||||
&& location.getLongitude() == waypoint.getLocation().getLongitude()) {
|
||||
|
||||
// Get tripStatistics, description, and icon
|
||||
TripStatistics tripStatistics;
|
||||
String waypointDescription;
|
||||
String icon;
|
||||
if (waypoint.getType() == WaypointType.STATISTICS) {
|
||||
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
|
||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime());
|
||||
waypointDescription = new DescriptionGeneratorImpl(context)
|
||||
.generateWaypointDescription(tripStatistics);
|
||||
icon = context.getString(R.string.marker_statistics_icon_url);
|
||||
} else {
|
||||
tripStatistics = null;
|
||||
waypointDescription = waypoint.getDescription();
|
||||
icon = context.getString(R.string.marker_waypoint_icon_url);
|
||||
}
|
||||
|
||||
// Get length and duration
|
||||
double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance();
|
||||
long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime();
|
||||
|
||||
// Insert waypoint
|
||||
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription,
|
||||
waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration,
|
||||
-1L, -1L, location, tripStatistics, waypoint.getPhotoUrl());
|
||||
contentProviderUtils.insertWaypoint(newWaypoint);
|
||||
}
|
||||
|
||||
// Load the next waypoint
|
||||
waypoint = null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (locationIterator != null) {
|
||||
locationIterator.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On track start.
|
||||
*/
|
||||
protected void onTrackStart() throws SAXException {
|
||||
trackData = new TrackData();
|
||||
long trackId;
|
||||
if (importTrackId == -1L) {
|
||||
Uri uri = contentProviderUtils.insertTrack(trackData.track);
|
||||
trackId = Long.parseLong(uri.getLastPathSegment());
|
||||
} else {
|
||||
if (trackIds.size() > 0) {
|
||||
throw new SAXException(createErrorMessage(
|
||||
"Cannot import more than one track to an existing track " + importTrackId));
|
||||
}
|
||||
trackId = importTrackId;
|
||||
contentProviderUtils.clearTrack(context, trackId);
|
||||
}
|
||||
trackIds.add(trackId);
|
||||
trackData.track.setId(trackId);
|
||||
}
|
||||
|
||||
/**
|
||||
* On track end.
|
||||
*/
|
||||
protected void onTrackEnd() {
|
||||
flushLocations(trackData);
|
||||
if (name != null) {
|
||||
trackData.track.setName(name);
|
||||
}
|
||||
if (description != null) {
|
||||
trackData.track.setDescription(description);
|
||||
}
|
||||
if (category != null) {
|
||||
trackData.track.setCategory(category);
|
||||
trackData.track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||
}
|
||||
if (trackData.tripStatisticsUpdater == null) {
|
||||
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackData.importTime);
|
||||
trackData.tripStatisticsUpdater.updateTime(trackData.importTime);
|
||||
}
|
||||
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
|
||||
trackData.track.setNumberOfPoints(trackData.numberOfLocations);
|
||||
contentProviderUtils.updateTrack(trackData.track);
|
||||
insertFirstWaypoint(trackData.track);
|
||||
}
|
||||
|
||||
/**
|
||||
* On track segment start.
|
||||
*/
|
||||
protected void onTrackSegmentStart() {
|
||||
trackData.numberOfSegments++;
|
||||
|
||||
/*
|
||||
* If not the first segment, add a pause separator if there is at least one
|
||||
* location in the last segment.
|
||||
*/
|
||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
|
||||
insertLocation(createLocation(TrackRecordingService.PAUSE_LATITUDE, 0.0, 0.0,
|
||||
trackData.lastLocationInCurrentSegment.getTime()));
|
||||
}
|
||||
trackData.lastLocationInCurrentSegment = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a waypoint.
|
||||
*
|
||||
* @param type the waypoint type
|
||||
*/
|
||||
protected void addWaypoint(WaypointType type) throws SAXException {
|
||||
// Waypoint must have a time, else cannot match to the track points
|
||||
if (time == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Waypoint waypoint = new Waypoint();
|
||||
Location location = createLocation();
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
||||
}
|
||||
waypoint.setLocation(location);
|
||||
|
||||
if (name != null) {
|
||||
waypoint.setName(name);
|
||||
}
|
||||
if (description != null) {
|
||||
waypoint.setDescription(description);
|
||||
}
|
||||
if (category != null) {
|
||||
waypoint.setCategory(category);
|
||||
}
|
||||
waypoint.setType(type);
|
||||
|
||||
if (photoUrl != null) {
|
||||
waypoint.setPhotoUrl(photoUrl);
|
||||
}
|
||||
waypoints.add(waypoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a track point.
|
||||
*/
|
||||
protected Location getTrackPoint() throws SAXException {
|
||||
Location location = createLocation();
|
||||
|
||||
// Calculate derived attributes from the previous point
|
||||
if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) {
|
||||
long timeDifference = location.getTime() - trackData.lastLocationInCurrentSegment.getTime();
|
||||
|
||||
// Check for negative time change
|
||||
if (timeDifference <= 0) {
|
||||
Log.w(TAG, "Time difference not postive.");
|
||||
} else {
|
||||
|
||||
/*
|
||||
* We don't have a speed and bearing in GPX, make something up from the
|
||||
* last two points. 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.
|
||||
*/
|
||||
double duration = timeDifference * UnitConversions.MS_TO_S;
|
||||
double speed = trackData.lastLocationInCurrentSegment.distanceTo(location) / duration;
|
||||
location.setSpeed((float) speed);
|
||||
}
|
||||
location.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(location));
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
||||
}
|
||||
|
||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
|
||||
/*
|
||||
* If not the first segment, add a resume separator before adding the
|
||||
* first location.
|
||||
*/
|
||||
insertLocation(
|
||||
createLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
|
||||
}
|
||||
trackData.lastLocationInCurrentSegment = location;
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a track point.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
protected void insertTrackPoint(Location location) {
|
||||
insertLocation(location);
|
||||
|
||||
if (trackData.track.getStartId() == -1L) {
|
||||
// Flush the location to set the track start id and the track end id
|
||||
flushLocations(trackData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error message.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
protected String createErrorMessage(String message) {
|
||||
return String.format(Locale.US, "Parsing error at line: %d column: %d. %s",
|
||||
locator.getLineNumber(), locator.getColumnNumber(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the photo url for a file.
|
||||
*
|
||||
* @param fileName the file name
|
||||
*/
|
||||
protected String getPhotoUrl(String fileName) {
|
||||
if (importTrackId == -1L) {
|
||||
return null;
|
||||
}
|
||||
File dir = FileUtils.getPhotoDir(importTrackId);
|
||||
File file = new File(dir, fileName);
|
||||
return Uri.fromFile(file).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
*/
|
||||
private Location createLocation() throws SAXException {
|
||||
if (latitude == null || longitude == null) {
|
||||
return null;
|
||||
}
|
||||
double latitudeValue;
|
||||
double longitudeValue;
|
||||
try {
|
||||
latitudeValue = Double.parseDouble(latitude);
|
||||
longitudeValue = Double.parseDouble(longitude);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(
|
||||
Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||
}
|
||||
Double altitudeValue = null;
|
||||
if (altitude != null) {
|
||||
try {
|
||||
altitudeValue = Double.parseDouble(altitude);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(
|
||||
createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
long timeValue;
|
||||
if (time == null) {
|
||||
timeValue = trackData.importTime;
|
||||
} else {
|
||||
try {
|
||||
timeValue = StringUtils.getTime(time);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SAXException(
|
||||
createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
}
|
||||
}
|
||||
return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
*
|
||||
* @param latitudeValue the latitude value
|
||||
* @param longitudeValue the longitude value
|
||||
* @param altitudeValue the altitude value
|
||||
* @param timeValue the time value
|
||||
*/
|
||||
private Location createLocation(
|
||||
double latitudeValue, double longitudeValue, Double altitudeValue, long timeValue) {
|
||||
Location location = new Location(LocationManager.GPS_PROVIDER);
|
||||
location.setLatitude(latitudeValue);
|
||||
location.setLongitude(longitudeValue);
|
||||
if (altitudeValue != null) {
|
||||
location.setAltitude(altitudeValue);
|
||||
} else {
|
||||
location.removeAltitude();
|
||||
}
|
||||
location.setTime(timeValue);
|
||||
location.removeAccuracy();
|
||||
location.removeBearing();
|
||||
location.removeSpeed();
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a location.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
private void insertLocation(Location location) {
|
||||
if (trackData.tripStatisticsUpdater == null) {
|
||||
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(
|
||||
location.getTime() != -1L ? location.getTime() : trackData.importTime);
|
||||
}
|
||||
trackData.tripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
|
||||
trackData.bufferedLocations[trackData.numBufferedLocations] = location;
|
||||
trackData.numBufferedLocations++;
|
||||
trackData.numberOfLocations++;
|
||||
|
||||
if (trackData.numBufferedLocations >= MAX_BUFFERED_LOCATIONS) {
|
||||
flushLocations(trackData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the locations to the database.
|
||||
*
|
||||
* @param data the track data
|
||||
*/
|
||||
private void flushLocations(TrackData data) {
|
||||
if (data.numBufferedLocations <= 0) {
|
||||
return;
|
||||
}
|
||||
contentProviderUtils.bulkInsertTrackPoint(
|
||||
data.bufferedLocations, data.numBufferedLocations, data.track.getId());
|
||||
data.numBufferedLocations = 0;
|
||||
if (data.track.getStartId() == -1L) {
|
||||
data.track.setStartId(contentProviderUtils.getFirstTrackPointId(data.track.getId()));
|
||||
}
|
||||
data.track.setStopId(contentProviderUtils.getLastTrackPointId(data.track.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the first waypoint, the track statistics waypoint.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
private void insertFirstWaypoint(Track track) {
|
||||
String waypointName = context.getString(R.string.marker_split_name_format, 0);
|
||||
String waypointCategory = "";
|
||||
TripStatisticsUpdater updater = new TripStatisticsUpdater(
|
||||
track.getTripStatistics().getStartTime());
|
||||
TripStatistics tripStatistics = updater.getTripStatistics();
|
||||
String waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription(
|
||||
tripStatistics);
|
||||
String icon = context.getString(R.string.marker_statistics_icon_url);
|
||||
double length = 0.0;
|
||||
long duration = 0L;
|
||||
Location waypointLocation = new Location("");
|
||||
waypointLocation.setLatitude(100);
|
||||
waypointLocation.setLongitude(180);
|
||||
Waypoint waypoint = new Waypoint(waypointName, waypointDescription, waypointCategory, icon,
|
||||
track.getId(), WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation,
|
||||
tripStatistics, "");
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up import.
|
||||
*/
|
||||
private void cleanImport() {
|
||||
for (long trackId : trackIds) {
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for the current track.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private class TrackData {
|
||||
|
||||
// The current track
|
||||
Track track = new Track();
|
||||
|
||||
// The number of segments processed for the current track
|
||||
int numberOfSegments = 0;
|
||||
|
||||
/*
|
||||
* The last location in the current segment. Null if the current segment
|
||||
* doesn't have a last location.
|
||||
*/
|
||||
Location lastLocationInCurrentSegment;
|
||||
|
||||
// The number of locations processed for the current track
|
||||
int numberOfLocations = 0;
|
||||
|
||||
// The trip statistics updater for the current track
|
||||
TripStatisticsUpdater tripStatisticsUpdater;
|
||||
|
||||
// The import time of the track.
|
||||
long importTime = System.currentTimeMillis();
|
||||
|
||||
// The buffered locations
|
||||
Location[] bufferedLocations = new Location[MAX_BUFFERED_LOCATIONS];
|
||||
|
||||
// The number of buffered locations
|
||||
int numBufferedLocations = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Imports a GPX file.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
private static final String TAG_DESCRIPTION = "desc";
|
||||
private static final String TAG_COMMENT = "cmt";
|
||||
private static final String TAG_ELEVATION = "ele";
|
||||
private static final String TAG_GPX = "gpx";
|
||||
private static final String TAG_NAME = "name";
|
||||
private static final String TAG_TIME = "time";
|
||||
private static final String TAG_TRACK = "trk";
|
||||
private static final String TAG_TRACK_POINT = "trkpt";
|
||||
private static final String TAG_TRACK_SEGMENT = "trkseg";
|
||||
private static final String TAG_TYPE = "type";
|
||||
private static final String TAG_WAYPOINT = "wpt";
|
||||
|
||||
private static final String ATTRIBUTE_LAT = "lat";
|
||||
private static final String ATTRIBUTE_LON = "lon";
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public GpxFileTrackImporter(Context context) {
|
||||
this(context, ContentProviderUtils.Factory.get(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
||||
super(context, -1L, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes)
|
||||
throws SAXException {
|
||||
switch (tag) {
|
||||
case TAG_WAYPOINT:
|
||||
onWaypointStart(attributes);
|
||||
break;
|
||||
case TAG_TRACK:
|
||||
onTrackStart();
|
||||
break;
|
||||
case TAG_TRACK_SEGMENT:
|
||||
onTrackSegmentStart();
|
||||
break;
|
||||
case TAG_TRACK_POINT:
|
||||
onTrackPointStart(attributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||
switch (tag) {
|
||||
case TAG_GPX:
|
||||
onFileEnd();
|
||||
break;
|
||||
case TAG_WAYPOINT:
|
||||
onWaypointEnd();
|
||||
break;
|
||||
case TAG_TRACK:
|
||||
onTrackEnd();
|
||||
break;
|
||||
case TAG_TRACK_POINT:
|
||||
onTrackPointEnd();
|
||||
break;
|
||||
case TAG_NAME:
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_DESCRIPTION:
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_TYPE:
|
||||
if (content != null) {
|
||||
category = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_TIME:
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ELEVATION:
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_COMMENT:
|
||||
if (content != null) {
|
||||
waypointType = content.trim();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
content = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTrackStart() throws SAXException {
|
||||
super.onTrackStart();
|
||||
name = null;
|
||||
description = null;
|
||||
category = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On track point start.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
*/
|
||||
private void onTrackPointStart(Attributes attributes) {
|
||||
latitude = attributes.getValue(ATTRIBUTE_LAT);
|
||||
longitude = attributes.getValue(ATTRIBUTE_LON);
|
||||
altitude = null;
|
||||
time = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On track point end.
|
||||
*/
|
||||
private void onTrackPointEnd() throws SAXException {
|
||||
Location location = getTrackPoint();
|
||||
if (location == null) {
|
||||
return;
|
||||
}
|
||||
insertTrackPoint(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* On waypoint start.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
*/
|
||||
private void onWaypointStart(Attributes attributes) {
|
||||
name = null;
|
||||
description = null;
|
||||
category = null;
|
||||
photoUrl = null;
|
||||
latitude = attributes.getValue(ATTRIBUTE_LAT);
|
||||
longitude = attributes.getValue(ATTRIBUTE_LON);
|
||||
altitude = null;
|
||||
time = null;
|
||||
waypointType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On waypoint end.
|
||||
*/
|
||||
private void onWaypointEnd() throws SAXException {
|
||||
addWaypoint(WaypointType.STATISTICS.name().equals(waypointType) ? WaypointType.STATISTICS
|
||||
: WaypointType.WAYPOINT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
|
||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* An activity to import files from the external storage. Optionally to import
|
||||
* one specific file.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ImportActivity extends Activity {
|
||||
|
||||
public static final String EXTRA_IMPORT_ALL = "import_all";
|
||||
public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format";
|
||||
|
||||
private static final String TAG = ImportActivity.class.getSimpleName();
|
||||
private static final int DIALOG_PROGRESS_ID = 0;
|
||||
private static final int DIALOG_RESULT_ID = 1;
|
||||
|
||||
private ImportAsyncTask importAsyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
private boolean importAll;
|
||||
|
||||
// the path on the external storage to import
|
||||
private String directoryDisplayName;
|
||||
|
||||
// the number of files successfully imported
|
||||
private int successCount;
|
||||
|
||||
// the number of files to import
|
||||
private int totalCount;
|
||||
|
||||
// the last successfully imported track id
|
||||
private long trackId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
importAll = intent.getBooleanExtra(EXTRA_IMPORT_ALL, false);
|
||||
TrackFileFormat trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT);
|
||||
if (trackFileFormat == null) {
|
||||
trackFileFormat = TrackFileFormat.GPX;
|
||||
}
|
||||
|
||||
if (!FileUtils.isExternalStorageAvailable()) {
|
||||
Toast.makeText(this, R.string.external_storage_not_available, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
String directoryPath;
|
||||
if (importAll) {
|
||||
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
||||
directoryPath = FileUtils.getPath(trackFileFormat.getExtension());
|
||||
if (!FileUtils.isDirectory(new File(directoryPath))) {
|
||||
Toast.makeText(
|
||||
this, getString(R.string.import_no_directory, directoryDisplayName), Toast.LENGTH_LONG)
|
||||
.show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
String action = intent.getAction();
|
||||
if (!(Intent.ACTION_ATTACH_DATA.equals(action) || Intent.ACTION_VIEW.equals(action))) {
|
||||
Log.d(TAG, "Invalid action: " + intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Uri data = intent.getData();
|
||||
if ("file".equals(data.getScheme())) {
|
||||
Log.d(TAG, "Invalid data: " + intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
directoryDisplayName = data.getPath();
|
||||
directoryPath = data.getPath();
|
||||
}
|
||||
|
||||
Object retained = getLastNonConfigurationInstance();
|
||||
if (retained instanceof ImportAsyncTask) {
|
||||
importAsyncTask = (ImportAsyncTask) retained;
|
||||
importAsyncTask.setActivity(this);
|
||||
} else {
|
||||
importAsyncTask = new ImportAsyncTask(this, importAll, trackFileFormat, directoryPath);
|
||||
importAsyncTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
importAsyncTask.setActivity(null);
|
||||
return importAsyncTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case DIALOG_PROGRESS_ID:
|
||||
progressDialog = DialogUtils.createHorizontalProgressDialog(
|
||||
this, R.string.import_progress_message, new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
importAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}, directoryDisplayName);
|
||||
return progressDialog;
|
||||
case DIALOG_RESULT_ID:
|
||||
int iconId;
|
||||
int titleId;
|
||||
String message;
|
||||
String totalFiles = getResources()
|
||||
.getQuantityString(R.plurals.files, totalCount, totalCount);
|
||||
if (successCount == totalCount) {
|
||||
if (totalCount == 0) {
|
||||
iconId = android.R.drawable.ic_dialog_info;
|
||||
titleId = R.string.import_no_file_title;
|
||||
message = getString(R.string.import_no_file, directoryDisplayName);
|
||||
} else {
|
||||
iconId = R.drawable.ic_dialog_success;
|
||||
titleId = R.string.generic_success_title;
|
||||
message = getString(R.string.import_success, totalFiles, directoryDisplayName);
|
||||
}
|
||||
} else {
|
||||
iconId = android.R.drawable.ic_dialog_alert;
|
||||
titleId = R.string.generic_error_title;
|
||||
message = getString(
|
||||
R.string.import_error, successCount, totalFiles, directoryDisplayName);
|
||||
}
|
||||
final Dialog dialog = new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId)
|
||||
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialogInterface) {
|
||||
dialogInterface.dismiss();
|
||||
finish();
|
||||
}
|
||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int which) {
|
||||
if (successCount == totalCount && !importAll && trackId != -1L) {
|
||||
Intent intent = IntentUtils.newIntent(
|
||||
ImportActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||
TaskStackBuilder.create(ImportActivity.this)
|
||||
.addParentStack(TrackDetailActivity.class).addNextIntent(intent)
|
||||
.startActivities();
|
||||
}
|
||||
dialogInterface.dismiss();
|
||||
finish();
|
||||
}
|
||||
}).setTitle(titleId).create();
|
||||
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
|
||||
@Override
|
||||
public void onShow(DialogInterface dialogInterface) {
|
||||
DialogUtils.setDialogTitleDivider(ImportActivity.this, dialog);
|
||||
}
|
||||
});
|
||||
return dialog;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param aSuccessCount the number of files successfully imported
|
||||
* @param aTotalCount the number of files to import
|
||||
* @param aTrackId the last successfully imported track id
|
||||
*/
|
||||
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, long aTrackId) {
|
||||
successCount = aSuccessCount;
|
||||
totalCount = aTotalCount;
|
||||
trackId = aTrackId;
|
||||
removeDialog(DIALOG_PROGRESS_ID);
|
||||
showDialog(DIALOG_RESULT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress dialog.
|
||||
*/
|
||||
public void showProgressDialog() {
|
||||
showDialog(DIALOG_PROGRESS_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress dialog value.
|
||||
*
|
||||
* @param number the number of files imported
|
||||
* @param max the maximum number of files
|
||||
*/
|
||||
public void setProgressDialogValue(int number, int max) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax(max);
|
||||
progressDialog.setProgress(Math.min(number, max));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AsyncTask to import files from the external storage.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
||||
private final boolean importAll;
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
private final String path;
|
||||
private final Context context;
|
||||
private ImportActivity importActivity;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
// true if the AsyncTask has completed
|
||||
private boolean completed;
|
||||
|
||||
// the number of files successfully imported
|
||||
private int successCount;
|
||||
|
||||
// the number of files to import
|
||||
private int totalCount;
|
||||
|
||||
// the last successfully imported track id
|
||||
private long trackId;
|
||||
|
||||
/**
|
||||
* Creates an AsyncTask.
|
||||
*
|
||||
* @param importActivity the activity currently associated with this AsyncTask
|
||||
* @param importAll true to import all GPX files
|
||||
* @param trackFileFormat the track file format
|
||||
* @param path path to import GPX files
|
||||
*/
|
||||
public ImportAsyncTask(ImportActivity importActivity, boolean importAll,
|
||||
TrackFileFormat trackFileFormat, String path) {
|
||||
this.importActivity = importActivity;
|
||||
this.importAll = importAll;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.path = path;
|
||||
context = importActivity.getApplicationContext();
|
||||
|
||||
completed = false;
|
||||
successCount = 0;
|
||||
totalCount = 0;
|
||||
trackId = -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current {@link ImportActivity} associated with this AyncTask.
|
||||
*
|
||||
* @param importActivity the current {@link ImportActivity}, can be null
|
||||
*/
|
||||
public void setActivity(ImportActivity importActivity) {
|
||||
this.importActivity = importActivity;
|
||||
if (completed && importActivity != null) {
|
||||
importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (importActivity != null) {
|
||||
importActivity.showProgressDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
// Get the wake lock if not recording or paused
|
||||
boolean isRecording =
|
||||
PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key)
|
||||
!= PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
boolean isPaused = PreferencesUtils.getBoolean(importActivity,
|
||||
R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (!isRecording || isPaused) {
|
||||
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
|
||||
}
|
||||
|
||||
List<File> files = getFiles();
|
||||
totalCount = files.size();
|
||||
if (totalCount == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < totalCount; i++) {
|
||||
if (isCancelled()) {
|
||||
// If cancelled, return true to show the number of files imported
|
||||
return true;
|
||||
}
|
||||
if (importFile(files.get(i))) {
|
||||
successCount++;
|
||||
}
|
||||
publishProgress(i + 1, totalCount);
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (importActivity != null) {
|
||||
importActivity.setProgressDialogValue(values[0], values[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
completed = true;
|
||||
if (importActivity != null) {
|
||||
importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
completed = true;
|
||||
if (importActivity != null) {
|
||||
importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a file.
|
||||
*
|
||||
* @param file the file
|
||||
*/
|
||||
private boolean importFile(final File file) {
|
||||
FileInputStream fileInputStream = null;
|
||||
try {
|
||||
TrackImporter trackImporter;
|
||||
if (trackFileFormat == TrackFileFormat.KML) {
|
||||
String extension = FileUtils.getExtension(file.getName());
|
||||
if (TrackFileFormat.KML.getExtension().equals(extension)) {
|
||||
trackImporter = new KmlFileTrackImporter(context, -1L);
|
||||
} else {
|
||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
||||
Uri uri = contentProviderUtils.insertTrack(new Track());
|
||||
long newId = Long.parseLong(uri.getLastPathSegment());
|
||||
|
||||
trackImporter = new KmzTrackImporter(context, newId);
|
||||
}
|
||||
} else {
|
||||
trackImporter = new GpxFileTrackImporter(context);
|
||||
}
|
||||
fileInputStream = new FileInputStream(file);
|
||||
trackId = trackImporter.importFile(fileInputStream);
|
||||
return trackId != -1L;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
return false;
|
||||
} finally {
|
||||
if (fileInputStream != null) {
|
||||
try {
|
||||
fileInputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to close file input stream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of files. If importAll is true, returns a list of the files
|
||||
* under the path directory. If importAll is false, returns a list containing
|
||||
* just the path file.
|
||||
*/
|
||||
private List<File> getFiles() {
|
||||
List<File> files = new ArrayList<>();
|
||||
File file = new File(path);
|
||||
if (importAll) {
|
||||
File[] candidates = file.listFiles();
|
||||
if (candidates != null) {
|
||||
for (File candidate : candidates) {
|
||||
if (!FileUtils.isDirectory(candidate)) {
|
||||
String extension = FileUtils.getExtension(candidate.getName());
|
||||
if (trackFileFormat == TrackFileFormat.KML && (
|
||||
TrackFileFormat.KML.getExtension().equals(extension)
|
||||
|| KmzTrackExporter.KMZ_EXTENSION.equals(extension))) {
|
||||
files.add(candidate);
|
||||
} else if (trackFileFormat == TrackFileFormat.GPX
|
||||
&& TrackFileFormat.GPX.getExtension().equals(extension)) {
|
||||
files.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
files.add(file);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Imports a KML file.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
private static final String CADENCE = "cadence";
|
||||
private static final String HEART_RATE = "heart_rate";
|
||||
private static final String POWER = "power";
|
||||
|
||||
private static final String STATISTICS_STYLE = "#statistics";
|
||||
private static final String WAYPOINT_STYLE = "#waypoint";
|
||||
|
||||
private static final String TAG_COORDINATES = "coordinates";
|
||||
private static final String TAG_DESCRIPTION = "description";
|
||||
private static final String TAG_GX_COORD = "gx:coord";
|
||||
private static final String TAG_GX_MULTI_TRACK = "gx:MultiTrack";
|
||||
private static final String TAG_GX_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData";
|
||||
private static final String TAG_GX_TRACK = "gx:Track";
|
||||
private static final String TAG_GX_VALUE = "gx:value";
|
||||
private static final String TAG_HREF = "href";
|
||||
private static final String TAG_KML = "kml";
|
||||
private static final String TAG_NAME = "name";
|
||||
private static final String TAG_PHOTO_OVERLAY = "PhotoOverlay";
|
||||
private static final String TAG_PLACEMARK = "Placemark";
|
||||
private static final String TAG_STYLE_URL = "styleUrl";
|
||||
private static final String TAG_VALUE = "value";
|
||||
private static final String TAG_WHEN = "when";
|
||||
|
||||
private static final String ATTRIBUTE_NAME = "name";
|
||||
|
||||
private boolean trackStarted = false;
|
||||
private String sensorName;
|
||||
private ArrayList<Location> locationList;
|
||||
private ArrayList<Integer> cadenceList;
|
||||
private ArrayList<Integer> heartRateList;
|
||||
private ArrayList<Integer> powerList;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param importTrackId track id to import to. -1L to import to a new track.
|
||||
*/
|
||||
public KmlFileTrackImporter(Context context, long importTrackId) {
|
||||
this(context, importTrackId, ContentProviderUtils.Factory.get(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
KmlFileTrackImporter(
|
||||
Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
|
||||
super(context, importTrackId, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes)
|
||||
throws SAXException {
|
||||
switch (tag) {
|
||||
case TAG_PLACEMARK:
|
||||
case TAG_PHOTO_OVERLAY:
|
||||
/*
|
||||
* Note that a track is contained in a Placemark, calling onWaypointStart
|
||||
* will clear various track variables like name, category, and
|
||||
* description.
|
||||
*/
|
||||
onWaypointStart();
|
||||
break;
|
||||
case TAG_GX_MULTI_TRACK:
|
||||
trackStarted = true;
|
||||
onTrackStart();
|
||||
break;
|
||||
case TAG_GX_TRACK:
|
||||
if (!trackStarted) {
|
||||
throw new SAXException("No " + TAG_GX_MULTI_TRACK);
|
||||
}
|
||||
onTrackSegmentStart();
|
||||
break;
|
||||
case TAG_GX_SIMPLE_ARRAY_DATA:
|
||||
onSensorDataStart(attributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||
if (tag.equals(TAG_KML)) {
|
||||
onFileEnd();
|
||||
} else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) {
|
||||
/*
|
||||
* Note that a track is contained in a Placemark, calling onWaypointend is
|
||||
* save since waypointType is not set for a track.
|
||||
*/
|
||||
onWaypointEnd();
|
||||
} else if (localName.equals(TAG_COORDINATES)) {
|
||||
onWaypointLocationEnd();
|
||||
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
|
||||
onTrackEnd();
|
||||
} else if (tag.equals(TAG_GX_TRACK)) {
|
||||
onTrackSegmentEnd();
|
||||
} else if (tag.equals(TAG_GX_COORD)) {
|
||||
onTrackPointEnd();
|
||||
} else if (tag.equals(TAG_GX_VALUE)) {
|
||||
onSensorValueEnd();
|
||||
} else if (tag.equals(TAG_NAME)) {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_DESCRIPTION)) {
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_VALUE)) {
|
||||
if (content != null) {
|
||||
category = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_WHEN)) {
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_STYLE_URL)) {
|
||||
if (content != null) {
|
||||
waypointType = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_HREF)) {
|
||||
if (content != null) {
|
||||
photoUrl = content.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
content = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On waypoint start.
|
||||
*/
|
||||
private void onWaypointStart() {
|
||||
// Reset all Placemark variables
|
||||
name = null;
|
||||
description = null;
|
||||
category = null;
|
||||
photoUrl = null;
|
||||
latitude = null;
|
||||
longitude = null;
|
||||
altitude = null;
|
||||
time = null;
|
||||
waypointType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On waypoint end.
|
||||
*/
|
||||
private void onWaypointEnd() throws SAXException {
|
||||
// Add a waypoint if the waypointType matches
|
||||
WaypointType type = null;
|
||||
if (WAYPOINT_STYLE.equals(waypointType)) {
|
||||
type = WaypointType.WAYPOINT;
|
||||
} else if (STATISTICS_STYLE.equals(waypointType)) {
|
||||
type = WaypointType.STATISTICS;
|
||||
}
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
if (photoUrl != null) {
|
||||
Uri uri = Uri.parse(photoUrl);
|
||||
photoUrl = getPhotoUrl(uri.getLastPathSegment());
|
||||
}
|
||||
addWaypoint(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* On waypoint location end.
|
||||
*/
|
||||
private void onWaypointLocationEnd() {
|
||||
if (content != null) {
|
||||
String[] parts = content.trim().split(",");
|
||||
if (parts.length != 2 && parts.length != 3) {
|
||||
return;
|
||||
}
|
||||
longitude = parts[0];
|
||||
latitude = parts[1];
|
||||
altitude = parts.length == 3 ? parts[2] : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTrackSegmentStart() {
|
||||
super.onTrackSegmentStart();
|
||||
locationList = new ArrayList<>();
|
||||
heartRateList = new ArrayList<>();
|
||||
cadenceList = new ArrayList<>();
|
||||
powerList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* On track segment end.
|
||||
*/
|
||||
private void onTrackSegmentEnd() {
|
||||
// Close a track segment by inserting the segment locations
|
||||
boolean hasHeartRate = heartRateList.size() == locationList.size();
|
||||
boolean hasCadence = cadenceList.size() == locationList.size();
|
||||
boolean hasPower = powerList.size() == locationList.size();
|
||||
|
||||
for (int i = 0; i < locationList.size(); i++) {
|
||||
Location location = locationList.get(i);
|
||||
|
||||
if (!hasPower && !hasCadence && !hasHeartRate) {
|
||||
insertTrackPoint(location);
|
||||
} else {
|
||||
float heartrate = hasHeartRate ? heartRateList.get(i) : SensorDataSet.DATA_UNAVAILABLE;
|
||||
float cadence = hasHeartRate ? cadenceList.get(i) : SensorDataSet.DATA_UNAVAILABLE;
|
||||
float power = hasHeartRate ? powerList.get(i) : SensorDataSet.DATA_UNAVAILABLE;
|
||||
|
||||
SensorDataSetLocation sensorDataSetLocation = new SensorDataSetLocation(location, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime()));
|
||||
insertTrackPoint(sensorDataSetLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On track point end. gx:coord end tag.
|
||||
*/
|
||||
private void onTrackPointEnd() throws SAXException {
|
||||
// Add location to locationList
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
String[] parts = content.trim().split(" ");
|
||||
if (parts.length != 2 && parts.length != 3) {
|
||||
return;
|
||||
}
|
||||
longitude = parts[0];
|
||||
latitude = parts[1];
|
||||
altitude = parts.length == 3 ? parts[2] : null;
|
||||
|
||||
Location location = getTrackPoint();
|
||||
if (location == null) {
|
||||
return;
|
||||
}
|
||||
locationList.add(location);
|
||||
time = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On sensor data start. gx:SimpleArrayData start tag.
|
||||
*/
|
||||
private void onSensorDataStart(Attributes attributes) {
|
||||
sensorName = attributes.getValue(ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* On sensor value end. gx:value end tag.
|
||||
*/
|
||||
private void onSensorValueEnd() throws SAXException {
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
content = content.trim();
|
||||
if (content.equals("")) {
|
||||
return;
|
||||
}
|
||||
int value;
|
||||
try {
|
||||
value = Integer.parseInt(content);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
|
||||
}
|
||||
if (POWER.equals(sensorName)) {
|
||||
powerList.add(value);
|
||||
} else if (HEART_RATE.equals(sensorName)) {
|
||||
heartRateList.add(value);
|
||||
} else if (CADENCE.equals(sensorName)) {
|
||||
cadenceList.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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 de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
/**
|
||||
* Imports a KMZ file.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class KmzTrackImporter implements TrackImporter {
|
||||
|
||||
private static final String TAG = KmzTrackImporter.class.getSimpleName();
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
private final Context context;
|
||||
private final long importTrackId;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param importTrackId track id to import to. This should not be -1L so that
|
||||
* images in the kmz file can be imported.
|
||||
*/
|
||||
public KmzTrackImporter(Context context, long importTrackId) {
|
||||
this.context = context;
|
||||
this.importTrackId = importTrackId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long importFile(InputStream inputStream) {
|
||||
ZipInputStream zipInputStream = null;
|
||||
long trackId = importTrackId;
|
||||
try {
|
||||
ZipEntry zipEntry;
|
||||
|
||||
zipInputStream = new ZipInputStream(inputStream);
|
||||
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
||||
if (Thread.interrupted()) {
|
||||
Log.d(TAG, "Thread interrupted");
|
||||
cleanImport(trackId);
|
||||
return -1L;
|
||||
}
|
||||
String fileName = zipEntry.getName();
|
||||
if (fileName.equals(KmzTrackExporter.KMZ_KML_FILE)) {
|
||||
trackId = parseKml(zipInputStream);
|
||||
if (trackId == -1L) {
|
||||
Log.d(TAG, "Unable to parse kml in kmz");
|
||||
cleanImport(trackId);
|
||||
return -1L;
|
||||
}
|
||||
} else {
|
||||
String prefix = KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar;
|
||||
if (fileName.startsWith(prefix)) {
|
||||
readImageFile(zipInputStream, fileName.substring(prefix.length()));
|
||||
}
|
||||
}
|
||||
zipInputStream.closeEntry();
|
||||
}
|
||||
return trackId;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
cleanImport(trackId);
|
||||
return -1L;
|
||||
} finally {
|
||||
if (zipInputStream != null) {
|
||||
try {
|
||||
zipInputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to close zip input stream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up import.
|
||||
*
|
||||
* @param trackId the trackId
|
||||
*/
|
||||
private void cleanImport(long trackId) {
|
||||
if (trackId != -1L) {
|
||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
}
|
||||
|
||||
if (importTrackId != -1L) {
|
||||
File dir = FileUtils.getPhotoDir(importTrackId);
|
||||
if (FileUtils.isDirectory(dir)) {
|
||||
for (File file : dir.listFiles()) {
|
||||
file.delete();
|
||||
}
|
||||
dir.delete();
|
||||
FileUtils.updateMediaScanner(context, Uri.fromFile(dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses kml
|
||||
*
|
||||
* @param zipInputStream the zip input stream
|
||||
* @return the imported track id or -1L
|
||||
*/
|
||||
private long parseKml(ZipInputStream zipInputStream) throws IOException {
|
||||
ByteArrayInputStream byteArrayInputStream = null;
|
||||
try {
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId);
|
||||
byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream));
|
||||
return kmlFileTrackImporter.importFile(byteArrayInputStream);
|
||||
} finally {
|
||||
if (byteArrayInputStream != null) {
|
||||
byteArrayInputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the kml as byte array.
|
||||
*
|
||||
* @param zipInputStream the zip input stream
|
||||
*/
|
||||
private byte[] getKml(ZipInputStream zipInputStream) throws IOException {
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int count;
|
||||
while ((count = zipInputStream.read(buffer)) != -1) {
|
||||
byteArrayOutputStream.write(buffer, 0, count);
|
||||
}
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an image file.
|
||||
*
|
||||
* @param zipInputStream the zip input stream
|
||||
* @param fileName the file name
|
||||
*/
|
||||
private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
|
||||
FileOutputStream fileOutputStream = null;
|
||||
try {
|
||||
if (importTrackId == -1L) {
|
||||
return;
|
||||
}
|
||||
if (fileName.equals("")) {
|
||||
return;
|
||||
}
|
||||
File dir = FileUtils.getPhotoDir(importTrackId);
|
||||
FileUtils.ensureDirectoryExists(dir);
|
||||
|
||||
File file = new File(dir, fileName);
|
||||
fileOutputStream = new FileOutputStream(file);
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int count;
|
||||
while ((count = zipInputStream.read(buffer)) != -1) {
|
||||
fileOutputStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
FileUtils.updateMediaScanner(context, Uri.fromFile(file));
|
||||
} finally {
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Interface for a track importer.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public interface TrackImporter {
|
||||
|
||||
/**
|
||||
* Import a file.
|
||||
*
|
||||
* @param inputStream the file's input stream
|
||||
* @return the imported track id or -1L.
|
||||
*/
|
||||
long importFile(InputStream inputStream);
|
||||
}
|
||||
Reference in New Issue
Block a user