forked from upstream-mirrors/OpenTracks
Add a res directory for the MyTracksTest eclipse project.
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorData;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* Write track as CSV to a file. See RFC 4180 for info on CSV. Output three
|
||||
* tables.<br>
|
||||
* The first table contains the track info. Its columns are:<br>
|
||||
* "Track name","Activity type","Track description" <br>
|
||||
* <br>
|
||||
* The second table contains the markers. Its columns are:<br>
|
||||
* "Marker name","Marker type","Marker description","Latitude (deg)","Longitude
|
||||
* (deg)","Altitude (m)","Bearing (deg)","Accuracy (m)","Speed (m/s)","Time"<br>
|
||||
* <br>
|
||||
* The thrid table contains the points. Its columns are:<br>
|
||||
* "Segment","Point","Latitude (deg)","Longitude (deg)","Altitude (m)","Bearing
|
||||
* (deg)","Accuracy (m)","Speed (m/s)","Time","Power (W)","Cadence (rpm)","Heart
|
||||
* rate (bpm)","Battery level (%)"<br>
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class CsvTrackWriter implements TrackFormatWriter {
|
||||
|
||||
private static final NumberFormat SHORT_FORMAT = NumberFormat.getInstance();
|
||||
|
||||
static {
|
||||
SHORT_FORMAT.setMaximumFractionDigits(4);
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private PrintWriter printWriter;
|
||||
private Track track;
|
||||
private int segmentIndex;
|
||||
private int pointIndex;
|
||||
|
||||
public CsvTrackWriter(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return TrackFileFormat.CSV.getExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Track aTrack, OutputStream out) {
|
||||
track = aTrack;
|
||||
printWriter = new PrintWriter(out);
|
||||
segmentIndex = 0;
|
||||
pointIndex = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
printWriter.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeHeader() {
|
||||
writeCommaSeparatedLine(context.getString(R.string.generic_name),
|
||||
context.getString(R.string.track_edit_activity_type_hint),
|
||||
context.getString(R.string.generic_description));
|
||||
writeCommaSeparatedLine(track.getName(), track.getCategory(), track.getDescription());
|
||||
writeCommaSeparatedLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFooter() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginWaypoints() {
|
||||
writeCommaSeparatedLine(context.getString(R.string.generic_name),
|
||||
context.getString(R.string.marker_edit_marker_type_hint),
|
||||
context.getString(R.string.generic_description),
|
||||
context.getString(R.string.description_location_latitude),
|
||||
context.getString(R.string.description_location_longitude),
|
||||
context.getString(R.string.description_location_altitude),
|
||||
context.getString(R.string.description_location_bearing),
|
||||
context.getString(R.string.description_location_accuracy),
|
||||
context.getString(R.string.description_location_speed),
|
||||
context.getString(R.string.description_time));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndWaypoints() {
|
||||
writeCommaSeparatedLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeWaypoint(Waypoint waypoint) {
|
||||
Location location = waypoint.getLocation();
|
||||
writeCommaSeparatedLine(waypoint.getName(),
|
||||
waypoint.getCategory(),
|
||||
waypoint.getDescription(),
|
||||
Double.toString(location.getLatitude()),
|
||||
Double.toString(location.getLongitude()),
|
||||
Double.toString(location.getAltitude()),
|
||||
Double.toString(location.getBearing()),
|
||||
SHORT_FORMAT.format(location.getAccuracy()),
|
||||
SHORT_FORMAT.format(location.getSpeed()),
|
||||
StringUtils.formatDateTimeIso8601(location.getTime()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTrack(Location firstPoint) {
|
||||
writeCommaSeparatedLine(context.getString(R.string.description_track_segment),
|
||||
context.getString(R.string.description_track_point),
|
||||
context.getString(R.string.description_location_latitude),
|
||||
context.getString(R.string.description_location_longitude),
|
||||
context.getString(R.string.description_location_altitude),
|
||||
context.getString(R.string.description_location_bearing),
|
||||
context.getString(R.string.description_location_accuracy),
|
||||
context.getString(R.string.description_location_speed),
|
||||
context.getString(R.string.description_time),
|
||||
context.getString(R.string.description_sensor_power),
|
||||
context.getString(R.string.description_sensor_cadence),
|
||||
context.getString(R.string.description_sensor_heart_rate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTrack(Location lastPoint) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeOpenSegment() {
|
||||
segmentIndex++;
|
||||
pointIndex = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeCloseSegment() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLocation(Location location) {
|
||||
String power = null;
|
||||
String cadence = null;
|
||||
String heartRate = null;
|
||||
if (location instanceof MyTracksLocation) {
|
||||
SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
|
||||
|
||||
if (sensorDataSet != null) {
|
||||
if (sensorDataSet.hasPower()) {
|
||||
SensorData sensorData = sensorDataSet.getPower();
|
||||
if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
|
||||
power = Double.toString(sensorData.getValue());
|
||||
}
|
||||
}
|
||||
if (sensorDataSet.hasCadence()) {
|
||||
SensorData sensorData = sensorDataSet.getCadence();
|
||||
if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
|
||||
cadence = Double.toString(sensorData.getValue());
|
||||
}
|
||||
}
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
SensorData sensorData = sensorDataSet.getHeartRate();
|
||||
if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
|
||||
heartRate = Double.toString(sensorData.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pointIndex++;
|
||||
writeCommaSeparatedLine(Integer.toString(segmentIndex),
|
||||
Integer.toString(pointIndex),
|
||||
Double.toString(location.getLatitude()),
|
||||
Double.toString(location.getLongitude()),
|
||||
Double.toString(location.getAltitude()),
|
||||
Double.toString(location.getBearing()),
|
||||
SHORT_FORMAT.format(location.getAccuracy()),
|
||||
SHORT_FORMAT.format(location.getSpeed()),
|
||||
StringUtils.formatDateTimeIso8601(location.getTime()),
|
||||
power,
|
||||
cadence,
|
||||
heartRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single line of a CSV file.
|
||||
*
|
||||
* @param values the values to be written as CSV
|
||||
*/
|
||||
private void writeCommaSeparatedLine(String... values) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean isFirst = true;
|
||||
for (String value : values) {
|
||||
if (!isFirst) {
|
||||
builder.append(',');
|
||||
}
|
||||
isFirst = false;
|
||||
|
||||
builder.append('"');
|
||||
if (value != null) {
|
||||
builder.append(value.replaceAll("\"", "\"\""));
|
||||
}
|
||||
builder.append('"');
|
||||
}
|
||||
printWriter.println(builder.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
||||
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
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;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
/**
|
||||
* Imports GPX file to My Tracks.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Steffen Horlacher
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class GpxImporter extends DefaultHandler {
|
||||
|
||||
// GPX tag names and attributes
|
||||
private static final String TAG_ALTITUDE = "ele";
|
||||
private static final String TAG_DESCRIPTION = "desc";
|
||||
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 Object TAG_TRACK_SEGMENT = "trkseg";
|
||||
|
||||
private static final String ATT_LAT = "lat";
|
||||
private static final String ATT_LON = "lon";
|
||||
|
||||
// The maximum number of buffered locations for bulk-insertion
|
||||
private static final int MAX_BUFFERED_LOCATIONS = 512;
|
||||
|
||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||
private final int minRecordingDistance;
|
||||
|
||||
// List of successfully imported track ids
|
||||
private final List<Long> tracksIds;
|
||||
|
||||
// The SAX locator to get the current line information
|
||||
private Locator locator;
|
||||
|
||||
// The current element content
|
||||
private String content;
|
||||
|
||||
// True if if we're inside a track's xml element
|
||||
private boolean isInTrackElement = false;
|
||||
|
||||
// The current child depth for the current track
|
||||
private int trackChildDepth = 0;
|
||||
|
||||
// The current track
|
||||
private Track track;
|
||||
|
||||
// True if the current track parsing is finished
|
||||
private boolean isCurrentTrackFinished = true;
|
||||
|
||||
// The number of track segments processed for the current track
|
||||
private int numberOfTrackSegments = 0;
|
||||
|
||||
// The number of locations processed for the current track
|
||||
private int numberOfLocations = 0;
|
||||
|
||||
// The trip statistics updater for the current track
|
||||
private TripStatisticsUpdater tripStatisticsUpdater;
|
||||
|
||||
// True if the current track has a start time
|
||||
private boolean hasStartTime;
|
||||
|
||||
// The import time
|
||||
private long importTime;
|
||||
|
||||
// The current location
|
||||
private Location location;
|
||||
|
||||
// The last location in the current segment
|
||||
private Location lastLocationInSegment;
|
||||
|
||||
// The buffered locations
|
||||
private Location[] bufferedLocations = new Location[MAX_BUFFERED_LOCATIONS];
|
||||
|
||||
// The number of buffered locations
|
||||
private int numBufferedLocations = 0;
|
||||
|
||||
/**
|
||||
* Reads GPS tracks from a GPX file and writes tracks and their coordinates to
|
||||
* the database.
|
||||
*
|
||||
* @param inputStream the input stream for the GPX file
|
||||
* @param myTracksProviderUtils my tracks provider utils
|
||||
* @param minRecordingDistance the min recording distance
|
||||
* @return long[] array of track ids written to the database.
|
||||
*/
|
||||
public static long[] importGPXFile(InputStream inputStream,
|
||||
MyTracksProviderUtils myTracksProviderUtils, int minRecordingDistance)
|
||||
throws ParserConfigurationException, SAXException, IOException {
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
GpxImporter gpxImporter = new GpxImporter(myTracksProviderUtils, minRecordingDistance);
|
||||
long[] trackIds = new long[0];
|
||||
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
saxParser.parse(inputStream, gpxImporter);
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
Log.d(Constants.TAG, "Total import time: " + (end - start) + "ms");
|
||||
|
||||
trackIds = gpxImporter.getImportedTrackIds();
|
||||
if (trackIds.length == 0) {
|
||||
throw new IOException("No track imported.");
|
||||
}
|
||||
} finally {
|
||||
// Delete the current track if not finished
|
||||
gpxImporter.rollbackUnfinishedTracks();
|
||||
}
|
||||
return trackIds;
|
||||
}
|
||||
|
||||
public GpxImporter(MyTracksProviderUtils myTracksProviderUtils, int minRecordingDistance) {
|
||||
this.myTracksProviderUtils = myTracksProviderUtils;
|
||||
this.minRecordingDistance = minRecordingDistance;
|
||||
tracksIds = new ArrayList<Long>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||
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 void startElement(String uri, String localName, String name, Attributes attributes)
|
||||
throws SAXException {
|
||||
if (isInTrackElement) {
|
||||
trackChildDepth++;
|
||||
if (localName.equals(TAG_TRACK)) {
|
||||
throw new SAXException(createErrorMessage("Invalid GPX. Already inside a track."));
|
||||
} else if (localName.equals(TAG_TRACK_SEGMENT)) {
|
||||
onTrackSegmentElementStart();
|
||||
} else if (localName.equals(TAG_TRACK_POINT)) {
|
||||
onTrackPointElementStart(attributes);
|
||||
}
|
||||
} else if (localName.equals(TAG_TRACK)) {
|
||||
isInTrackElement = true;
|
||||
trackChildDepth = 0;
|
||||
onTrackElementStart();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String name) throws SAXException {
|
||||
if (!isInTrackElement) {
|
||||
content = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (localName.equals(TAG_TRACK)) {
|
||||
onTrackElementEnd();
|
||||
isInTrackElement = false;
|
||||
trackChildDepth = 0;
|
||||
} else if (localName.equals(TAG_NAME)) {
|
||||
// we are only interested in the first level name element
|
||||
if (trackChildDepth == 1) {
|
||||
onNameElementEnd();
|
||||
}
|
||||
} else if (localName.equals(TAG_DESCRIPTION)) {
|
||||
// we are only interested in the first level description element
|
||||
if (trackChildDepth == 1) {
|
||||
onDescriptionElementEnd();
|
||||
}
|
||||
} else if (localName.equals(TAG_TRACK_SEGMENT)) {
|
||||
onTrackSegmentElementEnd();
|
||||
} else if (localName.equals(TAG_TRACK_POINT)) {
|
||||
onTrackPointElementEnd();
|
||||
} else if (localName.equals(TAG_ALTITUDE)) {
|
||||
onAltitudeElementEnd();
|
||||
} else if (localName.equals(TAG_TIME)) {
|
||||
onTimeElementEnd();
|
||||
}
|
||||
trackChildDepth--;
|
||||
|
||||
// reset element content
|
||||
content = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocumentLocator(Locator locator) {
|
||||
this.locator = locator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolls back last track if possible.
|
||||
*/
|
||||
public void rollbackUnfinishedTracks() {
|
||||
if (!isCurrentTrackFinished && track != null) {
|
||||
myTracksProviderUtils.deleteTrack(track.getId());
|
||||
isCurrentTrackFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On track element start.
|
||||
*/
|
||||
private void onTrackElementStart() {
|
||||
track = new Track();
|
||||
Uri uri = myTracksProviderUtils.insertTrack(track);
|
||||
track.setId(Long.parseLong(uri.getLastPathSegment()));
|
||||
isCurrentTrackFinished = false;
|
||||
numberOfTrackSegments = 0;
|
||||
numberOfLocations = 0;
|
||||
tripStatisticsUpdater = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On track element end.
|
||||
*/
|
||||
private void onTrackElementEnd() {
|
||||
flushPoints();
|
||||
if (tripStatisticsUpdater == null) {
|
||||
long now = System.currentTimeMillis();
|
||||
tripStatisticsUpdater = new TripStatisticsUpdater(now);
|
||||
tripStatisticsUpdater.updateTime(now);
|
||||
}
|
||||
track.setStopId(getLastPointId());
|
||||
track.setTripStatistics(tripStatisticsUpdater.getTripStatistics());
|
||||
track.setNumberOfPoints(numberOfLocations);
|
||||
myTracksProviderUtils.updateTrack(track);
|
||||
tracksIds.add(track.getId());
|
||||
isCurrentTrackFinished = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* On name element end.
|
||||
*/
|
||||
private void onNameElementEnd() {
|
||||
if (content != null) {
|
||||
track.setName(content.toString().trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On description element end.
|
||||
*/
|
||||
private void onDescriptionElementEnd() {
|
||||
if (content != null) {
|
||||
track.setDescription(content.toString().trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On track segment start.
|
||||
*/
|
||||
private void onTrackSegmentElementStart() {
|
||||
numberOfTrackSegments++;
|
||||
// If not the first segment, add a pause separator if there is at least one
|
||||
// location in the last segment
|
||||
if (numberOfTrackSegments > 1 && lastLocationInSegment != null) {
|
||||
insertPoint(createNewLocation(
|
||||
TrackRecordingService.PAUSE_LATITUDE, 0.0, lastLocationInSegment.getTime()));
|
||||
}
|
||||
location = null;
|
||||
lastLocationInSegment = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On track segment element end.
|
||||
*/
|
||||
private void onTrackSegmentElementEnd() {
|
||||
// Nothing needs to be done
|
||||
}
|
||||
|
||||
/**
|
||||
* On track point element start.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
*/
|
||||
private void onTrackPointElementStart(Attributes attributes) throws SAXException {
|
||||
if (location != null) {
|
||||
throw new SAXException(createErrorMessage("Found a track point inside another one."));
|
||||
}
|
||||
String latitude = attributes.getValue(ATT_LAT);
|
||||
String longitude = attributes.getValue(ATT_LON);
|
||||
|
||||
if (latitude == null || longitude == null) {
|
||||
throw new SAXException(createErrorMessage("Point with no longitude or latitude."));
|
||||
}
|
||||
double latitudeValue;
|
||||
double longitudeValue;
|
||||
try {
|
||||
latitudeValue = Double.parseDouble(latitude);
|
||||
longitudeValue = Double.parseDouble(longitude);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(
|
||||
createErrorMessage("Unable to parse latitude/longitude: " + latitude + "/" + longitude),
|
||||
e);
|
||||
}
|
||||
|
||||
location = createNewLocation(latitudeValue, longitudeValue, -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* On track point element end.
|
||||
*/
|
||||
private void onTrackPointElementEnd() throws SAXException {
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
||||
}
|
||||
|
||||
if (numberOfTrackSegments > 1 && lastLocationInSegment == null) {
|
||||
// If not the first segment, add a resume separator before adding the
|
||||
// first location.
|
||||
insertPoint(
|
||||
createNewLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, location.getTime()));
|
||||
}
|
||||
|
||||
// insert in db
|
||||
insertPoint(location);
|
||||
|
||||
// set the start id if necessary
|
||||
if (track.getStartId() == -1L) {
|
||||
track.setStartId(getFirstPointId());
|
||||
}
|
||||
|
||||
lastLocationInSegment = location;
|
||||
location = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On altitude element end.
|
||||
*/
|
||||
private void onAltitudeElementEnd() throws SAXException {
|
||||
if (location == null || content == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
location.setAltitude(Double.parseDouble(content));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage("Unable to parse altitude: " + content), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On time element end. Sets location time and doing additional calculations
|
||||
* as this is the last value required for the location. Also sets the start
|
||||
* time for the trip statistics builder as there is no start time in the track
|
||||
* root element.
|
||||
*/
|
||||
private void onTimeElementEnd() throws SAXException {
|
||||
if (location == null || content == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the time
|
||||
long time;
|
||||
try {
|
||||
time = StringUtils.getTime(content.trim());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SAXException(createErrorMessage("Unable to parse time: " + content), e);
|
||||
}
|
||||
location.setTime(time);
|
||||
|
||||
// Calculate derived attributes from previous point
|
||||
if (lastLocationInSegment != null && lastLocationInSegment.getTime() != 0) {
|
||||
long timeDifference = time - lastLocationInSegment.getTime();
|
||||
|
||||
// check for negative time change
|
||||
if (timeDifference <= 0) {
|
||||
Log.w(Constants.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.
|
||||
*/
|
||||
float speed = lastLocationInSegment.distanceTo(location) * 1000.0f / timeDifference;
|
||||
location.setSpeed(speed);
|
||||
}
|
||||
location.setBearing(lastLocationInSegment.bearingTo(location));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new location
|
||||
* @param latitude location latitude
|
||||
* @param longitude location longitude
|
||||
* @param time location time
|
||||
*/
|
||||
private Location createNewLocation(double latitude, double longitude, long time) {
|
||||
Location loc = new Location(LocationManager.GPS_PROVIDER);
|
||||
loc.setLatitude(latitude);
|
||||
loc.setLongitude(longitude);
|
||||
loc.setAltitude(0.0f);
|
||||
loc.setTime(time);
|
||||
loc.removeAccuracy();
|
||||
loc.removeBearing();
|
||||
loc.removeSpeed();
|
||||
return loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a point.
|
||||
*
|
||||
* @param newLocation the location
|
||||
*/
|
||||
private void insertPoint(Location newLocation) {
|
||||
if (tripStatisticsUpdater == null) {
|
||||
hasStartTime = newLocation.getTime() != -1L;
|
||||
importTime = System.currentTimeMillis();
|
||||
tripStatisticsUpdater = new TripStatisticsUpdater(
|
||||
hasStartTime ? newLocation.getTime() : importTime);
|
||||
}
|
||||
if (!hasStartTime) {
|
||||
newLocation.setTime(importTime);
|
||||
}
|
||||
tripStatisticsUpdater.addLocation(newLocation, minRecordingDistance);
|
||||
|
||||
bufferedLocations[numBufferedLocations] = newLocation;
|
||||
numBufferedLocations++;
|
||||
numberOfLocations++;
|
||||
|
||||
if (numBufferedLocations >= MAX_BUFFERED_LOCATIONS) {
|
||||
flushPoints();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the points to the database.
|
||||
*/
|
||||
private void flushPoints() {
|
||||
if (numBufferedLocations <= 0) {
|
||||
return;
|
||||
}
|
||||
myTracksProviderUtils.bulkInsertTrackPoint(
|
||||
bufferedLocations, numBufferedLocations, track.getId());
|
||||
numBufferedLocations = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the imported track ids.
|
||||
*/
|
||||
private long[] getImportedTrackIds() {
|
||||
long[] result = new long[tracksIds.size()];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = tracksIds.get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first point id inserted into the database.
|
||||
*/
|
||||
private long getFirstPointId() {
|
||||
flushPoints();
|
||||
return myTracksProviderUtils.getFirstTrackPointId(track.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last point id inserted into the database.
|
||||
*/
|
||||
private long getLastPointId() {
|
||||
flushPoints();
|
||||
return myTracksProviderUtils.getLastTrackPointId(track.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error message.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
private String createErrorMessage(String message) {
|
||||
return String.format(Locale.US, "Parsing error at line: %d column: %d. %s",
|
||||
locator.getLineNumber(), locator.getColumnNumber(), message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Write track as GPX to a file.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class GpxTrackWriter implements TrackFormatWriter {
|
||||
|
||||
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 Context context;
|
||||
private Track track;
|
||||
private PrintWriter printWriter;
|
||||
|
||||
public GpxTrackWriter(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return TrackFileFormat.GPX.getExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Track aTrack, OutputStream outputStream) {
|
||||
this.track = aTrack;
|
||||
this.printWriter = new PrintWriter(outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (printWriter != null) {
|
||||
printWriter.close();
|
||||
printWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeHeader() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
printWriter.println("<gpx");
|
||||
printWriter.println("version=\"1.1\"");
|
||||
printWriter.println(
|
||||
"creator=\"" + context.getString(R.string.send_google_by_my_tracks, "", "") + "\"");
|
||||
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>");
|
||||
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 writeBeginTrack(Location firstLocation) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<trk>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||
printWriter.println("<extensions><topografix:color>c0c0c0</topografix:color></extensions>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTrack(Location lastLocation) {
|
||||
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) + ">");
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
|
||||
printWriter.println("</trkpt>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginWaypoints() {
|
||||
// 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) + ">");
|
||||
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(
|
||||
"<desc>" + StringUtils.formatCData(waypoint.getDescription()) + "</desc>");
|
||||
printWriter.println("</wpt>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,358 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.content.DescriptionGenerator;
|
||||
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorData;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
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 TrackFormatWriter {
|
||||
|
||||
/**
|
||||
* ID of the KML feature to play a tour.
|
||||
*/
|
||||
public static final String TOUR_FEATURE_ID = "tour";
|
||||
|
||||
private static final String WAYPOINT_STYLE = "waypoint";
|
||||
private static final String STATISTICS_STYLE = "statistics";
|
||||
private static final String START_STYLE = "start";
|
||||
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 DescriptionGenerator descriptionGenerator;
|
||||
private Track track;
|
||||
private PrintWriter printWriter;
|
||||
private ArrayList<Integer> powerList = new ArrayList<Integer>();
|
||||
private ArrayList<Integer> cadenceList = new ArrayList<Integer>();
|
||||
private ArrayList<Integer> heartRateList = new ArrayList<Integer>();
|
||||
private boolean hasPower;
|
||||
private boolean hasCadence;
|
||||
private boolean hasHeartRate;
|
||||
|
||||
public KmlTrackWriter(Context context) {
|
||||
this(context, new DescriptionGeneratorImpl(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
KmlTrackWriter(Context context, DescriptionGenerator descriptionGenerator) {
|
||||
this.context = context;
|
||||
this.descriptionGenerator = descriptionGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return TrackFileFormat.KML.getExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Track aTrack, OutputStream outputStream) {
|
||||
this.track = aTrack;
|
||||
this.printWriter = new PrintWriter(outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (printWriter != null) {
|
||||
printWriter.close();
|
||||
printWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeHeader() {
|
||||
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:atom=\"http://www.w3.org/2005/Atom\"");
|
||||
printWriter.println("xmlns:gx=\"http://www.google.com/kml/ext/2.2\">");
|
||||
printWriter.println("<Document>");
|
||||
printWriter.println("<open>1</open>");
|
||||
printWriter.println("<visibility>1</visibility>");
|
||||
printWriter.println(
|
||||
"<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<atom:author><atom:name>" + StringUtils.formatCData(
|
||||
context.getString(R.string.send_google_by_my_tracks, "", ""))
|
||||
+ "</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() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println(
|
||||
"<Folder><name>" + StringUtils.formatCData(context.getString(R.string.menu_markers))
|
||||
+ "</name>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndWaypoints() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</Folder>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeWaypoint(Waypoint waypoint) {
|
||||
if (printWriter != null) {
|
||||
String styleName = waypoint.getType() == Waypoint.TYPE_STATISTICS ? STATISTICS_STYLE
|
||||
: WAYPOINT_STYLE;
|
||||
writePlacemark(
|
||||
waypoint.getName(), waypoint.getDescription(), styleName, waypoint.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTrack(Location firstLocation) {
|
||||
if (printWriter != null) {
|
||||
String name = context.getString(R.string.marker_label_start, track.getName());
|
||||
writePlacemark(name, track.getDescription(), START_STYLE, firstLocation);
|
||||
printWriter.println("<Placemark id=\"" + TOUR_FEATURE_ID + "\">");
|
||||
printWriter.println(
|
||||
"<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
|
||||
printWriter.println("<gx:MultiTrack>");
|
||||
printWriter.println("<altitudeMode>absolute</altitudeMode>");
|
||||
printWriter.println("<gx:interpolate>1</gx:interpolate>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTrack(Location lastLocation) {
|
||||
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, lastLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@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>" + location.getLongitude() + " " + location.getLatitude() + " "
|
||||
+ location.getAltitude() + "</gx:coord>");
|
||||
if (location instanceof MyTracksLocation) {
|
||||
SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
|
||||
int power = -1;
|
||||
int cadence = -1;
|
||||
int heartRate = -1;
|
||||
|
||||
if (sensorDataSet != null) {
|
||||
if (sensorDataSet.hasPower()) {
|
||||
SensorData sensorData = sensorDataSet.getPower();
|
||||
if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
|
||||
hasPower = true;
|
||||
power = sensorData.getValue();
|
||||
}
|
||||
}
|
||||
if (sensorDataSet.hasCadence()) {
|
||||
SensorData sensorData = sensorDataSet.getCadence();
|
||||
if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
|
||||
hasCadence = true;
|
||||
cadence = sensorData.getValue();
|
||||
}
|
||||
}
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
SensorData sensorData = sensorDataSet.getHeartRate();
|
||||
if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
|
||||
hasHeartRate = true;
|
||||
heartRate = sensorData.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
powerList.add(power);
|
||||
cadenceList.add(cadence);
|
||||
heartRateList.add(heartRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the sensor data.
|
||||
*
|
||||
* @param list a list of sensor data
|
||||
* @param name the name of the sensor data
|
||||
*/
|
||||
private void writeSensorData(ArrayList<Integer> 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 of the placemark
|
||||
* @param description the description
|
||||
* @param styleName the style name
|
||||
* @param location the location
|
||||
*/
|
||||
private void writePlacemark(
|
||||
String name, String description, String styleName, Location location) {
|
||||
if (location != null) {
|
||||
printWriter.println("<Placemark>");
|
||||
printWriter.println(
|
||||
"<description>" + StringUtils.formatCData(description) + "</description>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
||||
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
|
||||
printWriter.println("<Point>");
|
||||
printWriter.println(
|
||||
"<coordinates>" + location.getLongitude() + "," + location.getLatitude() + ","
|
||||
+ location.getAltitude() + "</coordinates>");
|
||||
printWriter.println("</Point>");
|
||||
printWriter.println("</Placemark>");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,199 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.util.DialogUtils;
|
||||
import com.google.android.apps.mytracks.util.IntentUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
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 java.io.File;
|
||||
|
||||
/**
|
||||
* An activity for saving tracks to the SD card. 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_ID = "track_id";
|
||||
public static final String EXTRA_PLAY_TRACK = "play_track";
|
||||
|
||||
public static final String GOOGLE_EARTH_KML_MIME_TYPE = "application/vnd.google-earth.kml+xml";
|
||||
public static final String GOOGLE_EARTH_PACKAGE = "com.google.earth";
|
||||
public static final String GOOGLE_EARTH_MARKET_URL = "market://details?id="
|
||||
+ GOOGLE_EARTH_PACKAGE;
|
||||
private static final String
|
||||
GOOGLE_EARTH_TOUR_FEATURE_ID = "com.google.earth.EXTRA.tour_feature_id";
|
||||
private static final String GOOGLE_EARTH_CLASS = "com.google.earth.EarthActivity";
|
||||
|
||||
private static final int DIALOG_PROGRESS_ID = 0;
|
||||
private static final int DIALOG_RESULT_ID = 1;
|
||||
|
||||
private TrackFileFormat trackFileFormat;
|
||||
private long trackId;
|
||||
private boolean playTrack;
|
||||
|
||||
private SaveAsyncTask saveAsyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
// result from the AsyncTask
|
||||
private boolean success;
|
||||
|
||||
// message id from the AsyncTask
|
||||
private int messageId;
|
||||
|
||||
// saved file path from the AsyncTask
|
||||
private String savedPath;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT);
|
||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false);
|
||||
|
||||
Object retained = getLastNonConfigurationInstance();
|
||||
if (retained instanceof SaveAsyncTask) {
|
||||
saveAsyncTask = (SaveAsyncTask) retained;
|
||||
saveAsyncTask.setActivity(this);
|
||||
} else {
|
||||
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackId, playTrack);
|
||||
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.external_storage_save_progress_message,
|
||||
new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
saveAsyncTask.cancel(true);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
return progressDialog;
|
||||
case DIALOG_RESULT_ID:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
.setIcon(success
|
||||
? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert)
|
||||
.setMessage(messageId)
|
||||
.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(success ? R.string.generic_success_title : R.string.generic_error_title);
|
||||
|
||||
if (success && trackId != -1L && !playTrack && 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, trackId, savedPath, trackFileFormat);
|
||||
startActivity(
|
||||
Intent.createChooser(intent, getString(R.string.share_track_picker_title)));
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
return builder.create();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param isSuccess true if the AsyncTask is successful
|
||||
* @param aMessageId the id of the AsyncTask message
|
||||
* @param aSavedPath the path of the saved file
|
||||
*/
|
||||
public void onAsyncTaskCompleted(boolean isSuccess, int aMessageId, String aSavedPath) {
|
||||
this.success = isSuccess;
|
||||
this.messageId = aMessageId;
|
||||
this.savedPath = aSavedPath;
|
||||
removeDialog(DIALOG_PROGRESS_ID);
|
||||
if (success && playTrack && savedPath != null) {
|
||||
Intent intent = new Intent()
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(GOOGLE_EARTH_TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID)
|
||||
.setClassName(GOOGLE_EARTH_PACKAGE, GOOGLE_EARTH_CLASS)
|
||||
.setDataAndType(Uri.fromFile(new File(savedPath)), GOOGLE_EARTH_KML_MIME_TYPE);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} else {
|
||||
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,223 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.util.FileUtils;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Async Task to save tracks to the SD card.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private static final String TAG = SaveAsyncTask.class.getSimpleName();
|
||||
|
||||
private SaveActivity saveActivity;
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
private final long trackId;
|
||||
private final boolean useTempDir;
|
||||
|
||||
private final Context context;
|
||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||
private WakeLock wakeLock;
|
||||
private TrackWriter trackWriter;
|
||||
|
||||
// true if the AsyncTask result is success
|
||||
private boolean success;
|
||||
|
||||
// true if the AsyncTask has completed
|
||||
private boolean completed;
|
||||
|
||||
// message id to return to the activity
|
||||
private int messageId;
|
||||
|
||||
// saved file path to return to the activity
|
||||
private String savedPath;
|
||||
|
||||
/**
|
||||
* Creates an AsyncTask.
|
||||
*
|
||||
* @param saveActivity the activity currently associated with this task.
|
||||
* @param trackFileFormat the track format
|
||||
* @param trackId the track id
|
||||
* @param useTempDir true to use the temp directory
|
||||
*/
|
||||
public SaveAsyncTask(SaveActivity saveActivity, TrackFileFormat trackFileFormat, long trackId,
|
||||
boolean useTempDir) {
|
||||
this.saveActivity = saveActivity;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.trackId = trackId;
|
||||
this.useTempDir = useTempDir;
|
||||
context = saveActivity.getApplicationContext();
|
||||
|
||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(saveActivity);
|
||||
|
||||
// Get the wake lock if not recording or paused
|
||||
if (PreferencesUtils.getLong(saveActivity, R.string.recording_track_id_key)
|
||||
== PreferencesUtils.RECORDING_TRACK_ID_DEFAULT || PreferencesUtils.getBoolean(saveActivity,
|
||||
R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT)) {
|
||||
wakeLock = SystemUtils.acquireWakeLock(saveActivity, wakeLock);
|
||||
}
|
||||
|
||||
trackWriter = null;
|
||||
success = false;
|
||||
completed = false;
|
||||
messageId = R.string.external_storage_save_error;
|
||||
savedPath = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current activity associated with this AyncTask.
|
||||
*
|
||||
* @param saveActivity the current activity, can be null
|
||||
*/
|
||||
public void setActivity(SaveActivity saveActivity) {
|
||||
this.saveActivity = saveActivity;
|
||||
if (completed && saveActivity != null) {
|
||||
saveActivity.onAsyncTaskCompleted(success, messageId, savedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (saveActivity != null) {
|
||||
saveActivity.showProgressDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
if (trackId != -1L) {
|
||||
return saveOneTrack(trackId, true);
|
||||
} else {
|
||||
return saveAllTracks();
|
||||
}
|
||||
} finally {
|
||||
// Release the wake lock if obtained
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves one track.
|
||||
*
|
||||
* @param id the track id
|
||||
* @param updateSavingProgress true to update the saving progress
|
||||
*/
|
||||
private Boolean saveOneTrack(long id, final boolean updateSavingProgress) {
|
||||
trackWriter = TrackWriterFactory.newWriter(
|
||||
context, myTracksProviderUtils, id, trackFileFormat);
|
||||
if (trackWriter == null) {
|
||||
Log.e(TAG, "Track writer is null");
|
||||
return false;
|
||||
}
|
||||
if (useTempDir) {
|
||||
String dirName = FileUtils.buildExternalDirectoryPath(trackFileFormat.getExtension(), "tmp");
|
||||
trackWriter.setDirectory(new File(dirName));
|
||||
}
|
||||
trackWriter.setOnWriteListener(new TrackWriter.OnWriteListener() {
|
||||
@Override
|
||||
public void onWrite(int number, int max) {
|
||||
// Update the progress dialog once every 500 points
|
||||
if (updateSavingProgress && number % 500 == 0) {
|
||||
publishProgress(number, max);
|
||||
}
|
||||
}
|
||||
});
|
||||
trackWriter.writeTrack();
|
||||
messageId = trackWriter.getErrorMessage();
|
||||
savedPath = trackWriter.getAbsolutePath();
|
||||
return trackWriter.wasSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all the tracks.
|
||||
*/
|
||||
private Boolean saveAllTracks() {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = myTracksProviderUtils.getTrackCursor(null, null, TracksColumns._ID);
|
||||
if (cursor == null) {
|
||||
messageId = R.string.external_storage_save_error_no_track;
|
||||
return false;
|
||||
}
|
||||
int count = cursor.getCount();
|
||||
if (count == 0) {
|
||||
messageId = R.string.external_storage_save_error_no_track;
|
||||
return false;
|
||||
}
|
||||
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
cursor.moveToPosition(i);
|
||||
long id = cursor.getLong(idIndex);
|
||||
if (!saveOneTrack(id, false)) {
|
||||
return false;
|
||||
}
|
||||
publishProgress(i + 1, count);
|
||||
}
|
||||
messageId = R.string.external_storage_save_success;
|
||||
return true;
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (saveActivity != null) {
|
||||
saveActivity.setProgressDialogValue(values[0], values[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
success = result;
|
||||
completed = true;
|
||||
if (saveActivity != null) {
|
||||
saveActivity.onAsyncTaskCompleted(success, messageId, savedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
if (trackWriter != null) {
|
||||
trackWriter.stopWriteTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Write track as TCX to a file. See http://developer.garmin.com/schemas/tcx/v2/
|
||||
* for info on TCX. <br>
|
||||
* The TCX file output is verified by uploading the file to
|
||||
* http://connect.garmin.com/.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Dominik Rttsches
|
||||
*/
|
||||
public class TcxTrackWriter implements TrackFormatWriter {
|
||||
|
||||
/**
|
||||
* TCX sport type. See the TCX spec.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private enum SportType {
|
||||
RUNNING("Running"),
|
||||
BIKING("Biking"),
|
||||
OTHER("Other");
|
||||
|
||||
private final String name;
|
||||
|
||||
private SportType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the sport type
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// My Tracks categories that are considered as TCX biking sport type.
|
||||
private static final int TCX_SPORT_BIKING_IDS[] = {
|
||||
R.string.activity_type_cycling,
|
||||
R.string.activity_type_dirt_bike,
|
||||
R.string.activity_type_mountain_biking,
|
||||
R.string.activity_type_road_biking,
|
||||
R.string.activity_type_track_cycling };
|
||||
|
||||
// My Tracks categories that are considered as TCX running sport type.
|
||||
private static final int TCX_SPORT_RUNNING_IDS[] = {
|
||||
R.string.activity_type_running,
|
||||
R.string.activity_type_speed_walking,
|
||||
R.string.activity_type_street_running,
|
||||
R.string.activity_type_track_running,
|
||||
R.string.activity_type_trail_running,
|
||||
R.string.activity_type_walking };
|
||||
|
||||
private final Context context;
|
||||
private Track track;
|
||||
private PrintWriter printWriter;
|
||||
private SportType sportType;
|
||||
|
||||
public TcxTrackWriter(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Track aTrack, OutputStream out) {
|
||||
this.track = aTrack;
|
||||
this.printWriter = new PrintWriter(out);
|
||||
this.sportType = getSportType(track.getCategory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (printWriter != null) {
|
||||
printWriter.close();
|
||||
printWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return TrackFileFormat.TCX.getExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeHeader() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
printWriter.println("<TrainingCenterDatabase"
|
||||
+ " xmlns=\"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2\"");
|
||||
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
||||
printWriter.println("xsi:schemaLocation="
|
||||
+ "\"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"
|
||||
+ " http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd\">");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFooter() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<Author xsi:type=\"Application_t\">");
|
||||
printWriter.println("<Name>"
|
||||
+ StringUtils.formatCData(context.getString(R.string.send_google_by_my_tracks, "", ""))
|
||||
+ "</Name>");
|
||||
// <Build>, <LangID>, and <PartNumber> are required by type=Application_t.
|
||||
printWriter.println("<Build>");
|
||||
writeVersion();
|
||||
printWriter.println("</Build>");
|
||||
printWriter.println("<LangID>" + Locale.getDefault().getLanguage() + "</LangID>");
|
||||
printWriter.println("<PartNumber>000-00000-00</PartNumber>");
|
||||
printWriter.println("</Author>");
|
||||
printWriter.println("</TrainingCenterDatabase>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTrack(Location firstPoint) {
|
||||
if (printWriter != null) {
|
||||
String startTime = StringUtils.formatDateTimeIso8601(
|
||||
track.getTripStatistics().getStartTime());
|
||||
long totalTimeInSeconds = track.getTripStatistics().getTotalTime() / 1000;
|
||||
|
||||
printWriter.println("<Activities>");
|
||||
printWriter.println("<Activity Sport=\"" + sportType.getName() + "\">");
|
||||
printWriter.println("<Id>" + startTime + "</Id>");
|
||||
printWriter.println("<Lap StartTime=\"" + startTime + "\">");
|
||||
printWriter.println("<TotalTimeSeconds>" + totalTimeInSeconds + "</TotalTimeSeconds>");
|
||||
printWriter.println("<DistanceMeters>" + track.getTripStatistics().getTotalDistance()
|
||||
+ "</DistanceMeters>");
|
||||
// <Calories> is required, just put in 0.
|
||||
printWriter.println("<Calories>0</Calories>");
|
||||
printWriter.println("<Intensity>Active</Intensity>");
|
||||
printWriter.println("<TriggerMethod>Manual</TriggerMethod>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTrack(Location lastPoint) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</Lap>");
|
||||
printWriter.println("<Notes>" + StringUtils.formatCData(track.getDescription()) + "</Notes>");
|
||||
printWriter.println("<Creator xsi:type=\"Device_t\">");
|
||||
printWriter.println("<Name>"
|
||||
+ StringUtils.formatCData(context.getString(R.string.send_google_by_my_tracks, "", ""))
|
||||
+ "</Name>");
|
||||
// <UnitId>, <ProductID>, and <Version> are required for type=Device_t.
|
||||
printWriter.println("<UnitId>0</UnitId>");
|
||||
printWriter.println("<ProductID>0</ProductID>");
|
||||
writeVersion();
|
||||
printWriter.println("</Creator>");
|
||||
printWriter.println("</Activity>");
|
||||
printWriter.println("</Activities>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeOpenSegment() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<Track>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeCloseSegment() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</Track>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLocation(Location location) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<Trackpoint>");
|
||||
printWriter.println("<Time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</Time>");
|
||||
printWriter.println("<Position>");
|
||||
printWriter.println("<LatitudeDegrees>" + location.getLatitude() + "</LatitudeDegrees>");
|
||||
printWriter.println("<LongitudeDegrees>" + location.getLongitude() + "</LongitudeDegrees>");
|
||||
printWriter.println("</Position>");
|
||||
printWriter.println("<AltitudeMeters>" + location.getAltitude() + "</AltitudeMeters>");
|
||||
|
||||
if (location instanceof MyTracksLocation) {
|
||||
SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
|
||||
if (sensorDataSet != null) {
|
||||
boolean heartRateAvailable = sensorDataSet.hasHeartRate()
|
||||
&& sensorDataSet.getHeartRate().hasValue()
|
||||
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING;
|
||||
boolean cadenceAvailable = sensorDataSet.hasCadence()
|
||||
&& sensorDataSet.getCadence().hasValue()
|
||||
&& sensorDataSet.getCadence().getState() == Sensor.SensorState.SENDING;
|
||||
boolean powerAvailable = sensorDataSet.hasPower()
|
||||
&& sensorDataSet.getPower().hasValue()
|
||||
&& sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING;
|
||||
|
||||
if (heartRateAvailable) {
|
||||
printWriter.println("<HeartRateBpm>");
|
||||
printWriter.println("<Value>" + sensorDataSet.getHeartRate().getValue() + "</Value>");
|
||||
printWriter.println("</HeartRateBpm>");
|
||||
}
|
||||
|
||||
// <Cadence> needs to be put before <Extensions>.
|
||||
// According to the TCX spec, <Cadence> is only for the biking sport
|
||||
// type. For others, use <RunCadence> in <Extensions>.
|
||||
if (cadenceAvailable && sportType == SportType.BIKING) {
|
||||
// The spec requires the max value be 254.
|
||||
printWriter.println(
|
||||
"<Cadence>" + Math.min(254, sensorDataSet.getCadence().getValue()) + "</Cadence>");
|
||||
}
|
||||
|
||||
if ((cadenceAvailable && sportType != SportType.BIKING) || powerAvailable) {
|
||||
printWriter.println("<Extensions>");
|
||||
printWriter.println(
|
||||
"<TPX xmlns=\"http://www.garmin.com/xmlschemas/ActivityExtension/v2\">");
|
||||
|
||||
// <RunCadence> needs to be put before <Watts>.
|
||||
if (cadenceAvailable && sportType != SportType.BIKING) {
|
||||
// The spec requires the max value to be 254.
|
||||
printWriter.println("<RunCadence>"
|
||||
+ Math.min(254, sensorDataSet.getCadence().getValue()) + "</RunCadence>");
|
||||
}
|
||||
|
||||
if (powerAvailable) {
|
||||
printWriter.println("<Watts>" + sensorDataSet.getPower().getValue() + "</Watts>");
|
||||
}
|
||||
printWriter.println("</TPX>");
|
||||
printWriter.println("</Extensions>");
|
||||
}
|
||||
}
|
||||
}
|
||||
printWriter.println("</Trackpoint>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginWaypoints() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndWaypoints() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeWaypoint(Waypoint waypoint) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the TCX Version.
|
||||
*/
|
||||
private void writeVersion() {
|
||||
// Split the My Tracks version code into VersionMajor, VersionMinor, and,
|
||||
// BuildMajor to fit the integer type requirement for these fields in the
|
||||
// TCX spec.
|
||||
String[] versionComponents = SystemUtils.getMyTracksVersion(context).split("\\.");
|
||||
int versionMajor = versionComponents.length > 0 ? Integer.valueOf(versionComponents[0]) : 0;
|
||||
int versionMinor = versionComponents.length > 1 ? Integer.valueOf(versionComponents[1]) : 0;
|
||||
int buildMajor = versionComponents.length > 2 ? Integer.valueOf(versionComponents[2]) : 0;
|
||||
|
||||
printWriter.println("<Version>");
|
||||
printWriter.println("<VersionMajor>" + versionMajor + "</VersionMajor>");
|
||||
printWriter.println("<VersionMinor>" + versionMinor + "</VersionMinor>");
|
||||
// According to TCX spec, these are optional. But http://connect.garmin.com
|
||||
// requires them.
|
||||
printWriter.println("<BuildMajor>" + buildMajor + "</BuildMajor>");
|
||||
printWriter.println("<BuildMinor>0</BuildMinor>");
|
||||
printWriter.println("</Version>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sport type from the category.
|
||||
*
|
||||
* @param category the category
|
||||
*/
|
||||
private SportType getSportType(String category) {
|
||||
category = category.trim();
|
||||
|
||||
// For tracks with localized category.
|
||||
for (int i : TCX_SPORT_RUNNING_IDS) {
|
||||
if (category.equalsIgnoreCase(context.getString(i))) {
|
||||
return SportType.RUNNING;
|
||||
}
|
||||
}
|
||||
for (int i : TCX_SPORT_BIKING_IDS) {
|
||||
if (category.equalsIgnoreCase(context.getString(i))) {
|
||||
return SportType.BIKING;
|
||||
}
|
||||
}
|
||||
|
||||
// For tracks without localized category.
|
||||
if (category.equalsIgnoreCase(SportType.RUNNING.getName())) {
|
||||
return SportType.RUNNING;
|
||||
} else if (category.equalsIgnoreCase(SportType.BIKING.getName())) {
|
||||
return SportType.BIKING;
|
||||
} else {
|
||||
return SportType.OTHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Interface for writing a track to file.
|
||||
*
|
||||
* The expected sequence of calls is:
|
||||
* <ol>
|
||||
* <li>{@link #prepare}
|
||||
* <li>{@link #writeHeader}
|
||||
* <li>{@link #writeBeginWaypoints}
|
||||
* <li>For each waypoint: {@link #writeWaypoint}
|
||||
* <li>{@link #writeEndWaypoints}
|
||||
* <li>{@link #writeBeginTrack}
|
||||
* <li>For each segment:
|
||||
* <ol>
|
||||
* <li>{@link #writeOpenSegment}
|
||||
* <li>For each location in the segment: {@link #writeLocation}
|
||||
* <li>{@link #writeCloseSegment}
|
||||
* </ol>
|
||||
* <li>{@link #writeEndTrack}
|
||||
* <li>{@link #writeFooter}
|
||||
* <li>{@link #close}
|
||||
* </ol>
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public interface TrackFormatWriter {
|
||||
|
||||
/**
|
||||
* Gets the file extension (i.e. gpx, kml, ...)
|
||||
*/
|
||||
public String getExtension();
|
||||
|
||||
/**
|
||||
* Sets up the writer to write the given track.
|
||||
*
|
||||
* @param track the track to write
|
||||
* @param outputStream the output stream to write the track to
|
||||
*/
|
||||
public void prepare(Track track, OutputStream outputStream);
|
||||
|
||||
/**
|
||||
* Closes the underlying file handler.
|
||||
*/
|
||||
public void close();
|
||||
|
||||
/**
|
||||
* Writes the header.
|
||||
*/
|
||||
public void writeHeader();
|
||||
|
||||
/**
|
||||
* Writes the footer.
|
||||
*/
|
||||
public void writeFooter();
|
||||
|
||||
/**
|
||||
* Writes the beginning of the waypoints.
|
||||
*/
|
||||
public void writeBeginWaypoints();
|
||||
|
||||
/**
|
||||
* Writes the end of the waypoints.
|
||||
*/
|
||||
public void writeEndWaypoints();
|
||||
|
||||
/**
|
||||
* Writes a waypoint.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
*/
|
||||
public void writeWaypoint(Waypoint waypoint);
|
||||
|
||||
/**
|
||||
* Writes the beginning of the track.
|
||||
*
|
||||
* @param firstLocation the first location
|
||||
*/
|
||||
public void writeBeginTrack(Location firstLocation);
|
||||
|
||||
/**
|
||||
* Writes the end of the track.
|
||||
*
|
||||
* @param lastLocation the last location
|
||||
*/
|
||||
public void writeEndTrack(Location lastLocation);
|
||||
|
||||
/**
|
||||
* Writes the statements necessary to open a new segment.
|
||||
*/
|
||||
public void writeOpenSegment();
|
||||
|
||||
/**
|
||||
* Writes the statements necessary to close a segment.
|
||||
*/
|
||||
public void writeCloseSegment();
|
||||
|
||||
/**
|
||||
* Writes a location.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
public void writeLocation(Location location);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Implementations of this class export tracks to the SD card. This class is
|
||||
* intended to be format-neutral - it handles creating the output file and
|
||||
* reading the track to be exported, but requires an instance of
|
||||
* {@link TrackFormatWriter} to actually format the data.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public interface TrackWriter {
|
||||
|
||||
/** This listener is used to signal track writes. */
|
||||
public interface OnWriteListener {
|
||||
/**
|
||||
* This method is invoked whenever a location within a track is written.
|
||||
* @param number the location number
|
||||
* @param max the maximum number of locations, for calculation of
|
||||
* completion percentage
|
||||
*/
|
||||
public void onWrite(int number, int max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener to be invoked for each location writer.
|
||||
*/
|
||||
void setOnWriteListener(OnWriteListener onWriteListener);
|
||||
|
||||
/**
|
||||
* Sets a custom directory where the file will be written.
|
||||
*/
|
||||
void setDirectory(File directory);
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the file which was created.
|
||||
*/
|
||||
String getAbsolutePath();
|
||||
|
||||
/**
|
||||
* Writes the given track id to the SD card.
|
||||
* This is blocking.
|
||||
*/
|
||||
void writeTrack();
|
||||
|
||||
/**
|
||||
* Stop any in-progress writes
|
||||
*/
|
||||
void stopWriteTrack();
|
||||
|
||||
/**
|
||||
* Returns true if the write completed successfully.
|
||||
*/
|
||||
boolean wasSuccess();
|
||||
|
||||
/**
|
||||
* Returns the error message (if any) generated by a writer failure.
|
||||
*/
|
||||
int getErrorMessage();
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A factory to produce track writers for any format.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackWriterFactory {
|
||||
|
||||
/**
|
||||
* Definition of all possible track formats.
|
||||
*/
|
||||
public enum TrackFileFormat implements Parcelable {
|
||||
GPX {
|
||||
@Override
|
||||
TrackFormatWriter newFormatWriter(Context context) {
|
||||
return new GpxTrackWriter(context);
|
||||
}
|
||||
},
|
||||
KML {
|
||||
@Override
|
||||
TrackFormatWriter newFormatWriter(Context context) {
|
||||
return new KmlTrackWriter(context);
|
||||
}
|
||||
},
|
||||
CSV {
|
||||
@Override
|
||||
public TrackFormatWriter newFormatWriter(Context context) {
|
||||
return new CsvTrackWriter(context);
|
||||
}
|
||||
},
|
||||
TCX {
|
||||
@Override
|
||||
public TrackFormatWriter newFormatWriter(Context context) {
|
||||
return new TcxTrackWriter(context);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeInt(ordinal());
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and returns a new format writer for each format.
|
||||
*/
|
||||
abstract TrackFormatWriter newFormatWriter(Context context);
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new track writer to write the track with the given ID.
|
||||
*
|
||||
* @param context the context in which the track will be read
|
||||
* @param providerUtils the data provider utils to read the track with
|
||||
* @param trackId the ID of the track to be written
|
||||
* @param format the output format to write in
|
||||
* @return the new track writer
|
||||
*/
|
||||
public static TrackWriter newWriter(Context context,
|
||||
MyTracksProviderUtils providerUtils,
|
||||
long trackId, TrackFileFormat format) {
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.d(TAG, "No track for " + trackId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newWriter(context, providerUtils, track, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new track writer to write the given track.
|
||||
*
|
||||
* @param context the context in which the track will be read
|
||||
* @param providerUtils the data provider utils to read the track with
|
||||
* @param track the track to be written
|
||||
* @param format the output format to write in
|
||||
* @return the new track writer
|
||||
*/
|
||||
private static TrackWriter newWriter(Context context,
|
||||
MyTracksProviderUtils providerUtils,
|
||||
Track track, TrackFileFormat format) {
|
||||
TrackFormatWriter writer = format.newFormatWriter(context);
|
||||
return new TrackWriterImpl(context, providerUtils, track, writer);
|
||||
}
|
||||
|
||||
private TrackWriterFactory() { }
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.util.FileUtils;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* This class exports tracks to the SD card. It is intended to be format-
|
||||
* neutral -- it handles creating the output file and reading the track to be
|
||||
* exported, but requires an instance of {@link TrackFormatWriter} to actually
|
||||
* format the data.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class TrackWriterImpl implements TrackWriter {
|
||||
private final Context context;
|
||||
private final MyTracksProviderUtils providerUtils;
|
||||
private final Track track;
|
||||
private final TrackFormatWriter writer;
|
||||
private boolean success = false;
|
||||
private int errorMessage = R.string.external_storage_save_error;
|
||||
private File directory = null;
|
||||
private File file = null;
|
||||
private OnWriteListener onWriteListener;
|
||||
private Thread writeThread;
|
||||
|
||||
TrackWriterImpl(Context context, MyTracksProviderUtils providerUtils,
|
||||
Track track, TrackFormatWriter writer) {
|
||||
this.context = context;
|
||||
this.providerUtils = providerUtils;
|
||||
this.track = track;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnWriteListener(OnWriteListener onWriteListener) {
|
||||
this.onWriteListener = onWriteListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDirectory(File directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAbsolutePath() {
|
||||
return file == null ? null : file.getAbsolutePath();
|
||||
}
|
||||
|
||||
private void writeTrackAsync() {
|
||||
writeThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
doWriteTrack();
|
||||
}
|
||||
};
|
||||
writeThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTrack() {
|
||||
writeTrackAsync();
|
||||
try {
|
||||
writeThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "Interrupted waiting for write to complete", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void doWriteTrack() {
|
||||
// Open the input and output
|
||||
success = false;
|
||||
errorMessage = R.string.external_storage_save_error;
|
||||
if (track != null) {
|
||||
if (openFile()) {
|
||||
try {
|
||||
writeDocument();
|
||||
} catch (InterruptedException e) {
|
||||
Log.i(Constants.TAG, "The track write was interrupted");
|
||||
if (file != null && !file.delete()) {
|
||||
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
|
||||
}
|
||||
success = false;
|
||||
errorMessage = R.string.external_storage_canceled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stopWriteTrack() {
|
||||
if (writeThread != null && writeThread.isAlive()) {
|
||||
Log.i(Constants.TAG, "Attempting to stop track write");
|
||||
writeThread.interrupt();
|
||||
|
||||
try {
|
||||
writeThread.join();
|
||||
Log.i(Constants.TAG, "Track write stopped");
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "Failed to wait for writer to stop", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper methods:
|
||||
* ===============
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runs the given runnable in the UI thread.
|
||||
*/
|
||||
protected void runOnUiThread(Runnable runnable) {
|
||||
if (context instanceof Activity) {
|
||||
((Activity) context).runOnUiThread(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the file and prepares the format writer for it.
|
||||
*
|
||||
* @return true on success, false otherwise (and errorMessage is set)
|
||||
*/
|
||||
protected boolean openFile() {
|
||||
if (!canWriteFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the file doesn't exist yet (possibly by changing the filename)
|
||||
String fileName = FileUtils.buildUniqueFileName(
|
||||
directory, track.getName(), writer.getExtension());
|
||||
if (fileName == null) {
|
||||
Log.e(Constants.TAG,
|
||||
"Unable to get a unique filename for " + track.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.i(Constants.TAG, "Writing track to: " + fileName);
|
||||
try {
|
||||
writer.prepare(track, newOutputStream(fileName));
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Failed to open output file.", e);
|
||||
errorMessage = R.string.external_storage_save_error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and returns whether we're ready to create the output file.
|
||||
*/
|
||||
protected boolean canWriteFile() {
|
||||
if (!FileUtils.isSdCardAvailable()) {
|
||||
Log.i(Constants.TAG, "Could not find SD card.");
|
||||
errorMessage = R.string.external_storage_error_no_storage;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (directory == null) {
|
||||
String dirName =
|
||||
FileUtils.buildExternalDirectoryPath(writer.getExtension());
|
||||
directory = newFile(dirName);
|
||||
}
|
||||
if (!FileUtils.ensureDirectoryExists(directory)) {
|
||||
Log.i(Constants.TAG, "Could not create export directory.");
|
||||
errorMessage = R.string.external_storage_save_error_create_dir;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new output stream to write to the given filename.
|
||||
*
|
||||
* @throws FileNotFoundException if the file could't be created
|
||||
*/
|
||||
protected OutputStream newOutputStream(String fileName)
|
||||
throws FileNotFoundException {
|
||||
file = new File(directory, fileName);
|
||||
return new FileOutputStream(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file object for the given path.
|
||||
*/
|
||||
protected File newFile(String path) {
|
||||
return new File(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the waypoints for the given track.
|
||||
*
|
||||
* @param trackId the ID of the track to write waypoints for
|
||||
*/
|
||||
private void writeWaypoints(long trackId) {
|
||||
// TODO: Stream through he 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.
|
||||
Cursor cursor = null;
|
||||
cursor = providerUtils.getWaypointCursor(trackId, 0,
|
||||
Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||
boolean hasWaypoints = false;
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
// Yes, this will skip the 1st way point and that is intentional
|
||||
// as the 1st points holds the stats for the current/last segment.
|
||||
while (cursor.moveToNext()) {
|
||||
if (!hasWaypoints) {
|
||||
writer.writeBeginWaypoints();
|
||||
hasWaypoints = true;
|
||||
}
|
||||
Waypoint wpt = providerUtils.createWaypoint(cursor);
|
||||
writer.writeWaypoint(wpt);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
if (hasWaypoints) {
|
||||
writer.writeEndWaypoints();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual work of writing the track to the now open file.
|
||||
*/
|
||||
void writeDocument() throws InterruptedException {
|
||||
Log.d(Constants.TAG, "Started writing track.");
|
||||
writer.writeHeader();
|
||||
writeWaypoints(track.getId());
|
||||
writeLocations();
|
||||
writer.writeFooter();
|
||||
writer.close();
|
||||
success = true;
|
||||
Log.d(Constants.TAG, "Done writing track.");
|
||||
errorMessage = R.string.external_storage_save_success;
|
||||
}
|
||||
|
||||
private void writeLocations() throws InterruptedException {
|
||||
boolean wroteFirst = false;
|
||||
boolean segmentOpen = false;
|
||||
boolean isLastValid = false;
|
||||
|
||||
class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
|
||||
Location currentLocation;
|
||||
Location lastLocation;
|
||||
|
||||
@Override
|
||||
public Location createLocation() {
|
||||
if (currentLocation == null) {
|
||||
currentLocation = new MyTracksLocation("");
|
||||
}
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
public void swapLocations() {
|
||||
Location tmpLoc = lastLocation;
|
||||
lastLocation = currentLocation;
|
||||
currentLocation = tmpLoc;
|
||||
if (currentLocation != null) {
|
||||
currentLocation.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
|
||||
LocationIterator it = providerUtils.getTrackPointLocationIterator(track.getId(), 0, false,
|
||||
locationFactory);
|
||||
try {
|
||||
int pointNumber = 0;
|
||||
while (it.hasNext()) {
|
||||
Location location = it.next();
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
|
||||
pointNumber++;
|
||||
|
||||
boolean isValid = LocationUtils.isValidLocation(location);
|
||||
boolean validSegment = isValid && isLastValid;
|
||||
if (!wroteFirst && validSegment) {
|
||||
// Found the first two consecutive points which are valid
|
||||
writer.writeBeginTrack(locationFactory.lastLocation);
|
||||
wroteFirst = true;
|
||||
}
|
||||
|
||||
if (validSegment) {
|
||||
if (!segmentOpen) {
|
||||
// Start a segment for this point
|
||||
writer.writeOpenSegment();
|
||||
segmentOpen = true;
|
||||
|
||||
// Write the previous point, which we had previously skipped
|
||||
writer.writeLocation(locationFactory.lastLocation);
|
||||
}
|
||||
|
||||
// Write the current point
|
||||
writer.writeLocation(location);
|
||||
if (onWriteListener != null) {
|
||||
onWriteListener.onWrite(pointNumber, track.getNumberOfPoints());
|
||||
}
|
||||
} else {
|
||||
if (segmentOpen) {
|
||||
writer.writeCloseSegment();
|
||||
segmentOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
locationFactory.swapLocations();
|
||||
isLastValid = isValid;
|
||||
}
|
||||
if (segmentOpen) {
|
||||
writer.writeCloseSegment();
|
||||
segmentOpen = false;
|
||||
}
|
||||
if (wroteFirst) {
|
||||
writer.writeEndTrack(locationFactory.lastLocation);
|
||||
} else {
|
||||
// Writes an empty track
|
||||
writer.writeBeginTrack(null);
|
||||
writer.writeEndTrack(null);
|
||||
}
|
||||
} finally {
|
||||
it.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user