forked from upstream-mirrors/OpenTracks
Code cleanup: Add TrackExporter interface
This commit is contained in:
@@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.LocationUtils;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track Writer for writing tracks to an {@link OutputStream}.
|
||||||
|
*
|
||||||
|
* @author Sandor Dornbush
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class FileTrackExporter implements TrackExporter {
|
||||||
|
|
||||||
|
private final static String TAG = FileTrackExporter.class.getSimpleName();
|
||||||
|
|
||||||
|
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||||
|
private final Track[] tracks;
|
||||||
|
private final TrackWriter trackWriter;
|
||||||
|
private final TrackExporterListener trackExporterListener;
|
||||||
|
|
||||||
|
private Thread writeThread;
|
||||||
|
private boolean success = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param context the context
|
||||||
|
* @param myTracksProviderUtils the my tracks provider utils
|
||||||
|
* @param tracks the tracks
|
||||||
|
* @param trackFileFormat the track file format
|
||||||
|
* @param trackExporterListener the track export listener
|
||||||
|
*/
|
||||||
|
public FileTrackExporter(Context context, MyTracksProviderUtils myTracksProviderUtils,
|
||||||
|
Track[] tracks, TrackFileFormat trackFileFormat,
|
||||||
|
TrackExporterListener trackExporterListener) {
|
||||||
|
this(myTracksProviderUtils, tracks, trackFileFormat.newTrackWriter(context),
|
||||||
|
trackExporterListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public FileTrackExporter(MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
|
||||||
|
TrackWriter trackWriter, TrackExporterListener trackExporterListener) {
|
||||||
|
this.myTracksProviderUtils = myTracksProviderUtils;
|
||||||
|
this.tracks = tracks;
|
||||||
|
this.trackWriter = trackWriter;
|
||||||
|
this.trackExporterListener = trackExporterListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTrack(final OutputStream outputStream) {
|
||||||
|
writeThread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
trackWriter.prepare(outputStream);
|
||||||
|
trackWriter.writeHeader(tracks[0]);
|
||||||
|
long startTime = tracks[0].getTripStatistics().getStartTime();
|
||||||
|
for (int i = 0; i < tracks.length; i++) {
|
||||||
|
writeWaypoints(tracks[i]);
|
||||||
|
long offset = tracks[i].getTripStatistics().getStartTime() - startTime;
|
||||||
|
writeLocations(tracks[i], offset);
|
||||||
|
}
|
||||||
|
trackWriter.writeFooter();
|
||||||
|
trackWriter.close();
|
||||||
|
success = true;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writeThread.start();
|
||||||
|
try {
|
||||||
|
writeThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Interrupted while waiting for write to complete", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopWriteTrack() {
|
||||||
|
if (writeThread != null && writeThread.isAlive()) {
|
||||||
|
Log.i(TAG, "Attempting to stop track write");
|
||||||
|
writeThread.interrupt();
|
||||||
|
|
||||||
|
try {
|
||||||
|
writeThread.join();
|
||||||
|
Log.i(TAG, "Track write stopped");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Interrupted while waiting for writer to stop", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the waypoints.
|
||||||
|
*/
|
||||||
|
private void writeWaypoints(Track track) {
|
||||||
|
/*
|
||||||
|
* TODO: Stream through the waypoints in chunks. I am leaving the number of
|
||||||
|
* waypoints very high which should not be a problem because we don't try to
|
||||||
|
* load them into objects all at the same time.
|
||||||
|
*/
|
||||||
|
boolean hasWaypoints = false;
|
||||||
|
Cursor cursor = null;
|
||||||
|
try {
|
||||||
|
cursor = myTracksProviderUtils.getWaypointCursor(
|
||||||
|
track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
/*
|
||||||
|
* Yes, this will skip the first waypoint and that is intentional as the
|
||||||
|
* first waypoint holds the stats for the track.
|
||||||
|
*/
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
if (!hasWaypoints) {
|
||||||
|
trackWriter.writeBeginWaypoints();
|
||||||
|
hasWaypoints = true;
|
||||||
|
}
|
||||||
|
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
|
||||||
|
trackWriter.writeWaypoint(waypoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasWaypoints) {
|
||||||
|
trackWriter.writeEndWaypoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the locations.
|
||||||
|
*/
|
||||||
|
private void writeLocations(Track track, long offset) throws InterruptedException {
|
||||||
|
boolean wroteTrack = false;
|
||||||
|
boolean wroteSegment = false;
|
||||||
|
boolean isLastLocationValid = false;
|
||||||
|
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
|
||||||
|
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
|
||||||
|
track.getId(), -1L, false, locationFactory);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int locationNumber = 0;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Location location = iterator.next();
|
||||||
|
setLocationTime(location, offset);
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
locationNumber++;
|
||||||
|
|
||||||
|
boolean isLocationValid = LocationUtils.isValidLocation(location);
|
||||||
|
boolean isSegmentValid = isLocationValid && isLastLocationValid;
|
||||||
|
if (!wroteTrack && isSegmentValid) {
|
||||||
|
// Found the first two consecutive locations that are valid
|
||||||
|
trackWriter.writeBeginTrack(track, locationFactory.lastLocation);
|
||||||
|
wroteTrack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSegmentValid) {
|
||||||
|
if (!wroteSegment) {
|
||||||
|
// Start a segment
|
||||||
|
trackWriter.writeOpenSegment();
|
||||||
|
wroteSegment = true;
|
||||||
|
|
||||||
|
// Write the previous location, which we had previously skipped
|
||||||
|
trackWriter.writeLocation(locationFactory.lastLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the current location
|
||||||
|
trackWriter.writeLocation(location);
|
||||||
|
if (trackExporterListener != null) {
|
||||||
|
trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wroteSegment) {
|
||||||
|
trackWriter.writeCloseSegment();
|
||||||
|
wroteSegment = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locationFactory.swapLocations();
|
||||||
|
isLastLocationValid = isLocationValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wroteSegment) {
|
||||||
|
trackWriter.writeCloseSegment();
|
||||||
|
wroteSegment = false;
|
||||||
|
}
|
||||||
|
if (wroteTrack) {
|
||||||
|
Location lastValidTrackPoint = myTracksProviderUtils.getLastValidTrackPoint(track.getId());
|
||||||
|
setLocationTime(lastValidTrackPoint, offset);
|
||||||
|
trackWriter.writeEndTrack(track, lastValidTrackPoint);
|
||||||
|
} else {
|
||||||
|
// Write an empty track
|
||||||
|
trackWriter.writeBeginTrack(track, null);
|
||||||
|
trackWriter.writeEndTrack(track, null);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
iterator.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a location time.
|
||||||
|
*
|
||||||
|
* @param location the location
|
||||||
|
* @param offset the time offset
|
||||||
|
*/
|
||||||
|
private void setLocationTime(Location location, long offset) {
|
||||||
|
if (location != null) {
|
||||||
|
location.setTime(location.getTime() - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track writer location factory. Keeping the last two locations.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
private class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
|
||||||
|
Location currentLocation;
|
||||||
|
Location lastLocation;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Location createLocation() {
|
||||||
|
if (currentLocation == null) {
|
||||||
|
currentLocation = new MyTracksLocation("");
|
||||||
|
}
|
||||||
|
return currentLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swapLocations() {
|
||||||
|
Location tempLocation = lastLocation;
|
||||||
|
lastLocation = currentLocation;
|
||||||
|
currentLocation = tempLocation;
|
||||||
|
if (currentLocation != null) {
|
||||||
|
currentLocation.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ package com.google.android.apps.mytracks.io.file;
|
|||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
|
import com.google.android.apps.mytracks.io.file.TrackExporter.TrackExporterListener;
|
||||||
import com.google.android.apps.mytracks.util.FileUtils;
|
import com.google.android.apps.mytracks.util.FileUtils;
|
||||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||||
@@ -52,7 +53,7 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||||
|
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
private TrackExporter trackExporter;
|
private FileTrackExporter fileTrackExporter;
|
||||||
|
|
||||||
// true if the AsyncTask has completed
|
// true if the AsyncTask has completed
|
||||||
private boolean completed;
|
private boolean completed;
|
||||||
@@ -163,8 +164,8 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCancelled() {
|
protected void onCancelled() {
|
||||||
if (trackExporter != null) {
|
if (fileTrackExporter != null) {
|
||||||
trackExporter.stopWriteTrack();
|
fileTrackExporter.stopWriteTrack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,10 +187,11 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackExporter = new TrackExporter(
|
fileTrackExporter = new FileTrackExporter(
|
||||||
context, myTracksProviderUtils, tracks, trackFileFormat, new TrackExporter.OnWriteListener() {
|
context, myTracksProviderUtils, tracks, trackFileFormat, new TrackExporterListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWrite(int number, int max) {
|
public void onProgressUpdate(int number, int max) {
|
||||||
/*
|
/*
|
||||||
* If only saving one track, update the progress dialog once every
|
* If only saving one track, update the progress dialog once every
|
||||||
* 500 points
|
* 500 points
|
||||||
@@ -204,20 +206,20 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
try {
|
try {
|
||||||
file = new File(directory, fileName);
|
file = new File(directory, fileName);
|
||||||
OutputStream outputStream = new FileOutputStream(file);
|
OutputStream outputStream = new FileOutputStream(file);
|
||||||
trackExporter.writeTrack(outputStream);
|
fileTrackExporter.writeTrack(outputStream);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Log.d(TAG, "File not found " + fileName, e);
|
Log.d(TAG, "File not found " + fileName, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackExporter.wasSuccess()) {
|
if (fileTrackExporter.isSuccess()) {
|
||||||
savedPath = file.getAbsolutePath();
|
savedPath = file.getAbsolutePath();
|
||||||
} else {
|
} else {
|
||||||
if (!file.delete()) {
|
if (!file.delete()) {
|
||||||
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
|
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return trackExporter.wasSuccess();
|
return fileTrackExporter.isSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,284 +16,47 @@
|
|||||||
|
|
||||||
package com.google.android.apps.mytracks.io.file;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
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.LocationUtils;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track Writer for writing tracks to an {@link OutputStream}.
|
* Track exporting for exporting track to an {@link OutputStream}.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Jimmy Shih
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
*/
|
||||||
public class TrackExporter {
|
public interface TrackExporter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener for when a track location is written.
|
* Listener for {@link TrackExporter} progress.
|
||||||
*/
|
|
||||||
public interface OnWriteListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a track location is written.
|
|
||||||
*
|
|
||||||
* @param number the location number
|
|
||||||
* @param max the maximum number of locations in a track, for calculation of completion
|
|
||||||
* percentage
|
|
||||||
*/
|
|
||||||
public void onWrite(int number, int max);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static String TAG = TrackExporter.class.getSimpleName();
|
|
||||||
|
|
||||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
|
||||||
private final Track[] tracks;
|
|
||||||
private final TrackWriter trackWriter;
|
|
||||||
private final OnWriteListener onWriteListener;
|
|
||||||
|
|
||||||
private Thread writeThread;
|
|
||||||
private boolean success = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
* @param myTracksProviderUtils the my tracks provider utils
|
|
||||||
* @param tracks the tracks
|
|
||||||
* @param trackFileFormat the track file format
|
|
||||||
* @param onWriteListener the on write listener
|
|
||||||
*/
|
|
||||||
public TrackExporter(Context context, MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
|
|
||||||
TrackFileFormat trackFileFormat, OnWriteListener onWriteListener) {
|
|
||||||
this(myTracksProviderUtils, tracks, trackFileFormat.newTrackWriter(context), onWriteListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public TrackExporter(MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
|
|
||||||
TrackWriter trackWriter, OnWriteListener onWriteListener) {
|
|
||||||
this.myTracksProviderUtils = myTracksProviderUtils;
|
|
||||||
this.tracks = tracks;
|
|
||||||
this.trackWriter = trackWriter;
|
|
||||||
this.onWriteListener = onWriteListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the write completed successfully.
|
|
||||||
*/
|
|
||||||
public boolean wasSuccess() {
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the given track to the output stream.
|
|
||||||
*
|
|
||||||
* @param outputStream the output stream.
|
|
||||||
*/
|
|
||||||
public void writeTrack(final OutputStream outputStream) {
|
|
||||||
writeThread = new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
trackWriter.prepare(outputStream);
|
|
||||||
trackWriter.writeHeader(tracks[0]);
|
|
||||||
long startTime = tracks[0].getTripStatistics().getStartTime();
|
|
||||||
for (int i = 0; i < tracks.length; i++) {
|
|
||||||
writeWaypoints(tracks[i]);
|
|
||||||
long offset = tracks[i].getTripStatistics().getStartTime() - startTime;
|
|
||||||
writeLocations(tracks[i], offset);
|
|
||||||
}
|
|
||||||
trackWriter.writeFooter();
|
|
||||||
trackWriter.close();
|
|
||||||
success = true;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
writeThread.start();
|
|
||||||
try {
|
|
||||||
writeThread.join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(TAG, "Interrupted while waiting for write to complete", e);
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops any in-progress writes.
|
|
||||||
*/
|
|
||||||
public void stopWriteTrack() {
|
|
||||||
if (writeThread != null && writeThread.isAlive()) {
|
|
||||||
Log.i(TAG, "Attempting to stop track write");
|
|
||||||
writeThread.interrupt();
|
|
||||||
|
|
||||||
try {
|
|
||||||
writeThread.join();
|
|
||||||
Log.i(TAG, "Track write stopped");
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(TAG, "Interrupted while waiting for writer to stop", e);
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the waypoints.
|
|
||||||
*/
|
|
||||||
private void writeWaypoints(Track track) {
|
|
||||||
/*
|
|
||||||
* TODO: Stream through the waypoints in chunks. I am leaving the number of
|
|
||||||
* waypoints very high which should not be a problem because we don't try to
|
|
||||||
* load them into objects all at the same time.
|
|
||||||
*/
|
|
||||||
boolean hasWaypoints = false;
|
|
||||||
Cursor cursor = null;
|
|
||||||
try {
|
|
||||||
cursor = myTracksProviderUtils.getWaypointCursor(
|
|
||||||
track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
/*
|
|
||||||
* Yes, this will skip the first waypoint and that is intentional as the
|
|
||||||
* first waypoint holds the stats for the track.
|
|
||||||
*/
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
if (!hasWaypoints) {
|
|
||||||
trackWriter.writeBeginWaypoints();
|
|
||||||
hasWaypoints = true;
|
|
||||||
}
|
|
||||||
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
|
|
||||||
trackWriter.writeWaypoint(waypoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasWaypoints) {
|
|
||||||
trackWriter.writeEndWaypoints();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the locations.
|
|
||||||
*/
|
|
||||||
private void writeLocations(Track track, long offset) throws InterruptedException {
|
|
||||||
boolean wroteTrack = false;
|
|
||||||
boolean wroteSegment = false;
|
|
||||||
boolean isLastLocationValid = false;
|
|
||||||
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
|
|
||||||
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
|
|
||||||
track.getId(), -1L, false, locationFactory);
|
|
||||||
|
|
||||||
try {
|
|
||||||
int locationNumber = 0;
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
Location location = iterator.next();
|
|
||||||
setLocationTime(location, offset);
|
|
||||||
if (Thread.interrupted()) {
|
|
||||||
throw new InterruptedException();
|
|
||||||
}
|
|
||||||
locationNumber++;
|
|
||||||
|
|
||||||
boolean isLocationValid = LocationUtils.isValidLocation(location);
|
|
||||||
boolean isSegmentValid = isLocationValid && isLastLocationValid;
|
|
||||||
if (!wroteTrack && isSegmentValid) {
|
|
||||||
// Found the first two consecutive locations that are valid
|
|
||||||
trackWriter.writeBeginTrack(track, locationFactory.lastLocation);
|
|
||||||
wroteTrack = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSegmentValid) {
|
|
||||||
if (!wroteSegment) {
|
|
||||||
// Start a segment
|
|
||||||
trackWriter.writeOpenSegment();
|
|
||||||
wroteSegment = true;
|
|
||||||
|
|
||||||
// Write the previous location, which we had previously skipped
|
|
||||||
trackWriter.writeLocation(locationFactory.lastLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the current location
|
|
||||||
trackWriter.writeLocation(location);
|
|
||||||
if (onWriteListener != null) {
|
|
||||||
onWriteListener.onWrite(locationNumber, track.getNumberOfPoints());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (wroteSegment) {
|
|
||||||
trackWriter.writeCloseSegment();
|
|
||||||
wroteSegment = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
locationFactory.swapLocations();
|
|
||||||
isLastLocationValid = isLocationValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wroteSegment) {
|
|
||||||
trackWriter.writeCloseSegment();
|
|
||||||
wroteSegment = false;
|
|
||||||
}
|
|
||||||
if (wroteTrack) {
|
|
||||||
Location lastValidTrackPoint = myTracksProviderUtils.getLastValidTrackPoint(track.getId());
|
|
||||||
setLocationTime(lastValidTrackPoint, offset);
|
|
||||||
trackWriter.writeEndTrack(track, lastValidTrackPoint);
|
|
||||||
} else {
|
|
||||||
// Write an empty track
|
|
||||||
trackWriter.writeBeginTrack(track, null);
|
|
||||||
trackWriter.writeEndTrack(track, null);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
iterator.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a location time.
|
|
||||||
*
|
|
||||||
* @param location the location
|
|
||||||
* @param offset the time offset
|
|
||||||
*/
|
|
||||||
private void setLocationTime(Location location, long offset) {
|
|
||||||
if (location != null) {
|
|
||||||
location.setTime(location.getTime() - offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track writer location factory. Keeping the last two locations.
|
|
||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
private class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
|
interface TrackExporterListener {
|
||||||
Location currentLocation;
|
|
||||||
Location lastLocation;
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public Location createLocation() {
|
* Called to update progress.
|
||||||
if (currentLocation == null) {
|
*
|
||||||
currentLocation = new MyTracksLocation("");
|
* @param number the number of locations written
|
||||||
}
|
* @param max the maximum number of locations in a track, for calculation of
|
||||||
return currentLocation;
|
* completion percentage
|
||||||
}
|
*/
|
||||||
|
void onProgressUpdate(int number, int max);
|
||||||
public void swapLocations() {
|
|
||||||
Location tempLocation = lastLocation;
|
|
||||||
lastLocation = currentLocation;
|
|
||||||
currentLocation = tempLocation;
|
|
||||||
if (currentLocation != null) {
|
|
||||||
currentLocation.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write track to an output stream
|
||||||
|
*
|
||||||
|
* @param outputStream the output stream
|
||||||
|
*/
|
||||||
|
void writeTrack(OutputStream outputStream);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops {@link #writeTrack(OutputStream)}.
|
||||||
|
*/
|
||||||
|
void stopWriteTrack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if {@link #writeTrack(OutputStream)} completed successfully.
|
||||||
|
*/
|
||||||
|
boolean isSuccess();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for {@link TrackExporter} progress.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public interface TrackExporterListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to update progress.
|
||||||
|
*
|
||||||
|
* @param number the number of locations written
|
||||||
|
* @param max the maximum number of locations in a track, for calculation of
|
||||||
|
* completion percentage
|
||||||
|
*/
|
||||||
|
public void onProgressUpdate(int number, int max);
|
||||||
|
}
|
||||||
@@ -20,8 +20,8 @@ import com.google.android.apps.mytracks.Constants;
|
|||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
|
import com.google.android.apps.mytracks.io.file.FileTrackExporter;
|
||||||
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
|
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
|
||||||
import com.google.android.apps.mytracks.io.file.TrackExporter;
|
|
||||||
import com.google.android.apps.mytracks.util.FileUtils;
|
import com.google.android.apps.mytracks.util.FileUtils;
|
||||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
@@ -441,11 +441,11 @@ public class SyncUtils {
|
|||||||
|
|
||||||
java.io.File file = new java.io.File(directory,
|
java.io.File file = new java.io.File(directory,
|
||||||
FileUtils.buildUniqueFileName(directory, track.getName(), trackFileFormat.getExtension()));
|
FileUtils.buildUniqueFileName(directory, track.getName(), trackFileFormat.getExtension()));
|
||||||
TrackExporter trackExporter = new TrackExporter(
|
FileTrackExporter fileTrackExporter = new FileTrackExporter(
|
||||||
context, myTracksProviderUtils, new Track[] {track}, trackFileFormat, null);
|
context, myTracksProviderUtils, new Track[] {track}, trackFileFormat, null);
|
||||||
|
|
||||||
trackExporter.writeTrack(new FileOutputStream(file));
|
fileTrackExporter.writeTrack(new FileOutputStream(file));
|
||||||
if (trackExporter.wasSuccess()) {
|
if (fileTrackExporter.isSuccess()) {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Unable to get file for track " + track.getName());
|
Log.d(TAG, "Unable to get file for track " + track.getName());
|
||||||
|
|||||||
+14
-13
@@ -7,7 +7,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
|||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.io.file.TrackExporter.OnWriteListener;
|
import com.google.android.apps.mytracks.io.file.TrackExporter.TrackExporterListener;
|
||||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
|
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
|
||||||
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
||||||
|
|
||||||
@@ -25,11 +25,11 @@ import org.easymock.IArgumentMatcher;
|
|||||||
import org.easymock.IMocksControl;
|
import org.easymock.IMocksControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link TrackExporter}.
|
* Tests for {@link FileTrackExporter}.
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class TrackExporterTest extends AndroidTestCase {
|
public class FileTrackExporterTest extends AndroidTestCase {
|
||||||
|
|
||||||
private static final long TRACK_ID = 1234567L;
|
private static final long TRACK_ID = 1234567L;
|
||||||
private static final String TRACK_NAME = "Swimming across the pacific";
|
private static final String TRACK_NAME = "Swimming across the pacific";
|
||||||
@@ -42,7 +42,7 @@ public class TrackExporterTest extends AndroidTestCase {
|
|||||||
private TrackWriter trackWriter;
|
private TrackWriter trackWriter;
|
||||||
private Track track;
|
private Track track;
|
||||||
private OutputStream outputStream;
|
private OutputStream outputStream;
|
||||||
private TrackExporter trackExporter;
|
private FileTrackExporter fileTrackExporter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
@@ -68,15 +68,16 @@ public class TrackExporterTest extends AndroidTestCase {
|
|||||||
track.setId(TRACK_ID);
|
track.setId(TRACK_ID);
|
||||||
|
|
||||||
outputStream = new ByteArrayOutputStream();
|
outputStream = new ByteArrayOutputStream();
|
||||||
OnWriteListener onWriteListener = new OnWriteListener() {
|
TrackExporterListener trackExporterListener = new TrackExporterListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWrite(int number, int max) {
|
public void onProgressUpdate(int number, int max) {
|
||||||
// Safe to ignore
|
// Safe to ignore
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
trackExporter = new TrackExporter(myTracksProviderUtils, new Track[] {track}, trackWriter, onWriteListener);
|
fileTrackExporter = new FileTrackExporter(
|
||||||
|
myTracksProviderUtils, new Track[] { track }, trackWriter, trackExporterListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -99,9 +100,9 @@ public class TrackExporterTest extends AndroidTestCase {
|
|||||||
trackWriter.close();
|
trackWriter.close();
|
||||||
|
|
||||||
mocksControl.replay();
|
mocksControl.replay();
|
||||||
trackExporter.writeTrack(outputStream);
|
fileTrackExporter.writeTrack(outputStream);
|
||||||
|
|
||||||
assertTrue(trackExporter.wasSuccess());
|
assertTrue(fileTrackExporter.isSuccess());
|
||||||
mocksControl.verify();
|
mocksControl.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,9 +132,9 @@ public class TrackExporterTest extends AndroidTestCase {
|
|||||||
trackWriter.close();
|
trackWriter.close();
|
||||||
|
|
||||||
mocksControl.replay();
|
mocksControl.replay();
|
||||||
trackExporter.writeTrack(outputStream);
|
fileTrackExporter.writeTrack(outputStream);
|
||||||
|
|
||||||
assertTrue(trackExporter.wasSuccess());
|
assertTrue(fileTrackExporter.isSuccess());
|
||||||
mocksControl.verify();
|
mocksControl.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,9 +197,9 @@ public class TrackExporterTest extends AndroidTestCase {
|
|||||||
trackWriter.close();
|
trackWriter.close();
|
||||||
|
|
||||||
mocksControl.replay();
|
mocksControl.replay();
|
||||||
trackExporter.writeTrack(outputStream);
|
fileTrackExporter.writeTrack(outputStream);
|
||||||
|
|
||||||
assertTrue(trackExporter.wasSuccess());
|
assertTrue(fileTrackExporter.isSuccess());
|
||||||
mocksControl.verify();
|
mocksControl.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user