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