Merge heads

This commit is contained in:
youtaol
2013-09-04 10:11:32 +08:00
39 changed files with 888 additions and 457 deletions
+1 -1
View File
@@ -214,7 +214,7 @@ limitations under the License.
<activity android:name="com.google.android.apps.mytracks.io.backup.RestoreActivity" />
<activity android:name="com.google.android.apps.mytracks.io.backup.RestoreChooserActivity" />
<activity android:name="com.google.android.apps.mytracks.io.drive.SendDriveActivity" />
<activity android:name="com.google.android.apps.mytracks.io.file.SaveActivity" />
<activity android:name="com.google.android.apps.mytracks.io.file.export.SaveActivity" />
<activity android:name="com.google.android.apps.mytracks.io.fusiontables.SendFusionTablesActivity" />
<activity android:name="com.google.android.apps.mytracks.io.maps.SendMapsActivity" />
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity" />
@@ -30,8 +30,8 @@ import com.google.android.apps.mytracks.fragments.InstallEarthDialogFragment;
import com.google.android.apps.mytracks.fragments.ShareTrackDialogFragment;
import com.google.android.apps.mytracks.fragments.ShareTrackDialogFragment.ShareTrackCaller;
import com.google.android.apps.mytracks.io.drive.SendDriveActivity;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.io.file.export.SaveActivity;
import com.google.android.apps.mytracks.io.fusiontables.SendFusionTablesActivity;
import com.google.android.apps.mytracks.io.gdata.maps.MapsConstants;
import com.google.android.apps.mytracks.io.maps.SendMapsActivity;
@@ -30,8 +30,8 @@ import com.google.android.apps.mytracks.fragments.ExportDialogFragment.ExportTyp
import com.google.android.apps.mytracks.fragments.FrequencyDialogFragment;
import com.google.android.apps.mytracks.fragments.MyTracksMapFragment;
import com.google.android.apps.mytracks.fragments.StatsFragment;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.io.file.export.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.settings.SettingsActivity;
@@ -27,8 +27,8 @@ import com.google.android.apps.mytracks.fragments.EulaDialogFragment.EulaCaller;
import com.google.android.apps.mytracks.fragments.FileTypeDialogFragment;
import com.google.android.apps.mytracks.fragments.FileTypeDialogFragment.FileTypeCaller;
import com.google.android.apps.mytracks.io.file.ImportActivity;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.io.file.export.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.io.sync.SyncUtils;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
@@ -117,8 +117,10 @@ public class TrackDataHub implements DataSourceListener {
runInHanderThread(new Runnable() {
@Override
public void run() {
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
loadDataForAll();
if (dataSourceManager != null) {
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
loadDataForAll();
}
}
});
}
@@ -173,9 +175,11 @@ public class TrackDataHub implements DataSourceListener {
runInHanderThread(new Runnable() {
@Override
public void run() {
trackDataManager.registerListener(trackDataListener, trackDataTypes);
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
loadDataForListener(trackDataListener);
if (dataSourceManager != null) {
trackDataManager.registerListener(trackDataListener, trackDataTypes);
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
loadDataForListener(trackDataListener);
}
}
});
}
@@ -189,8 +193,10 @@ public class TrackDataHub implements DataSourceListener {
runInHanderThread(new Runnable() {
@Override
public void run() {
trackDataManager.unregisterListener(trackDataListener);
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
if (dataSourceManager != null) {
trackDataManager.unregisterListener(trackDataListener);
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
}
}
});
}
@@ -37,6 +37,7 @@ import com.google.common.annotations.VisibleForTesting;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -172,7 +173,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
tripStatisticsUpdater = startTime != -1L ? new TripStatisticsUpdater(startTime) : null;
pendingPoints.clear();
chartView.reset();
getActivity().runOnUiThread(new Runnable() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
@@ -211,7 +212,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
if (isResumed()) {
chartView.addDataPoints(pendingPoints);
pendingPoints.clear();
getActivity().runOnUiThread(updateChart);
runOnUiThread(updateChart);
}
}
@@ -232,7 +233,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override
public void onNewWaypointsDone() {
if (isResumed()) {
getActivity().runOnUiThread(updateChart);
runOnUiThread(updateChart);
}
}
@@ -244,7 +245,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
metricUnits = metric;
chartView.setMetricUnits(metricUnits);
getActivity().runOnUiThread(new Runnable() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
@@ -269,7 +270,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
getActivity().runOnUiThread(new Runnable() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
@@ -414,6 +415,18 @@ public class ChartFragment extends Fragment implements TrackDataListener {
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
/**
* Runs a runnable on the UI thread if possible.
*
* @param runnable the runnable
*/
private void runOnUiThread(Runnable runnable) {
FragmentActivity fragmentActivity = getActivity();
if (fragmentActivity != null) {
fragmentActivity.runOnUiThread(runnable);
}
}
/**
* Given a location, fill in a data point, an array of double[]. <br>
* data[0] = time/distance <br>
@@ -98,7 +98,14 @@ public class CheckPermissionFragment extends Fragment {
SendToGoogleUtils.getGoogleAccountCredential(getActivity(), accountName, scope);
finish(scope, true, null);
} catch (UserRecoverableAuthException e) {
finish(scope, false, e.getIntent());
Intent intent = null;
try {
// HACK: UserRecoverableAuthException.getIntent can throw a null pointer exception.
intent = e.getIntent();
} catch (Exception e1) {
Log.e(TAG, "Exception in getIntent", e1);
}
finish(scope, false, intent);
} catch (GoogleAuthException e) {
Log.e(TAG, "GoogleAuthException", e);
finish(scope, false, null);
@@ -28,7 +28,6 @@ import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@@ -66,7 +65,6 @@ public class MarkerDetailFragment extends Fragment {
private MyTracksProviderUtils myTracksProviderUtils;
private Waypoint waypoint;
private Bitmap bitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -97,13 +95,6 @@ public class MarkerDetailFragment extends Fragment {
update();
}
public void onPause() {
super.onPause();
if (bitmap != null) {
bitmap.recycle();
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.marker_detail, menu);
@@ -180,8 +171,7 @@ public class MarkerDetailFragment extends Fragment {
int displayWidth = defaultDisplay.getWidth();
@SuppressWarnings("deprecation")
int displayHeight = defaultDisplay.getHeight();
bitmap = PhotoUtils.setImageVew(
imageView, Uri.parse(photoUrl), displayWidth, displayHeight);
PhotoUtils.setImageVew(imageView, Uri.parse(photoUrl), displayWidth, displayHeight);
}
TextView name = (TextView) getView().findViewById(R.id.marker_detail_waypoint_name);
@@ -54,12 +54,12 @@ import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Abstract class for various importers like {@link GpxImporter} and
* {@link KmlImporter}.
* Abstract class for various file track importers like {@link GpxFileTrackImporter} and
* {@link KmlFileTrackImporter}.
*
* @author Jimmy Shih
*/
abstract class AbstractImporter extends DefaultHandler {
abstract class AbstractFileTrackImporter extends DefaultHandler implements TrackImporter {
/**
* Data for the current track.
@@ -131,12 +131,12 @@ abstract class AbstractImporter extends DefaultHandler {
* @param importTrackId the track id to import to. -1L to import to a new
* track.
*/
protected AbstractImporter(Context context, long importTrackId) {
protected AbstractFileTrackImporter(Context context, long importTrackId) {
this(context, importTrackId, MyTracksProviderUtils.Factory.get(context));
}
@VisibleForTesting
protected AbstractImporter(
protected AbstractFileTrackImporter(
Context context, long importTrackId, MyTracksProviderUtils myTracksProviderUtils) {
this.context = context;
this.importTrackId = importTrackId;
@@ -31,7 +31,7 @@ import org.xml.sax.SAXException;
*
* @author Jimmy Shih
*/
public class GpxImporter extends AbstractImporter {
public class GpxFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG_DESCRIPTION = "desc";
private static final String TAG_COMMENT = "cmt";
@@ -54,12 +54,12 @@ public class GpxImporter extends AbstractImporter {
* @param context the context
* @param importTrackId track id to import to. -1L to import to a new track.
*/
public GpxImporter(Context context, long importTrackId) {
public GpxFileTrackImporter(Context context, long importTrackId) {
super(context, importTrackId);
}
@VisibleForTesting
public GpxImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
public GpxFileTrackImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
super(context, -1L, myTracksProviderUtils);
}
@@ -158,10 +158,10 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
*/
private boolean importFile(final File file) {
try {
AbstractImporter importer = trackFileFormat == TrackFileFormat.KML ? new KmlImporter(
TrackImporter trackImporter = trackFileFormat == TrackFileFormat.KML ? new KmlFileTrackImporter(
context, -1L)
: new GpxImporter(context, -1L);
long trackIds[] = importer.importFile(new FileInputStream(file));
: new GpxFileTrackImporter(context, -1L);
long trackIds[] = trackImporter.importFile(new FileInputStream(file));
int length = trackIds.length;
if (length > 0) {
trackId = trackIds[length - 1];
@@ -36,7 +36,7 @@ import org.xml.sax.SAXException;
*
* @author Jimmy Shih
*/
public class KmlImporter extends AbstractImporter {
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private static final String CADENCE = "cadence";
private static final String HEART_RATE = "heart_rate";
@@ -61,6 +61,7 @@ public class KmlImporter extends AbstractImporter {
private static final String ATTRIBUTE_NAME = "name";
private boolean trackStarted = false;
private String sensorName;
private ArrayList<Location> locationList;
private ArrayList<Integer> cadenceList;
@@ -73,12 +74,12 @@ public class KmlImporter extends AbstractImporter {
* @param context the context
* @param importTrackId track id to import to. -1L to import to a new track.
*/
public KmlImporter(Context context, long importTrackId) {
public KmlFileTrackImporter(Context context, long importTrackId) {
super(context, importTrackId);
}
@VisibleForTesting
public KmlImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
public KmlFileTrackImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
super(context, -1L, myTracksProviderUtils);
}
@@ -88,8 +89,12 @@ public class KmlImporter extends AbstractImporter {
if (tag.equals(TAG_PLACEMARK)) {
onWaypointStart();
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
trackStarted = true;
onTrackStart();
} else if (tag.equals(TAG_GX_TRACK)) {
if (!trackStarted) {
throw new SAXException("No " + TAG_GX_MULTI_TRACK);
}
onTrackSegmentStart();
} else if (tag.equals(TAG_GX_SIMPLE_ARRAY_DATA)) {
onSensorDataStart(attributes);
@@ -1,5 +1,11 @@
package com.google.android.apps.mytracks.io.file;
import com.google.android.apps.mytracks.io.file.export.CsvTrackWriter;
import com.google.android.apps.mytracks.io.file.export.GpxTrackWriter;
import com.google.android.apps.mytracks.io.file.export.KmlTrackWriter;
import com.google.android.apps.mytracks.io.file.export.TcxTrackWriter;
import com.google.android.apps.mytracks.io.file.export.TrackWriter;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
@@ -12,25 +18,25 @@ import java.util.Locale;
public enum TrackFileFormat implements Parcelable {
KML {
@Override
TrackFormatWriter newFormatWriter(Context context) {
return new KmlTrackWriter(context);
public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new KmlTrackWriter(context, inZip);
}
},
GPX {
@Override
TrackFormatWriter newFormatWriter(Context context) {
public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new GpxTrackWriter(context);
}
},
CSV {
@Override
public TrackFormatWriter newFormatWriter(Context context) {
public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new CsvTrackWriter(context);
}
},
TCX {
@Override
public TrackFormatWriter newFormatWriter(Context context) {
public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new TcxTrackWriter(context);
}
};
@@ -58,9 +64,9 @@ public enum TrackFileFormat implements Parcelable {
};
/**
* Creates and returns a new format writer for each format.
* Creates a new track writer for the format.
*/
abstract TrackFormatWriter newFormatWriter(Context context);
public abstract TrackWriter newTrackWriter(Context context, boolean inZip);
/**
* Returns the mime type for each format.
@@ -0,0 +1,34 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* Interface for a track importer.
*
* @author Jimmy Shih
*/
public interface TrackImporter {
public long[] importFile(InputStream inputStream)
throws SAXException, IOException, ParserConfigurationException;
}
@@ -0,0 +1,68 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file.export;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
/**
* Abstract class for {@link TrackExporter}.
*
* @author Jimmy Shih
*/
abstract class AbstractTrackExporter implements TrackExporter {
private static final String TAG = AbstractTrackExporter.class.getSimpleName();
private Thread writeThread;
private boolean success = false;
@Override
public void writeTrack(final OutputStream outputStream) {
writeThread = new Thread() {
@Override
public void run() {
try {
performWrite(outputStream);
success = true;
} catch (InterruptedException e) {
Log.e(TAG, "Unable to perform write", e);
success = false;
} catch (IOException e) {
Log.e(TAG, "Unable to perform write", e);
success = false;
}
}
};
writeThread.start();
try {
writeThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for write thread to finish", e);
success = false;
}
}
@Override
public boolean isSuccess() {
return success;
}
abstract void performWrite(OutputStream outputStream) throws InterruptedException, IOException;
}
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
@@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.content.Sensor.SensorData;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -48,7 +49,7 @@ import java.text.NumberFormat;
*
* @author Rodrigo Damazio
*/
public class CsvTrackWriter implements TrackFormatWriter {
public class CsvTrackWriter implements TrackWriter {
private static final NumberFormat SHORT_FORMAT = NumberFormat.getInstance();
@@ -77,11 +78,6 @@ public class CsvTrackWriter implements TrackFormatWriter {
pointIndex = 0;
}
@Override
public void close() {
printWriter.close();
}
@Override
public void writeHeader(Track track) {
writeCommaSeparatedLine(context.getString(R.string.generic_name),
@@ -14,7 +14,7 @@
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksLocation;
@@ -22,13 +22,13 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import java.io.OutputStream;
@@ -38,32 +38,12 @@ import java.io.OutputStream;
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class TrackWriter {
/**
* Listener for when a track location is written.
*/
public interface OnWriteListener {
/**
* When a track location is written.
*
* @param number the location number
* @param max the maximum number of locations in a track, for calculation of completion
* percentage
*/
public void onWrite(int number, int max);
}
private final static String TAG = TrackWriter.class.getSimpleName();
public class FileTrackExporter extends AbstractTrackExporter {
private final MyTracksProviderUtils myTracksProviderUtils;
private final Track[] tracks;
private final TrackFormatWriter trackFormatWriter;
private final OnWriteListener onWriteListener;
private Thread writeThread;
private boolean success = false;
private final TrackWriter trackWriter;
private final TrackExporterListener trackExporterListener;
/**
* Constructor.
@@ -72,86 +52,41 @@ public class TrackWriter {
* @param myTracksProviderUtils the my tracks provider utils
* @param tracks the tracks
* @param trackFileFormat the track file format
* @param onWriteListener the on write listener
* @param trackExporterListener the track export listener
*/
public TrackWriter(Context context, MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
TrackFileFormat trackFileFormat, OnWriteListener onWriteListener) {
this(myTracksProviderUtils, tracks, trackFileFormat.newFormatWriter(context), onWriteListener);
public FileTrackExporter(MyTracksProviderUtils myTracksProviderUtils,
Track[] tracks, TrackFileFormat trackFileFormat, Context context, boolean inZip,
TrackExporterListener trackExporterListener) {
this(myTracksProviderUtils, tracks, trackFileFormat.newTrackWriter(context, inZip),
trackExporterListener);
}
@VisibleForTesting
public TrackWriter(MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
TrackFormatWriter trackFormatWriter, OnWriteListener onWriteListener) {
public FileTrackExporter(MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
TrackWriter trackWriter, TrackExporterListener trackExporterListener) {
this.myTracksProviderUtils = myTracksProviderUtils;
this.tracks = tracks;
this.trackFormatWriter = trackFormatWriter;
this.onWriteListener = onWriteListener;
}
/**
* Returns true if the write completed successfully.
*/
public boolean wasSuccess() {
return success;
this.trackWriter = trackWriter;
this.trackExporterListener = trackExporterListener;
}
/**
* Writes the given track to the output stream.
*
* @param outputStream the output stream.
*/
public void writeTrack(final OutputStream outputStream) {
writeThread = new Thread() {
@Override
public void run() {
try {
trackFormatWriter.prepare(outputStream);
trackFormatWriter.writeHeader(tracks[0]);
long startTime = tracks[0].getTripStatistics().getStartTime();
for (int i = 0; i < tracks.length; i++) {
writeWaypoints(tracks[i]);
long offset = tracks[i].getTripStatistics().getStartTime() - startTime;
writeLocations(tracks[i], offset);
}
trackFormatWriter.writeFooter();
trackFormatWriter.close();
success = true;
} catch (InterruptedException e) {
success = false;
}
}
};
writeThread.start();
try {
writeThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for write to complete", e);
success = false;
}
}
/**
* Stops any in-progress writes.
*/
public void stopWriteTrack() {
if (writeThread != null && writeThread.isAlive()) {
Log.i(TAG, "Attempting to stop track write");
writeThread.interrupt();
try {
writeThread.join();
Log.i(TAG, "Track write stopped");
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for writer to stop", e);
success = false;
}
@Override
void performWrite(OutputStream outputStream) throws InterruptedException {
trackWriter.prepare(outputStream);
trackWriter.writeHeader(tracks[0]);
long startTime = tracks[0].getTripStatistics().getStartTime();
for (int i = 0; i < tracks.length; i++) {
writeWaypoints(tracks[i]);
long offset = tracks[i].getTripStatistics().getStartTime() - startTime;
writeLocations(tracks[i], offset);
}
trackWriter.writeFooter();
}
/**
* Writes the waypoints.
*/
private void writeWaypoints(Track track) {
private void writeWaypoints(Track track) throws InterruptedException {
/*
* TODO: Stream through the waypoints in chunks. I am leaving the number of
* waypoints very high which should not be a problem because we don't try to
@@ -168,12 +103,15 @@ public class TrackWriter {
* first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (!hasWaypoints) {
trackFormatWriter.writeBeginWaypoints();
trackWriter.writeBeginWaypoints();
hasWaypoints = true;
}
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
trackFormatWriter.writeWaypoint(waypoint);
trackWriter.writeWaypoint(waypoint);
}
}
} finally {
@@ -182,7 +120,7 @@ public class TrackWriter {
}
}
if (hasWaypoints) {
trackFormatWriter.writeEndWaypoints();
trackWriter.writeEndWaypoints();
}
}
@@ -196,62 +134,63 @@ public class TrackWriter {
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
try {
int locationNumber = 0;
while (iterator.hasNext()) {
Location location = iterator.next();
setLocationTime(location, offset);
if (Thread.interrupted()) {
throw new InterruptedException();
}
Location location = iterator.next();
setLocationTime(location, offset);
locationNumber++;
boolean isLocationValid = LocationUtils.isValidLocation(location);
boolean isSegmentValid = isLocationValid && isLastLocationValid;
if (!wroteTrack && isSegmentValid) {
// Found the first two consecutive locations that are valid
trackFormatWriter.writeBeginTrack(track, locationFactory.lastLocation);
// Found the first two consecutive locations that are valid
trackWriter.writeBeginTrack(track, locationFactory.lastLocation);
wroteTrack = true;
}
if (isSegmentValid) {
if (!wroteSegment) {
// Start a segment
trackFormatWriter.writeOpenSegment();
trackWriter.writeOpenSegment();
wroteSegment = true;
// Write the previous location, which we had previously skipped
trackFormatWriter.writeLocation(locationFactory.lastLocation);
trackWriter.writeLocation(locationFactory.lastLocation);
}
// Write the current location
trackFormatWriter.writeLocation(location);
if (onWriteListener != null) {
onWriteListener.onWrite(locationNumber, track.getNumberOfPoints());
trackWriter.writeLocation(location);
if (trackExporterListener != null) {
trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints());
}
} else {
if (wroteSegment) {
trackFormatWriter.writeCloseSegment();
trackWriter.writeCloseSegment();
wroteSegment = false;
}
}
locationFactory.swapLocations();
isLastLocationValid = isLocationValid;
}
if (wroteSegment) {
trackFormatWriter.writeCloseSegment();
trackWriter.writeCloseSegment();
wroteSegment = false;
}
if (wroteTrack) {
Location lastValidTrackPoint = myTracksProviderUtils.getLastValidTrackPoint(track.getId());
setLocationTime(lastValidTrackPoint, offset);
trackFormatWriter.writeEndTrack(track, lastValidTrackPoint);
trackWriter.writeEndTrack(track, lastValidTrackPoint);
} else {
// Write an empty track
trackFormatWriter.writeBeginTrack(track, null);
trackFormatWriter.writeEndTrack(track, null);
trackWriter.writeBeginTrack(track, null);
trackWriter.writeEndTrack(track, null);
}
} finally {
iterator.close();
@@ -269,7 +208,7 @@ public class TrackWriter {
location.setTime(location.getTime() - offset);
}
}
/**
* Track writer location factory. Keeping the last two locations.
*
@@ -14,10 +14,11 @@
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -34,7 +35,7 @@ import java.util.Locale;
*
* @author Sandor Dornbush
*/
public class GpxTrackWriter implements TrackFormatWriter {
public class GpxTrackWriter implements TrackWriter {
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US);
@@ -68,14 +69,6 @@ public class GpxTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(outputStream);
}
@Override
public void close() {
if (printWriter != null) {
printWriter.close();
printWriter = null;
}
}
@Override
public void writeHeader(Track track) {
if (printWriter != null) {
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
@@ -25,6 +25,7 @@ import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.GoogleEarthUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -35,6 +36,7 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import java.io.File;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -44,7 +46,7 @@ import java.util.ArrayList;
*
* @author Leif Hendrik Wilden
*/
public class KmlTrackWriter implements TrackFormatWriter {
public class KmlTrackWriter implements TrackWriter {
private static final String WAYPOINT_STYLE = "waypoint";
private static final String STATISTICS_STYLE = "statistics";
@@ -68,8 +70,10 @@ public class KmlTrackWriter implements TrackFormatWriter {
TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private final Context context;
private final boolean inKmz;
private final DescriptionGenerator descriptionGenerator;
private final MyTracksProviderUtils myTracksProviderUtils;
private PrintWriter printWriter;
private ArrayList<Integer> powerList = new ArrayList<Integer>();
private ArrayList<Integer> cadenceList = new ArrayList<Integer>();
@@ -78,13 +82,14 @@ public class KmlTrackWriter implements TrackFormatWriter {
private boolean hasCadence;
private boolean hasHeartRate;
public KmlTrackWriter(Context context) {
this(context, new DescriptionGeneratorImpl(context));
public KmlTrackWriter(Context context, boolean inKmz) {
this(context, inKmz, new DescriptionGeneratorImpl(context));
}
@VisibleForTesting
KmlTrackWriter(Context context, DescriptionGenerator descriptionGenerator) {
KmlTrackWriter(Context context, boolean inKmz, DescriptionGenerator descriptionGenerator) {
this.context = context;
this.inKmz = inKmz;
this.descriptionGenerator = descriptionGenerator;
this.myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
}
@@ -99,14 +104,6 @@ public class KmlTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(outputStream);
}
@Override
public void close() {
if (printWriter != null) {
printWriter.close();
printWriter = null;
}
}
@Override
public void writeHeader(Track track) {
if (printWriter != null) {
@@ -335,9 +332,8 @@ public class KmlTrackWriter implements TrackFormatWriter {
if (location != null) {
printWriter.println("<PhotoOverlay>");
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
printWriter.println("<description>"
+ StringUtils.formatCData(StringUtils.getCategoryDescription(category, description))
+ "</description>");
printWriter.println(
"<description>" + StringUtils.formatCData(description) + "</description>");
printWriter.print("<Camera>");
printWriter.print("<longitude>" + location.getLongitude() + "</longitude>");
printWriter.print("<latitude>" + location.getLatitude() + "</latitude>");
@@ -345,8 +341,18 @@ public class KmlTrackWriter implements TrackFormatWriter {
printWriter.print("<heading>" + heading + "</heading>");
printWriter.print("<tilt>90</tilt>");
printWriter.println("</Camera>");
printWriter.println("<TimeStamp><when>"
+ StringUtils.formatDateTimeIso8601(location.getTime()) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
printWriter.println("<Icon><href>" + Uri.decode(photoUrl) + "</href></Icon>");
writeCategory(category);
if (inKmz) {
Uri uri = Uri.parse(photoUrl);
printWriter.println("<Icon><href>" + KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar
+ uri.getLastPathSegment() + "</href></Icon>");
} else {
printWriter.println("<Icon><href>" + Uri.decode(photoUrl) + "</href></Icon>");
}
printWriter.print("<ViewVolume>");
printWriter.print("<near>10</near>");
printWriter.print("<leftFov>-60</leftFov>");
@@ -0,0 +1,143 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import android.database.Cursor;
import android.net.Uri;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* KMZ track exporter.
*
* @author Jimmy Shih
*/
public class KmzTrackExporter extends AbstractTrackExporter {
public static final String KMZ_IMAGES_DIR = "images";
public static final String KMZ_EXTENSION = "kmz";
public static final int BUFFER_SIZE = 4096;
public static final String KML_FILE_NAME = "doc.kml";
private final MyTracksProviderUtils myTracksProviderUtils;
private final FileTrackExporter fileTrackExporter;
private final Track[] tracks;
/**
* Constructor.
*
* @param myTracksProviderUtils the my tracks provider utils
* @param fileTrackExporter the file track exporter
* @param tracks the tracks to export
*/
public KmzTrackExporter(MyTracksProviderUtils myTracksProviderUtils,
FileTrackExporter fileTrackExporter, Track[] tracks) {
this.myTracksProviderUtils = myTracksProviderUtils;
this.fileTrackExporter = fileTrackExporter;
this.tracks = tracks;
}
@Override
public boolean isSuccess() {
return fileTrackExporter.isSuccess() && super.isSuccess();
}
@Override
void performWrite(OutputStream outputStream) throws InterruptedException, IOException {
ZipOutputStream zipOutputStream = null;
try {
zipOutputStream = new ZipOutputStream(outputStream);
// Add kml file
ZipEntry zipEntry = new ZipEntry(KML_FILE_NAME);
zipOutputStream.putNextEntry(zipEntry);
fileTrackExporter.writeTrack(zipOutputStream);
zipOutputStream.closeEntry();
if (!fileTrackExporter.isSuccess()) {
throw new IOException();
}
// Add photos
addImages(zipOutputStream);
} finally {
if (zipOutputStream != null) {
zipOutputStream.close();
}
}
}
private void addImages(ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
for (Track track : tracks) {
Cursor cursor = null;
try {
cursor = myTracksProviderUtils.getWaypointCursor(track.getId(), -1L, -1);
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as
* the first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
String photoUrl = waypoint.getPhotoUrl();
if (photoUrl != null && !photoUrl.equals("")) {
addImage(zipOutputStream, photoUrl);
}
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}
private void addImage(ZipOutputStream zipOutputStream, String photoUrl) throws IOException {
Uri uri = Uri.parse(photoUrl);
ZipEntry zipEntry = new ZipEntry(
KMZ_IMAGES_DIR + File.separatorChar + uri.getLastPathSegment());
zipOutputStream.putNextEntry(zipEntry);
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(new File(uri.getPath()));
byte[] buffer = new byte[BUFFER_SIZE];
int byteCount = 0;
while ((byteCount = fileInputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, byteCount);
}
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
}
zipOutputStream.closeEntry();
}
}
@@ -14,8 +14,9 @@
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.GoogleEarthUtils;
@@ -99,7 +100,7 @@ public class SaveActivity extends Activity {
saveAsyncTask = (SaveAsyncTask) retained;
saveAsyncTask.setActivity(this);
} else {
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackIds, directory);
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackIds, directory, playTrack);
saveAsyncTask.execute();
}
}
@@ -121,7 +122,6 @@ public class SaveActivity extends Activity {
public void onCancel(DialogInterface dialog) {
saveAsyncTask.cancel(true);
dialog.dismiss();
finish();
}
}, directoryDisplayName);
return progressDialog;
@@ -14,11 +14,12 @@
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
@@ -33,7 +34,7 @@ import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
/**
* Async Task to save tracks to the external storage.
@@ -48,11 +49,12 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private final TrackFileFormat trackFileFormat;
private final long[] trackIds;
private final File directory;
private final boolean playTrack;
private final Context context;
private final MyTracksProviderUtils myTracksProviderUtils;
private WakeLock wakeLock;
private TrackWriter trackWriter;
private TrackExporter trackExporter;
// true if the AsyncTask has completed
private boolean completed;
@@ -74,13 +76,15 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* @param trackIds the track ids to save. To save all, set to size 1 with
* trackIds[0] == -1L
* @param directory the directory to save to
* @param playTrack true to play track
*/
public SaveAsyncTask(
SaveActivity saveActivity, TrackFileFormat trackFileFormat, long[] trackIds, File directory) {
public SaveAsyncTask(SaveActivity saveActivity, TrackFileFormat trackFileFormat, long[] trackIds,
File directory, boolean playTrack) {
this.saveActivity = saveActivity;
this.trackFileFormat = trackFileFormat;
this.trackIds = trackIds;
this.directory = directory;
this.playTrack = playTrack;
context = saveActivity.getApplicationContext();
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
@@ -163,8 +167,9 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onCancelled() {
if (trackWriter != null) {
trackWriter.stopWriteTrack();
completed = true;
if (saveActivity != null) {
saveActivity.onAsyncTaskCompleted(successCount, totalCount, null);
}
}
@@ -178,18 +183,21 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
return false;
}
Track track = tracks[0];
boolean useKmz = !playTrack && trackFileFormat == TrackFileFormat.KML;
String extension = useKmz ? KmzTrackExporter.KMZ_EXTENSION : trackFileFormat.getExtension();
// Make sure the file doesn't exist yet (possibly by changing the filename)
String fileName = FileUtils.buildUniqueFileName(
directory, track.getName(), trackFileFormat.getExtension());
String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), extension);
if (fileName == null) {
Log.d(TAG, "Unable to get a unique filename for " + track.getName());
return false;
}
trackWriter = new TrackWriter(
context, myTracksProviderUtils, tracks, trackFileFormat, new TrackWriter.OnWriteListener() {
FileTrackExporter fileTrackExporter = new FileTrackExporter(myTracksProviderUtils, tracks,
trackFileFormat, context, useKmz, new TrackExporterListener() {
@Override
public void onWrite(int number, int max) {
public void onProgressUpdate(int number, int max) {
/*
* If only saving one track, update the progress dialog once every
* 500 points
@@ -200,24 +208,36 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
});
File file = null;
try {
file = new File(directory, fileName);
OutputStream outputStream = new FileOutputStream(file);
trackWriter.writeTrack(outputStream);
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found " + fileName, e);
return false;
}
trackExporter = useKmz ? new KmzTrackExporter(myTracksProviderUtils, fileTrackExporter, tracks)
: fileTrackExporter;
if (trackWriter.wasSuccess()) {
savedPath = file.getAbsolutePath();
} else {
if (!file.delete()) {
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
File file = new File(directory, fileName);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
trackExporter.writeTrack(fileOutputStream);
if (trackExporter.isSuccess()) {
savedPath = file.getAbsolutePath();
return true;
} else {
if (!file.delete()) {
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
}
return false;
}
} catch (FileNotFoundException e) {
Log.e(TAG, "Unable to open file " + file.getName(), e);
return false;
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close file output stream", e);
}
}
}
return trackWriter.wasSuccess();
}
/**
@@ -13,13 +13,14 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
@@ -40,7 +41,7 @@ import java.util.Locale;
* @author Sandor Dornbush
* @author Dominik Ršttsches
*/
public class TcxTrackWriter implements TrackFormatWriter {
public class TcxTrackWriter implements TrackWriter {
/**
* TCX sport type. See the TCX spec.
@@ -96,14 +97,6 @@ public class TcxTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(out);
}
@Override
public void close() {
if (printWriter != null) {
printWriter.close();
printWriter = null;
}
}
@Override
public String getExtension() {
return TrackFileFormat.TCX.getExtension();
@@ -0,0 +1,40 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file.export;
import java.io.OutputStream;
/**
* Track exporting for exporting track to an {@link OutputStream}.
*
* @author Jimmy Shih
*/
public interface TrackExporter {
/**
* Write track to an output stream
*
* @param outputStream the output stream
*/
void writeTrack(OutputStream outputStream);
/**
* Returns true if {@link #writeTrack(OutputStream)} completed successfully.
*/
boolean isSuccess();
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file.export;
/**
* Listener for {@link TrackExporter} progress.
*
* @author Jimmy Shih
*/
public interface TrackExporterListener {
/**
* Called to update progress.
*
* @param number the number of locations written
* @param max the maximum number of locations in a track, for calculation of
* completion percentage
*/
public void onProgressUpdate(int number, int max);
}
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
@@ -50,7 +50,7 @@ import java.io.OutputStream;
*
* @author Rodrigo Damazio
*/
public interface TrackFormatWriter {
public interface TrackWriter {
/**
* Gets the file extension (i.e. gpx, kml, ...)
@@ -64,11 +64,6 @@ public interface TrackFormatWriter {
*/
public void prepare(OutputStream outputStream);
/**
* Closes the underlying file handler.
*/
public void close();
/**
* Writes the header for a file
*
@@ -18,7 +18,7 @@ package com.google.android.apps.mytracks.io.sync;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.file.KmlImporter;
import com.google.android.apps.mytracks.io.file.KmlFileTrackImporter;
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.gms.auth.GoogleAuthException;
@@ -348,9 +348,9 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
if (driveFile != null) {
InputStream inputStream = downloadDriveFile(driveFile, true);
if (inputStream != null) {
KmlImporter kmlImporter = new KmlImporter(context, -1L);
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, -1L);
try {
long[] trackIds = kmlImporter.importFile(inputStream);
long[] trackIds = kmlFileTrackImporter.importFile(inputStream);
if (trackIds.length == 1) {
Track track = myTracksProviderUtils.getTrack(trackIds[0]);
if (track == null) {
@@ -494,9 +494,9 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
Log.e(TAG, "Unable to update track. Input stream is null for track " + track.getName());
return false;
}
KmlImporter kmlImporter = new KmlImporter(context, track.getId());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, track.getId());
try {
long[] trackIds = kmlImporter.importFile(inputStream);
long[] trackIds = kmlFileTrackImporter.importFile(inputStream);
if (trackIds.length == 1) {
Track newTrack = myTracksProviderUtils.getTrack(trackIds[0]);
if (newTrack == null) {
@@ -21,7 +21,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.io.file.TrackWriter;
import com.google.android.apps.mytracks.io.file.export.FileTrackExporter;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
@@ -441,15 +441,31 @@ public class SyncUtils {
java.io.File file = new java.io.File(directory,
FileUtils.buildUniqueFileName(directory, track.getName(), trackFileFormat.getExtension()));
TrackWriter trackWriter = new TrackWriter(
context, myTracksProviderUtils, new Track[] {track}, trackFileFormat, null);
FileTrackExporter fileTrackExporter = new FileTrackExporter(
myTracksProviderUtils, new Track[] { track }, trackFileFormat, context, false, null);
trackWriter.writeTrack(new FileOutputStream(file));
if (trackWriter.wasSuccess()) {
return file;
}
Log.d(TAG, "Unable to get file for track " + track.getName());
return null;
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
fileTrackExporter.writeTrack(fileOutputStream);
if (fileTrackExporter.isSuccess()) {
return file;
} else {
if (!file.delete()) {
Log.d(TAG, "Unable to delete file for track " + track.getName());
}
Log.d(TAG, "Unable to get file for track " + track.getName());
return null;
}
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close file output stream", e);
}
}
}
}
/**
@@ -0,0 +1,172 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.tasks;
import com.google.android.apps.mytracks.util.PhotoUtils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;
import java.io.IOException;
import java.lang.ref.WeakReference;
/**
* A bitmap loader.
*
* @author Jimmy Shih
*/
public class BitmapLoader extends AsyncTask<Void, Void, Bitmap> {
private static final String TAG = BitmapLoader.class.getSimpleName();
private final WeakReference<ImageView> imageViewReference;
private final Uri uri;
private final int displayWidth;
private final int displayHeight;
public BitmapLoader(ImageView imageView, Uri uri, int displayWidth, int displayHeight) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
this.uri = uri;
this.displayWidth = displayWidth;
this.displayHeight = displayHeight;
}
public Uri getUri() {
return uri;
}
@Override
protected Bitmap doInBackground(Void... params) {
// Get the image dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(uri.getPath(), options);
// Set targetWidth and targetHeight
int targetWidth = displayWidth;
int targetHeight = displayHeight;
if (targetHeight == 0) {
targetHeight = (int) (targetWidth * ((float) options.outHeight / (float) options.outWidth));
}
// Set imageWidth and imageHeight based on image rotation
int rotation = getRotation();
int imageWidth;
int imageHeight;
if (rotation == 0 || rotation == 180) {
imageWidth = options.outWidth;
imageHeight = options.outHeight;
} else {
imageWidth = options.outHeight;
imageHeight = options.outWidth;
}
// Get a scaled down version of the image
options.inJustDecodeBounds = false;
options.inSampleSize = getInSampleSize(imageWidth, imageHeight, targetWidth, targetHeight);
options.inPurgeable = true;
Bitmap scaledBitmap = BitmapFactory.decodeFile(uri.getPath(), options);
// Get the final bitmap after rotating the scaled down image
Bitmap bitmap;
if (rotation == 0) {
bitmap = scaledBitmap;
} else {
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
bitmap = Bitmap.createBitmap(
scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
scaledBitmap.recycle();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
// If imageView is still around, set bitmap
if (imageViewReference != null && bitmap != null) {
ImageView imageView = imageViewReference.get();
if (imageView != null) {
BitmapLoader bitmapLoader = PhotoUtils.getBitmapLoader(imageView);
if (this == bitmapLoader) {
imageView.setImageBitmap(bitmap);
}
}
}
}
private int getRotation() {
try {
ExifInterface exifInterface = new ExifInterface(uri.getPath());
switch (exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
default:
return 0;
}
} catch (IOException e) {
Log.e(TAG, "Unable to get photo orientation", e);
return 0;
}
}
/**
* Gets the in sample size.
*
* @param imageWidth the image width
* @param imageHeight the image height
* @param targetWidth the target width
* @param targetHeight the target height
*/
private int getInSampleSize(int imageWidth, int imageHeight, int targetWidth, int targetHeight) {
float widthRatio = 1;
if (imageWidth > targetWidth) {
widthRatio = (float) imageWidth / (float) targetWidth;
}
float heightRatio = 1;
if (imageHeight > targetHeight) {
heightRatio = (float) imageHeight / (float) targetHeight;
}
/*
* To fit within the target area, return the larger sample ratio so the
* image will not be larger than the target dimensions. Use Math.floor to
* not under sample.
*/
return (int) Math.floor(Math.max(widthRatio, heightRatio));
}
}
@@ -16,15 +16,12 @@
package com.google.android.apps.mytracks.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import com.google.android.apps.mytracks.services.tasks.BitmapLoader;
import android.net.Uri;
import android.util.Log;
import android.widget.ImageView;
import java.io.IOException;
import java.lang.ref.WeakReference;
/**
* Utilities for photos.
@@ -33,8 +30,6 @@ import java.io.IOException;
*/
public class PhotoUtils {
private static final String TAG = PhotoUtils.class.getSimpleName();
private PhotoUtils() {}
/**
@@ -45,108 +40,55 @@ public class PhotoUtils {
* @param displayWidth the display width
* @param displayHeight the display height
*/
public static Bitmap setImageVew(
public static void setImageVew(
ImageView imageView, Uri uri, int displayWidth, int displayHeight) {
// Get the image dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(uri.getPath(), options);
// Set targetWidth and targetHeight
int targetWidth = displayWidth;
int targetHeight = displayHeight;
if (targetHeight == 0) {
targetHeight = (int) (targetWidth * ((float) options.outHeight / (float) options.outWidth));
if (cancelBitmapLoader(imageView, uri)) {
BitmapLoader bitmapLoader = new BitmapLoader(imageView, uri, displayWidth, displayHeight);
WeakReference<BitmapLoader> bitmapLoaderReference = new WeakReference<BitmapLoader>(
bitmapLoader);
imageView.setTag(bitmapLoaderReference);
bitmapLoader.execute();
}
// Set imageWidth and imageHeight based on image rotation
int rotation = getRotation(uri);
int imageWidth;
int imageHeight;
if (rotation == 0 || rotation == 180) {
imageWidth = options.outWidth;
imageHeight = options.outHeight;
} else {
imageWidth = options.outHeight;
imageHeight = options.outWidth;
}
// Get a scaled down version of the image
options.inJustDecodeBounds = false;
options.inSampleSize = getInSampleSize(imageWidth, imageHeight, targetWidth, targetHeight);
options.inPurgeable = true;
Bitmap scaledBitmap = BitmapFactory.decodeFile(uri.getPath(), options);
// Get the final bitmap after rotating the scaled down image
Bitmap bitmap;
if (rotation == 0) {
bitmap = scaledBitmap;
} else {
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
bitmap = Bitmap.createBitmap(
scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
scaledBitmap.recycle();
}
imageView.setImageBitmap(bitmap);
return bitmap;
}
/**
* Gets the image rotation
* Gets the image view bitmap loader.
*
* @param uri the image uri
* @param imageView the image view
*/
private static int getRotation(Uri uri) {
try {
ExifInterface exifInterface = new ExifInterface(uri.getPath());
switch (exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
default:
return 0;
public static BitmapLoader getBitmapLoader(ImageView imageView) {
if (imageView != null) {
Object object = imageView.getTag();
if (object instanceof WeakReference<?>) {
@SuppressWarnings("unchecked")
WeakReference<BitmapLoader> bitmapLoaderReference = (WeakReference<BitmapLoader>) object;
return bitmapLoaderReference.get();
}
} catch (IOException e) {
Log.e(TAG, "Unable to get photo orientation", e);
return 0;
}
return null;
}
/**
* Gets the in sample size.
* Cancels the image view bitmap loader.
*
* @param imageWidth the image width
* @param imageHeight the image height
* @param targetWidth the target width
* @param targetHeight the target height
* @param imageView the image view
* @param uri the uri
* @return false if the bitmap loader shouldn't be canceled. True if there is
* no bitmap loader or the bitmap loader is cancelled.
*/
private static int getInSampleSize(
int imageWidth, int imageHeight, int targetWidth, int targetHeight) {
float widthRatio = 1;
if (imageWidth > targetWidth) {
widthRatio = (float) imageWidth / (float) targetWidth;
}
private static boolean cancelBitmapLoader(ImageView imageView, Uri uri) {
BitmapLoader bitmapLoaderAsyncTask = getBitmapLoader(imageView);
float heightRatio = 1;
if (imageHeight > targetHeight) {
heightRatio = (float) imageHeight / (float) targetHeight;
if (bitmapLoaderAsyncTask != null) {
if (bitmapLoaderAsyncTask.getUri().equals(uri)) {
// same bitmap loader is already in progress, don't cancel
return false;
} else {
// cancel previous bitmap loader
bitmapLoaderAsyncTask.cancel(true);
}
}
/*
* To fit within the target area, return the larger sample ratio so the
* image will not be larger than the target dimensions. Use Math.floor to
* not under sample.
*/
return (int) Math.floor(Math.max(widthRatio, heightRatio));
// imageview has no bitmap loader, or an existing bitmap loader is cancelled
return true;
}
}
@@ -42,11 +42,11 @@ import java.util.SimpleTimeZone;
import org.easymock.Capture;
/**
* Abstract class for testing importer.
* Abstract class for testing file track importers.
*
* @author Jimmy Shih.
*/
public class AbstractTestImporter extends AndroidTestCase {
public class AbstractTestFileTrackImporter extends AndroidTestCase {
protected static final String TRACK_NAME_0 = "blablub";
protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
@@ -31,11 +31,11 @@ import org.easymock.Capture;
import org.xml.sax.SAXException;
/**
* Tests for {@link GpxImporter}.
* Tests for {@link GpxFileTrackImporter}.
*
* @author Steffen Horlacher
*/
public class GpxImporterTest extends AbstractTestImporter {
public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static String getNameAndDescription(String name, String description) {
return "<name><![CDATA[" + name + "]]></name>" + "<desc><![CDATA[" + description + "]]></desc>";
@@ -107,8 +107,9 @@ public class GpxImporterTest extends AbstractTestImporter {
AndroidMock.replay(myTracksProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
long[] trackIds = gpxImporter.importFile(inputStream);
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), myTracksProviderUtils);
long[] trackIds = gpxFileTrackImporter.importFile(inputStream);
assertEquals(1, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
@@ -140,8 +141,9 @@ public class GpxImporterTest extends AbstractTestImporter {
AndroidMock.replay(myTracksProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
long[] trackIds = gpxImporter.importFile(inputStream);
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), myTracksProviderUtils);
long[] trackIds = gpxFileTrackImporter.importFile(inputStream);
assertEquals(1, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
@@ -177,8 +179,9 @@ public class GpxImporterTest extends AbstractTestImporter {
InputStream inputStream = new ByteArrayInputStream(
VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
long[] trackIds = gpxImporter.importFile(inputStream);
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), myTracksProviderUtils);
long[] trackIds = gpxFileTrackImporter.importFile(inputStream);
assertEquals(1, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
@@ -212,8 +215,9 @@ public class GpxImporterTest extends AbstractTestImporter {
AndroidMock.replay(myTracksProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_TWO_TRACKS_GPX.getBytes());
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
long[] trackIds = gpxImporter.importFile(inputStream);
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), myTracksProviderUtils);
long[] trackIds = gpxFileTrackImporter.importFile(inputStream);
assertEquals(2, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
assertEquals(TRACK_ID_1, trackIds[1]);
@@ -279,8 +283,9 @@ public class GpxImporterTest extends AbstractTestImporter {
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
try {
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
gpxImporter.importFile(inputStream);
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), myTracksProviderUtils);
gpxFileTrackImporter.importFile(inputStream);
} catch (SAXException e) {
// expected
}
@@ -30,11 +30,11 @@ import java.io.InputStream;
import org.easymock.Capture;
/**
* Tests for {@link KmlImporter}.
* Tests for {@link KmlFileTrackImporter}.
*
* @author Jimmy Shih
*/
public class KmlImporterTest extends AbstractTestImporter {
public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static String getNameAndDescription(String name, String description) {
return "<name><![CDATA[" + name + "]]></name><description><![CDATA[" + description
@@ -85,8 +85,9 @@ public class KmlImporterTest extends AbstractTestImporter {
AndroidMock.replay(myTracksProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
KmlImporter kmlImporter = new KmlImporter(getContext(), myTracksProviderUtils);
long[] trackIds = kmlImporter.importFile(inputStream);
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(
getContext(), myTracksProviderUtils);
long[] trackIds = kmlFileTrackImporter.importFile(inputStream);
assertEquals(1, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
@@ -118,8 +119,9 @@ public class KmlImporterTest extends AbstractTestImporter {
AndroidMock.replay(myTracksProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
KmlImporter kmlImporter = new KmlImporter(getContext(), myTracksProviderUtils);
long[] trackIds = kmlImporter.importFile(inputStream);
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(
getContext(), myTracksProviderUtils);
long[] trackIds = kmlFileTrackImporter.importFile(inputStream);
assertEquals(1, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
@@ -13,7 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.io.file.export.CsvTrackWriter;
/**
@@ -21,7 +23,7 @@ package com.google.android.apps.mytracks.io.file;
*
* @author Rodrigo Damazio
*/
public class CsvTrackWriterTest extends TrackFormatWriterTest {
public class CsvTrackWriterTest extends TrackWriterTest {
private static final String BEGIN_TAG = "\"";
private static final String END_TAG = "\"\n";
@@ -62,8 +64,8 @@ public class CsvTrackWriterTest extends TrackFormatWriterTest {
+ expectedMarkerHeader + expectedMarker1 + expectedMarker2 + "\n"
+ expectedPointHeader + expectedPoint1 + expectedPoint2 + expectedPoint3 + expectedPoint4;
CsvTrackWriter writer = new CsvTrackWriter(getContext());
assertEquals(expected, writeTrack(writer));
CsvTrackWriter csvTrackWriter = new CsvTrackWriter(getContext());
assertEquals(expected, writeTrack(csvTrackWriter));
}
/**
@@ -1,13 +1,15 @@
// Copyright 2010 Google Inc. All Rights Reserved.
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksProvider;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackWriter.OnWriteListener;
import com.google.android.apps.mytracks.io.file.export.FileTrackExporter;
import com.google.android.apps.mytracks.io.file.export.TrackExporterListener;
import com.google.android.apps.mytracks.io.file.export.TrackWriter;
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
@@ -25,11 +27,11 @@ import org.easymock.IArgumentMatcher;
import org.easymock.IMocksControl;
/**
* Tests for {@link TrackWriter}.
* Tests for {@link FileTrackExporter}.
*
* @author Rodrigo Damazio
*/
public class TrackWriterTest extends AndroidTestCase {
public class FileTrackExporterTest extends AndroidTestCase {
private static final long TRACK_ID = 1234567L;
private static final String TRACK_NAME = "Swimming across the pacific";
@@ -39,10 +41,10 @@ public class TrackWriterTest extends AndroidTestCase {
private IMocksControl mocksControl;
private TrackFormatWriter trackFormatWriter;
private TrackWriter trackWriter;
private Track track;
private OutputStream outputStream;
private TrackWriter trackWriter;
private FileTrackExporter fileTrackExporter;
@Override
protected void setUp() throws Exception {
@@ -61,22 +63,23 @@ public class TrackWriterTest extends AndroidTestCase {
myTracksProviderUtils);
mocksControl = EasyMock.createStrictControl();
trackFormatWriter = mocksControl.createMock(TrackFormatWriter.class);
trackWriter = mocksControl.createMock(TrackWriter.class);
track = new Track();
track.setName(TRACK_NAME);
track.setId(TRACK_ID);
outputStream = new ByteArrayOutputStream();
OnWriteListener onWriteListener = new OnWriteListener() {
TrackExporterListener trackExporterListener = new TrackExporterListener() {
@Override
public void onWrite(int number, int max) {
public void onProgressUpdate(int number, int max) {
// Safe to ignore
}
};
trackWriter = new TrackWriter(myTracksProviderUtils, new Track[] {track}, trackFormatWriter, onWriteListener);
fileTrackExporter = new FileTrackExporter(
myTracksProviderUtils, new Track[] { track }, trackWriter, trackExporterListener);
}
@Override
@@ -91,17 +94,16 @@ public class TrackWriterTest extends AndroidTestCase {
public void testWriteTrack_emptyTrack() throws Exception {
// Set expected mock behavior
trackFormatWriter.prepare(outputStream);
trackFormatWriter.writeHeader(track);
trackFormatWriter.writeBeginTrack(track, null);
trackFormatWriter.writeEndTrack(track, null);
trackFormatWriter.writeFooter();
trackFormatWriter.close();
trackWriter.prepare(outputStream);
trackWriter.writeHeader(track);
trackWriter.writeBeginTrack(track, null);
trackWriter.writeEndTrack(track, null);
trackWriter.writeFooter();
mocksControl.replay();
trackWriter.writeTrack(outputStream);
fileTrackExporter.writeTrack(outputStream);
assertTrue(trackWriter.wasSuccess());
assertTrue(fileTrackExporter.isSuccess());
mocksControl.verify();
}
@@ -123,17 +125,16 @@ public class TrackWriterTest extends AndroidTestCase {
myTracksProviderUtils.bulkInsertTrackPoint(locations, locations.length, TRACK_ID));
// Set expected mock behavior
trackFormatWriter.prepare(outputStream);
trackFormatWriter.writeHeader(track);
trackFormatWriter.writeBeginTrack(track, null);
trackFormatWriter.writeEndTrack(track, null);
trackFormatWriter.writeFooter();
trackFormatWriter.close();
trackWriter.prepare(outputStream);
trackWriter.writeHeader(track);
trackWriter.writeBeginTrack(track, null);
trackWriter.writeEndTrack(track, null);
trackWriter.writeFooter();
mocksControl.replay();
trackWriter.writeTrack(outputStream);
fileTrackExporter.writeTrack(outputStream);
assertTrue(trackWriter.wasSuccess());
assertTrue(fileTrackExporter.isSuccess());
mocksControl.verify();
}
@@ -162,43 +163,42 @@ public class TrackWriterTest extends AndroidTestCase {
waypoint.setId(i + 1);
}
trackFormatWriter.prepare(outputStream);
trackFormatWriter.writeHeader(track);
trackWriter.prepare(outputStream);
trackWriter.writeHeader(track);
// Expect reading/writing of the waypoints (except the first)
trackFormatWriter.writeBeginWaypoints();
trackFormatWriter.writeWaypoint(waypointEq(waypoints[1]));
trackFormatWriter.writeWaypoint(waypointEq(waypoints[2]));
trackFormatWriter.writeEndWaypoints();
trackWriter.writeBeginWaypoints();
trackWriter.writeWaypoint(waypointEq(waypoints[1]));
trackWriter.writeWaypoint(waypointEq(waypoints[2]));
trackWriter.writeEndWaypoints();
// Begin the track
trackFormatWriter.writeBeginTrack(trackEq(track), locationEq(locations[0]));
trackWriter.writeBeginTrack(trackEq(track), locationEq(locations[0]));
// Write locations 1-2
trackFormatWriter.writeOpenSegment();
trackFormatWriter.writeLocation(locationEq(locations[0]));
trackFormatWriter.writeLocation(locationEq(locations[1]));
trackFormatWriter.writeCloseSegment();
trackWriter.writeOpenSegment();
trackWriter.writeLocation(locationEq(locations[0]));
trackWriter.writeLocation(locationEq(locations[1]));
trackWriter.writeCloseSegment();
// Location 3 is not written - it's invalid
// Write locations 4-6
trackFormatWriter.writeOpenSegment();
trackFormatWriter.writeLocation(locationEq(locations[3]));
trackFormatWriter.writeLocation(locationEq(locations[4]));
trackFormatWriter.writeLocation(locationEq(locations[5]));
trackFormatWriter.writeCloseSegment();
trackWriter.writeOpenSegment();
trackWriter.writeLocation(locationEq(locations[3]));
trackWriter.writeLocation(locationEq(locations[4]));
trackWriter.writeLocation(locationEq(locations[5]));
trackWriter.writeCloseSegment();
// End the track
trackFormatWriter.writeEndTrack(trackEq(track), locationEq(locations[5]));
trackWriter.writeEndTrack(trackEq(track), locationEq(locations[5]));
trackFormatWriter.writeFooter();
trackFormatWriter.close();
trackWriter.writeFooter();
mocksControl.replay();
trackWriter.writeTrack(outputStream);
fileTrackExporter.writeTrack(outputStream);
assertTrue(trackWriter.wasSuccess());
assertTrue(fileTrackExporter.isSuccess());
mocksControl.verify();
}
@@ -13,7 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.io.file.export.GpxTrackWriter;
import java.util.List;
@@ -25,11 +27,11 @@ import org.w3c.dom.Element;
*
* @author Rodrigo Damazio
*/
public class GpxTrackWriterTest extends TrackFormatWriterTest {
public class GpxTrackWriterTest extends TrackWriterTest {
public void testXmlOutput() throws Exception {
TrackFormatWriter writer = new GpxTrackWriter(getContext());
String result = writeTrack(writer);
GpxTrackWriter gpxTrackWriter = new GpxTrackWriter(getContext());
String result = writeTrack(gpxTrackWriter);
Document doc = parseXmlDocument(result);
Element gpxTag = getChildElement(doc, "gpx");
@@ -13,10 +13,11 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.file.export.KmlTrackWriter;
import com.google.android.apps.mytracks.stats.TripStatistics;
import android.location.Location;
@@ -32,7 +33,7 @@ import org.w3c.dom.Element;
*
* @author Rodrigo Damazio
*/
public class KmlTrackWriterTest extends TrackFormatWriterTest {
public class KmlTrackWriterTest extends TrackWriterTest {
private static final String FULL_TRACK_DESCRIPTION = "full track description";
@@ -54,8 +55,9 @@ public class KmlTrackWriterTest extends TrackFormatWriterTest {
}
public void testXmlOutput() throws Exception {
KmlTrackWriter writer = new KmlTrackWriter(getContext(), new FakeDescriptionGenerator());
String result = writeTrack(writer);
KmlTrackWriter kmlTrackWriter = new KmlTrackWriter(
getContext(), false, new FakeDescriptionGenerator());
String result = writeTrack(kmlTrackWriter);
Document doc = parseXmlDocument(result);
Element kmlTag = getChildElement(doc, "kml");
@@ -13,10 +13,11 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.io.file.export.TcxTrackWriter;
import com.google.android.apps.mytracks.util.StringUtils;
import java.util.List;
@@ -29,11 +30,11 @@ import org.w3c.dom.Element;
*
* @author Sandor Dornbush
*/
public class TcxTrackWriterTest extends TrackFormatWriterTest {
public class TcxTrackWriterTest extends TrackWriterTest {
public void testXmlOutput() throws Exception {
TrackFormatWriter writer = new TcxTrackWriter(getContext());
String result = writeTrack(writer);
TcxTrackWriter tcxTrackWriter = new TcxTrackWriter(getContext());
String result = writeTrack(tcxTrackWriter);
Document doc = parseXmlDocument(result);
Element root = getChildElement(doc, "TrainingCenterDatabase");
@@ -1,10 +1,11 @@
// Copyright 2010 Google Inc. All Rights Reserved.
package com.google.android.apps.mytracks.io.file;
package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.export.TrackWriter;
import android.test.AndroidTestCase;
@@ -34,7 +35,7 @@ import org.xml.sax.SAXException;
*
* @author Rodrigo Damazio
*/
public abstract class TrackFormatWriterTest extends AndroidTestCase {
public abstract class TrackWriterTest extends AndroidTestCase {
// All the user-provided strings have "]]>" to ensure that proper escaping is
// being done.
@@ -110,29 +111,28 @@ public abstract class TrackFormatWriterTest extends AndroidTestCase {
* Makes the right sequence of calls to the writer in order to write the fake
* track in {@link #track}.
*
* @param writer the writer to write to
* @param trackWriter the track writer
* @return the written contents
*/
protected String writeTrack(TrackFormatWriter writer) throws Exception {
protected String writeTrack(TrackWriter trackWriter) throws Exception {
OutputStream output = new ByteArrayOutputStream(BUFFER_SIZE);
writer.prepare(output);
writer.writeHeader(track);
writer.writeBeginWaypoints();
writer.writeWaypoint(wp1);
writer.writeWaypoint(wp2);
writer.writeEndWaypoints();
writer.writeBeginTrack(track, location1);
writer.writeOpenSegment();
writer.writeLocation(location1);
writer.writeLocation(location2);
writer.writeCloseSegment();
writer.writeOpenSegment();
writer.writeLocation(location3);
writer.writeLocation(location4);
writer.writeCloseSegment();
writer.writeEndTrack(track, location4);
writer.writeFooter();
writer.close();
trackWriter.prepare(output);
trackWriter.writeHeader(track);
trackWriter.writeBeginWaypoints();
trackWriter.writeWaypoint(wp1);
trackWriter.writeWaypoint(wp2);
trackWriter.writeEndWaypoints();
trackWriter.writeBeginTrack(track, location1);
trackWriter.writeOpenSegment();
trackWriter.writeLocation(location1);
trackWriter.writeLocation(location2);
trackWriter.writeCloseSegment();
trackWriter.writeOpenSegment();
trackWriter.writeLocation(location3);
trackWriter.writeLocation(location4);
trackWriter.writeCloseSegment();
trackWriter.writeEndTrack(track, location4);
trackWriter.writeFooter();
return output.toString();
}