forked from upstream-mirrors/OpenTracks
Extract an interface from TrackWriter to ease testing.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2011 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
@@ -15,332 +15,54 @@
|
|||||||
*/
|
*/
|
||||||
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.Track;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
|
|
||||||
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.File;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class which exports tracks to the SD card.
|
* Implementations of this class export tracks to the SD card. This class is
|
||||||
* This writer is format-neutral - it handles creating the output file
|
* intended to be format-neutral - it handles creating the output file and
|
||||||
* and reading the track to be exported, but requires an instance of
|
* reading the track to be exported, but requires an instance of
|
||||||
* {@link TrackFormatWriter} to actually format the data.
|
* {@link TrackFormatWriter} to actually format the data.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class TrackWriter {
|
public interface TrackWriter {
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final MyTracksProviderUtils providerUtils;
|
|
||||||
private final Track track;
|
|
||||||
private final TrackFormatWriter writer;
|
|
||||||
private final FileUtils fileUtils;
|
|
||||||
private Runnable onCompletion = null;
|
|
||||||
private boolean success = false;
|
|
||||||
private int errorMessage = -1;
|
|
||||||
private File directory = null;
|
|
||||||
private File file = null;
|
|
||||||
|
|
||||||
public TrackWriter(Context context, MyTracksProviderUtils providerUtils,
|
|
||||||
Track track, TrackFormatWriter writer) {
|
|
||||||
this.context = context;
|
|
||||||
this.providerUtils = providerUtils;
|
|
||||||
this.track = track;
|
|
||||||
this.writer = writer;
|
|
||||||
this.fileUtils = new FileUtils();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a completion callback.
|
* Sets a completion callback.
|
||||||
*
|
*
|
||||||
* @param onCompletion Runnable that will be executed when finished
|
* @param onCompletion Runnable that will be executed when finished
|
||||||
*/
|
*/
|
||||||
public void setOnCompletion(Runnable onCompletion) {
|
void setOnCompletion(Runnable onCompletion);
|
||||||
this.onCompletion = onCompletion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a custom directory where the file will be written.
|
* Sets a custom directory where the file will be written.
|
||||||
*/
|
*/
|
||||||
public void setDirectory(File directory) {
|
void setDirectory(File directory);
|
||||||
this.directory = directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAbsolutePath() {
|
/**
|
||||||
return file.getAbsolutePath();
|
* Returns the absolute path to the file which was created.
|
||||||
}
|
*/
|
||||||
|
String getAbsolutePath();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given track id to the SD card.
|
* Writes the given track id to the SD card.
|
||||||
* This is non-blocking.
|
* This is non-blocking.
|
||||||
*/
|
*/
|
||||||
public void writeTrackAsync() {
|
void writeTrackAsync();
|
||||||
Thread t = new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
writeTrack();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
t.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given track id to the SD card.
|
* Writes the given track id to the SD card.
|
||||||
* This is blocking.
|
* This is blocking.
|
||||||
*/
|
*/
|
||||||
public void writeTrack() {
|
void writeTrack();
|
||||||
// Open the input and output
|
|
||||||
success = false;
|
|
||||||
errorMessage = R.string.error_track_does_not_exist;
|
|
||||||
if (track != null) {
|
|
||||||
if (openFile()) {
|
|
||||||
writeDocument();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finished();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getErrorMessage() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean wasSuccess() {
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Helper methods:
|
|
||||||
* ===============
|
|
||||||
*/
|
|
||||||
|
|
||||||
private void finished() {
|
|
||||||
if (onCompletion != null) {
|
|
||||||
runOnUiThread(onCompletion);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given runnable in the UI thread.
|
* Returns true if the write completed successfully.
|
||||||
*/
|
*/
|
||||||
protected void runOnUiThread(Runnable runnable) {
|
boolean wasSuccess();
|
||||||
if (context instanceof Activity) {
|
|
||||||
((Activity) context).runOnUiThread(runnable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the file and prepares the format writer for it.
|
* Returns the error message (if any) generated by a writer failure.
|
||||||
*
|
|
||||||
* @return true on success, false otherwise (and errorMessage is set)
|
|
||||||
*/
|
*/
|
||||||
protected boolean openFile() {
|
int getErrorMessage();
|
||||||
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 " + fileName);
|
|
||||||
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.io_write_failed;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks and returns whether we're ready to create the output file.
|
|
||||||
*/
|
|
||||||
protected boolean canWriteFile() {
|
|
||||||
if (directory == null) {
|
|
||||||
String dirName =
|
|
||||||
fileUtils.buildExternalDirectoryPath(writer.getExtension());
|
|
||||||
directory = newFile(dirName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fileUtils.isSdCardAvailable()) {
|
|
||||||
Log.i(Constants.TAG, "Could not find SD card.");
|
|
||||||
errorMessage = R.string.io_no_external_storage_found;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!fileUtils.ensureDirectoryExists(directory)) {
|
|
||||||
Log.i(Constants.TAG, "Could not create export directory.");
|
|
||||||
errorMessage = R.string.io_create_dir_failed;
|
|
||||||
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.getWaypointsCursor(trackId, 0,
|
|
||||||
Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
|
||||||
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()) {
|
|
||||||
Waypoint wpt = providerUtils.createWaypoint(cursor);
|
|
||||||
writer.writeWaypoint(wpt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the actual work of writing the track to the now open file.
|
|
||||||
*/
|
|
||||||
void writeDocument() {
|
|
||||||
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.io_write_finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeLocations() {
|
|
||||||
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.getLocationIterator(track.getId(), 0, false,
|
|
||||||
locationFactory);
|
|
||||||
try {
|
|
||||||
if (!it.hasNext()) {
|
|
||||||
Log.w(Constants.TAG, "Unable to get any points to write");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (it.hasNext()) {
|
|
||||||
Location loc = it.next();
|
|
||||||
|
|
||||||
boolean isValid = LocationUtils.isValidLocation(loc);
|
|
||||||
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(loc);
|
|
||||||
} else {
|
|
||||||
if (segmentOpen) {
|
|
||||||
writer.writeCloseSegment();
|
|
||||||
segmentOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
locationFactory.swapLocations();
|
|
||||||
isLastValid = isValid;
|
|
||||||
}
|
|
||||||
if (segmentOpen) {
|
|
||||||
writer.writeCloseSegment();
|
|
||||||
segmentOpen = false;
|
|
||||||
}
|
|
||||||
if (wroteFirst) {
|
|
||||||
writer.writeEndTrack(locationFactory.lastLocation);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
it.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class TrackWriterFactory {
|
|||||||
MyTracksProviderUtils providerUtils,
|
MyTracksProviderUtils providerUtils,
|
||||||
Track track, TrackFileFormat format) {
|
Track track, TrackFileFormat format) {
|
||||||
TrackFormatWriter writer = format.newFormatWriter(context);
|
TrackFormatWriter writer = format.newFormatWriter(context);
|
||||||
return new TrackWriter(context, providerUtils, track, writer);
|
return new TrackWriterImpl(context, providerUtils, track, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackWriterFactory() { }
|
private TrackWriterFactory() { }
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sandor Dornbush
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class TrackWriterImpl implements TrackWriter {
|
||||||
|
private final Context context;
|
||||||
|
private final MyTracksProviderUtils providerUtils;
|
||||||
|
private final Track track;
|
||||||
|
private final TrackFormatWriter writer;
|
||||||
|
private final FileUtils fileUtils;
|
||||||
|
private Runnable onCompletion = null;
|
||||||
|
private boolean success = false;
|
||||||
|
private int errorMessage = -1;
|
||||||
|
private File directory = null;
|
||||||
|
private File file = null;
|
||||||
|
|
||||||
|
TrackWriterImpl(Context context, MyTracksProviderUtils providerUtils,
|
||||||
|
Track track, TrackFormatWriter writer) {
|
||||||
|
this.context = context;
|
||||||
|
this.providerUtils = providerUtils;
|
||||||
|
this.track = track;
|
||||||
|
this.writer = writer;
|
||||||
|
this.fileUtils = new FileUtils();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnCompletion(Runnable onCompletion) {
|
||||||
|
this.onCompletion = onCompletion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDirectory(File directory) {
|
||||||
|
this.directory = directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAbsolutePath() {
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTrackAsync() {
|
||||||
|
Thread t = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
writeTrack();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTrack() {
|
||||||
|
// Open the input and output
|
||||||
|
success = false;
|
||||||
|
errorMessage = R.string.error_track_does_not_exist;
|
||||||
|
if (track != null) {
|
||||||
|
if (openFile()) {
|
||||||
|
writeDocument();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wasSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper methods:
|
||||||
|
* ===============
|
||||||
|
*/
|
||||||
|
|
||||||
|
private void finished() {
|
||||||
|
if (onCompletion != null) {
|
||||||
|
runOnUiThread(onCompletion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 " + fileName);
|
||||||
|
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.io_write_failed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and returns whether we're ready to create the output file.
|
||||||
|
*/
|
||||||
|
protected boolean canWriteFile() {
|
||||||
|
if (directory == null) {
|
||||||
|
String dirName =
|
||||||
|
fileUtils.buildExternalDirectoryPath(writer.getExtension());
|
||||||
|
directory = newFile(dirName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileUtils.isSdCardAvailable()) {
|
||||||
|
Log.i(Constants.TAG, "Could not find SD card.");
|
||||||
|
errorMessage = R.string.io_no_external_storage_found;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fileUtils.ensureDirectoryExists(directory)) {
|
||||||
|
Log.i(Constants.TAG, "Could not create export directory.");
|
||||||
|
errorMessage = R.string.io_create_dir_failed;
|
||||||
|
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.getWaypointsCursor(trackId, 0,
|
||||||
|
Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||||
|
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()) {
|
||||||
|
Waypoint wpt = providerUtils.createWaypoint(cursor);
|
||||||
|
writer.writeWaypoint(wpt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the actual work of writing the track to the now open file.
|
||||||
|
*/
|
||||||
|
void writeDocument() {
|
||||||
|
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.io_write_finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLocations() {
|
||||||
|
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.getLocationIterator(track.getId(), 0, false,
|
||||||
|
locationFactory);
|
||||||
|
try {
|
||||||
|
if (!it.hasNext()) {
|
||||||
|
Log.w(Constants.TAG, "Unable to get any points to write");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Location loc = it.next();
|
||||||
|
|
||||||
|
boolean isValid = LocationUtils.isValidLocation(loc);
|
||||||
|
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(loc);
|
||||||
|
} else {
|
||||||
|
if (segmentOpen) {
|
||||||
|
writer.writeCloseSegment();
|
||||||
|
segmentOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locationFactory.swapLocations();
|
||||||
|
isLastValid = isValid;
|
||||||
|
}
|
||||||
|
if (segmentOpen) {
|
||||||
|
writer.writeCloseSegment();
|
||||||
|
segmentOpen = false;
|
||||||
|
}
|
||||||
|
if (wroteFirst) {
|
||||||
|
writer.writeEndTrack(locationFactory.lastLocation);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
it.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ 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.content.MyTracksProviderUtils.Factory;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
||||||
import com.google.android.apps.mytracks.io.file.TrackFormatWriter;
|
import com.google.android.apps.mytracks.io.file.TrackFormatWriter;
|
||||||
import com.google.android.apps.mytracks.io.file.TrackWriter;
|
import com.google.android.apps.mytracks.io.file.TrackWriterImpl;
|
||||||
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;
|
||||||
|
|
||||||
@@ -35,10 +35,10 @@ import org.easymock.IMocksControl;
|
|||||||
*/
|
*/
|
||||||
public class TrackWriterTest extends AndroidTestCase {
|
public class TrackWriterTest extends AndroidTestCase {
|
||||||
/**
|
/**
|
||||||
* {@link TrackWriter} subclass which mocks out methods called from
|
* {@link TrackWriterImpl} subclass which mocks out methods called from
|
||||||
* {@link TrackWriter#openFile}.
|
* {@link TrackWriterImpl#openFile}.
|
||||||
*/
|
*/
|
||||||
private static final class OpenFileTrackWriter extends TrackWriter {
|
private static final class OpenFileTrackWriter extends TrackWriterImpl {
|
||||||
private final ByteArrayOutputStream stream;
|
private final ByteArrayOutputStream stream;
|
||||||
private final boolean canWrite;
|
private final boolean canWrite;
|
||||||
|
|
||||||
@@ -46,9 +46,9 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param stream the stream to return from
|
* @param stream the stream to return from
|
||||||
* {@link TrackWriter#newOutputStream}, or null to throw a
|
* {@link TrackWriterImpl#newOutputStream}, or null to throw a
|
||||||
* {@link FileNotFoundException}
|
* {@link FileNotFoundException}
|
||||||
* @param canWrite the value that {@link TrackWriter#canWriteFile} will
|
* @param canWrite the value that {@link TrackWriterImpl#canWriteFile} will
|
||||||
* return
|
* return
|
||||||
*/
|
*/
|
||||||
private OpenFileTrackWriter(Context context,
|
private OpenFileTrackWriter(Context context,
|
||||||
@@ -80,16 +80,16 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link TrackWriter} subclass which mocks out methods called from
|
* {@link TrackWriterImpl} subclass which mocks out methods called from
|
||||||
* {@link TrackWriter#writeTrack}.
|
* {@link TrackWriterImpl#writeTrack}.
|
||||||
*/
|
*/
|
||||||
private final class WriteTracksTrackWriter extends TrackWriter {
|
private final class WriteTracksTrackWriter extends TrackWriterImpl {
|
||||||
private final boolean openResult;
|
private final boolean openResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param openResult the return value for {@link TrackWriter#openFile}
|
* @param openResult the return value for {@link TrackWriterImpl#openFile}
|
||||||
*/
|
*/
|
||||||
private WriteTracksTrackWriter(Context context,
|
private WriteTracksTrackWriter(Context context,
|
||||||
MyTracksProviderUtils providerUtils, Track track,
|
MyTracksProviderUtils providerUtils, Track track,
|
||||||
@@ -123,7 +123,7 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
|
|
||||||
private Track track;
|
private Track track;
|
||||||
private TrackFormatWriter formatWriter;
|
private TrackFormatWriter formatWriter;
|
||||||
private TrackWriter writer;
|
private TrackWriterImpl writer;
|
||||||
private IMocksControl mocksControl;
|
private IMocksControl mocksControl;
|
||||||
private MyTracksProviderUtils providerUtils;
|
private MyTracksProviderUtils providerUtils;
|
||||||
private Factory oldProviderUtilsFactory;
|
private Factory oldProviderUtilsFactory;
|
||||||
@@ -228,7 +228,7 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testWriteDocument_emptyTrack() {
|
public void testWriteDocument_emptyTrack() {
|
||||||
writer = new TrackWriter(getContext(), providerUtils, track, formatWriter);
|
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
|
||||||
|
|
||||||
// Set expected mock behavior
|
// Set expected mock behavior
|
||||||
formatWriter.writeHeader();
|
formatWriter.writeHeader();
|
||||||
@@ -243,7 +243,7 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testWriteDocument() {
|
public void testWriteDocument() {
|
||||||
writer = new TrackWriter(getContext(), providerUtils, track, formatWriter);
|
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
|
||||||
|
|
||||||
final Location[] locs = {
|
final Location[] locs = {
|
||||||
new Location("fake0"),
|
new Location("fake0"),
|
||||||
|
|||||||
Reference in New Issue
Block a user