diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml
index 902073628..92f70d73f 100755
--- a/MyTracks/AndroidManifest.xml
+++ b/MyTracks/AndroidManifest.xml
@@ -214,7 +214,7 @@ limitations under the License.
-
+
diff --git a/MyTracks/src/com/google/android/apps/mytracks/AbstractSendToGoogleActivity.java b/MyTracks/src/com/google/android/apps/mytracks/AbstractSendToGoogleActivity.java
index d75c93e59..3ba648619 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/AbstractSendToGoogleActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/AbstractSendToGoogleActivity.java
@@ -30,8 +30,8 @@ import com.google.android.apps.mytracks.fragments.InstallEarthDialogFragment;
import com.google.android.apps.mytracks.fragments.ShareTrackDialogFragment;
import com.google.android.apps.mytracks.fragments.ShareTrackDialogFragment.ShareTrackCaller;
import com.google.android.apps.mytracks.io.drive.SendDriveActivity;
-import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
+import com.google.android.apps.mytracks.io.file.export.SaveActivity;
import com.google.android.apps.mytracks.io.fusiontables.SendFusionTablesActivity;
import com.google.android.apps.mytracks.io.gdata.maps.MapsConstants;
import com.google.android.apps.mytracks.io.maps.SendMapsActivity;
diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
index 0218afa42..680f087e3 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
@@ -30,8 +30,8 @@ import com.google.android.apps.mytracks.fragments.ExportDialogFragment.ExportTyp
import com.google.android.apps.mytracks.fragments.FrequencyDialogFragment;
import com.google.android.apps.mytracks.fragments.MyTracksMapFragment;
import com.google.android.apps.mytracks.fragments.StatsFragment;
-import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
+import com.google.android.apps.mytracks.io.file.export.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.settings.SettingsActivity;
diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java
index b0aba0b1a..177969fc7 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java
@@ -27,8 +27,8 @@ import com.google.android.apps.mytracks.fragments.EulaDialogFragment.EulaCaller;
import com.google.android.apps.mytracks.fragments.FileTypeDialogFragment;
import com.google.android.apps.mytracks.fragments.FileTypeDialogFragment.FileTypeCaller;
import com.google.android.apps.mytracks.io.file.ImportActivity;
-import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
+import com.google.android.apps.mytracks.io.file.export.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.io.sync.SyncUtils;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
index a155d5ed8..c15a75543 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
@@ -117,8 +117,10 @@ public class TrackDataHub implements DataSourceListener {
runInHanderThread(new Runnable() {
@Override
public void run() {
- dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
- loadDataForAll();
+ if (dataSourceManager != null) {
+ dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
+ loadDataForAll();
+ }
}
});
}
@@ -173,9 +175,11 @@ public class TrackDataHub implements DataSourceListener {
runInHanderThread(new Runnable() {
@Override
public void run() {
- trackDataManager.registerListener(trackDataListener, trackDataTypes);
- dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
- loadDataForListener(trackDataListener);
+ if (dataSourceManager != null) {
+ trackDataManager.registerListener(trackDataListener, trackDataTypes);
+ dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
+ loadDataForListener(trackDataListener);
+ }
}
});
}
@@ -189,8 +193,10 @@ public class TrackDataHub implements DataSourceListener {
runInHanderThread(new Runnable() {
@Override
public void run() {
- trackDataManager.unregisterListener(trackDataListener);
- dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
+ if (dataSourceManager != null) {
+ trackDataManager.unregisterListener(trackDataListener);
+ dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
+ }
}
});
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java
index 90252c35c..7ec6ab351 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java
@@ -37,6 +37,7 @@ import com.google.common.annotations.VisibleForTesting;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -172,7 +173,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
tripStatisticsUpdater = startTime != -1L ? new TripStatisticsUpdater(startTime) : null;
pendingPoints.clear();
chartView.reset();
- getActivity().runOnUiThread(new Runnable() {
+ runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
@@ -211,7 +212,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
if (isResumed()) {
chartView.addDataPoints(pendingPoints);
pendingPoints.clear();
- getActivity().runOnUiThread(updateChart);
+ runOnUiThread(updateChart);
}
}
@@ -232,7 +233,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override
public void onNewWaypointsDone() {
if (isResumed()) {
- getActivity().runOnUiThread(updateChart);
+ runOnUiThread(updateChart);
}
}
@@ -244,7 +245,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
metricUnits = metric;
chartView.setMetricUnits(metricUnits);
- getActivity().runOnUiThread(new Runnable() {
+ runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
@@ -269,7 +270,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
- getActivity().runOnUiThread(new Runnable() {
+ runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
@@ -414,6 +415,18 @@ public class ChartFragment extends Fragment implements TrackDataListener {
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
+ /**
+ * Runs a runnable on the UI thread if possible.
+ *
+ * @param runnable the runnable
+ */
+ private void runOnUiThread(Runnable runnable) {
+ FragmentActivity fragmentActivity = getActivity();
+ if (fragmentActivity != null) {
+ fragmentActivity.runOnUiThread(runnable);
+ }
+ }
+
/**
* Given a location, fill in a data point, an array of double[].
* data[0] = time/distance
diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/CheckPermissionFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/CheckPermissionFragment.java
index 8d9503878..7200c5b21 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/fragments/CheckPermissionFragment.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/CheckPermissionFragment.java
@@ -98,7 +98,14 @@ public class CheckPermissionFragment extends Fragment {
SendToGoogleUtils.getGoogleAccountCredential(getActivity(), accountName, scope);
finish(scope, true, null);
} catch (UserRecoverableAuthException e) {
- finish(scope, false, e.getIntent());
+ Intent intent = null;
+ try {
+ // HACK: UserRecoverableAuthException.getIntent can throw a null pointer exception.
+ intent = e.getIntent();
+ } catch (Exception e1) {
+ Log.e(TAG, "Exception in getIntent", e1);
+ }
+ finish(scope, false, intent);
} catch (GoogleAuthException e) {
Log.e(TAG, "GoogleAuthException", e);
finish(scope, false, null);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java
index 3f1de531a..8cdaa71e5 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java
@@ -28,7 +28,6 @@ import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
-import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@@ -66,7 +65,6 @@ public class MarkerDetailFragment extends Fragment {
private MyTracksProviderUtils myTracksProviderUtils;
private Waypoint waypoint;
- private Bitmap bitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -97,13 +95,6 @@ public class MarkerDetailFragment extends Fragment {
update();
}
- public void onPause() {
- super.onPause();
- if (bitmap != null) {
- bitmap.recycle();
- }
- }
-
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.marker_detail, menu);
@@ -180,8 +171,7 @@ public class MarkerDetailFragment extends Fragment {
int displayWidth = defaultDisplay.getWidth();
@SuppressWarnings("deprecation")
int displayHeight = defaultDisplay.getHeight();
- bitmap = PhotoUtils.setImageVew(
- imageView, Uri.parse(photoUrl), displayWidth, displayHeight);
+ PhotoUtils.setImageVew(imageView, Uri.parse(photoUrl), displayWidth, displayHeight);
}
TextView name = (TextView) getView().findViewById(R.id.marker_detail_waypoint_name);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractFileTrackImporter.java
similarity index 98%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractImporter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractFileTrackImporter.java
index 31bc378c3..3f3580405 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractImporter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractFileTrackImporter.java
@@ -54,12 +54,12 @@ import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
- * Abstract class for various importers like {@link GpxImporter} and
- * {@link KmlImporter}.
+ * Abstract class for various file track importers like {@link GpxFileTrackImporter} and
+ * {@link KmlFileTrackImporter}.
*
* @author Jimmy Shih
*/
-abstract class AbstractImporter extends DefaultHandler {
+abstract class AbstractFileTrackImporter extends DefaultHandler implements TrackImporter {
/**
* Data for the current track.
@@ -131,12 +131,12 @@ abstract class AbstractImporter extends DefaultHandler {
* @param importTrackId the track id to import to. -1L to import to a new
* track.
*/
- protected AbstractImporter(Context context, long importTrackId) {
+ protected AbstractFileTrackImporter(Context context, long importTrackId) {
this(context, importTrackId, MyTracksProviderUtils.Factory.get(context));
}
@VisibleForTesting
- protected AbstractImporter(
+ protected AbstractFileTrackImporter(
Context context, long importTrackId, MyTracksProviderUtils myTracksProviderUtils) {
this.context = context;
this.importTrackId = importTrackId;
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxFileTrackImporter.java
similarity index 95%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/GpxFileTrackImporter.java
index d4b97697b..bbbe122bc 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxFileTrackImporter.java
@@ -31,7 +31,7 @@ import org.xml.sax.SAXException;
*
* @author Jimmy Shih
*/
-public class GpxImporter extends AbstractImporter {
+public class GpxFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG_DESCRIPTION = "desc";
private static final String TAG_COMMENT = "cmt";
@@ -54,12 +54,12 @@ public class GpxImporter extends AbstractImporter {
* @param context the context
* @param importTrackId track id to import to. -1L to import to a new track.
*/
- public GpxImporter(Context context, long importTrackId) {
+ public GpxFileTrackImporter(Context context, long importTrackId) {
super(context, importTrackId);
}
@VisibleForTesting
- public GpxImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
+ public GpxFileTrackImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
super(context, -1L, myTracksProviderUtils);
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportAsyncTask.java
index 281b1b887..20f38f37b 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportAsyncTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportAsyncTask.java
@@ -158,10 +158,10 @@ public class ImportAsyncTask extends AsyncTask {
*/
private boolean importFile(final File file) {
try {
- AbstractImporter importer = trackFileFormat == TrackFileFormat.KML ? new KmlImporter(
+ TrackImporter trackImporter = trackFileFormat == TrackFileFormat.KML ? new KmlFileTrackImporter(
context, -1L)
- : new GpxImporter(context, -1L);
- long trackIds[] = importer.importFile(new FileInputStream(file));
+ : new GpxFileTrackImporter(context, -1L);
+ long trackIds[] = trackImporter.importFile(new FileInputStream(file));
int length = trackIds.length;
if (length > 0) {
trackId = trackIds[length - 1];
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlFileTrackImporter.java
similarity index 95%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/KmlImporter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/KmlFileTrackImporter.java
index a15c8800e..76762acb9 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlImporter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlFileTrackImporter.java
@@ -36,7 +36,7 @@ import org.xml.sax.SAXException;
*
* @author Jimmy Shih
*/
-public class KmlImporter extends AbstractImporter {
+public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private static final String CADENCE = "cadence";
private static final String HEART_RATE = "heart_rate";
@@ -61,6 +61,7 @@ public class KmlImporter extends AbstractImporter {
private static final String ATTRIBUTE_NAME = "name";
+ private boolean trackStarted = false;
private String sensorName;
private ArrayList locationList;
private ArrayList cadenceList;
@@ -73,12 +74,12 @@ public class KmlImporter extends AbstractImporter {
* @param context the context
* @param importTrackId track id to import to. -1L to import to a new track.
*/
- public KmlImporter(Context context, long importTrackId) {
+ public KmlFileTrackImporter(Context context, long importTrackId) {
super(context, importTrackId);
}
@VisibleForTesting
- public KmlImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
+ public KmlFileTrackImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
super(context, -1L, myTracksProviderUtils);
}
@@ -88,8 +89,12 @@ public class KmlImporter extends AbstractImporter {
if (tag.equals(TAG_PLACEMARK)) {
onWaypointStart();
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
+ trackStarted = true;
onTrackStart();
} else if (tag.equals(TAG_GX_TRACK)) {
+ if (!trackStarted) {
+ throw new SAXException("No " + TAG_GX_MULTI_TRACK);
+ }
onTrackSegmentStart();
} else if (tag.equals(TAG_GX_SIMPLE_ARRAY_DATA)) {
onSensorDataStart(attributes);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFileFormat.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFileFormat.java
index b4362ee80..74ac72955 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFileFormat.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFileFormat.java
@@ -1,5 +1,11 @@
package com.google.android.apps.mytracks.io.file;
+import com.google.android.apps.mytracks.io.file.export.CsvTrackWriter;
+import com.google.android.apps.mytracks.io.file.export.GpxTrackWriter;
+import com.google.android.apps.mytracks.io.file.export.KmlTrackWriter;
+import com.google.android.apps.mytracks.io.file.export.TcxTrackWriter;
+import com.google.android.apps.mytracks.io.file.export.TrackWriter;
+
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
@@ -12,25 +18,25 @@ import java.util.Locale;
public enum TrackFileFormat implements Parcelable {
KML {
@Override
- TrackFormatWriter newFormatWriter(Context context) {
- return new KmlTrackWriter(context);
+ public TrackWriter newTrackWriter(Context context, boolean inZip) {
+ return new KmlTrackWriter(context, inZip);
}
},
GPX {
@Override
- TrackFormatWriter newFormatWriter(Context context) {
+ public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new GpxTrackWriter(context);
}
},
CSV {
@Override
- public TrackFormatWriter newFormatWriter(Context context) {
+ public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new CsvTrackWriter(context);
}
},
TCX {
@Override
- public TrackFormatWriter newFormatWriter(Context context) {
+ public TrackWriter newTrackWriter(Context context, boolean inZip) {
return new TcxTrackWriter(context);
}
};
@@ -58,9 +64,9 @@ public enum TrackFileFormat implements Parcelable {
};
/**
- * Creates and returns a new format writer for each format.
+ * Creates a new track writer for the format.
*/
- abstract TrackFormatWriter newFormatWriter(Context context);
+ public abstract TrackWriter newTrackWriter(Context context, boolean inZip);
/**
* Returns the mime type for each format.
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackImporter.java
new file mode 100644
index 000000000..11620e71d
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackImporter.java
@@ -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;
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/export/AbstractTrackExporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/AbstractTrackExporter.java
new file mode 100644
index 000000000..6cd9692c7
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/AbstractTrackExporter.java
@@ -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;
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/CsvTrackWriter.java
similarity index 97%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/export/CsvTrackWriter.java
index 7a95a7aaa..860ee9a86 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/CsvTrackWriter.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.android.apps.mytracks.io.file;
+package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
@@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.content.Sensor.SensorData;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -48,7 +49,7 @@ import java.text.NumberFormat;
*
* @author Rodrigo Damazio
*/
-public class CsvTrackWriter implements TrackFormatWriter {
+public class CsvTrackWriter implements TrackWriter {
private static final NumberFormat SHORT_FORMAT = NumberFormat.getInstance();
@@ -77,11 +78,6 @@ public class CsvTrackWriter implements TrackFormatWriter {
pointIndex = 0;
}
- @Override
- public void close() {
- printWriter.close();
- }
-
@Override
public void writeHeader(Track track) {
writeCommaSeparatedLine(context.getString(R.string.generic_name),
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/FileTrackExporter.java
similarity index 59%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/export/FileTrackExporter.java
index 3a67fa81d..d6e577714 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/FileTrackExporter.java
@@ -14,7 +14,7 @@
* the License.
*/
-package com.google.android.apps.mytracks.io.file;
+package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksLocation;
@@ -22,13 +22,13 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
-import android.util.Log;
import java.io.OutputStream;
@@ -38,32 +38,12 @@ import java.io.OutputStream;
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
-public class TrackWriter {
-
- /**
- * Listener for when a track location is written.
- */
- public interface OnWriteListener {
-
- /**
- * When a track location is written.
- *
- * @param number the location number
- * @param max the maximum number of locations in a track, for calculation of completion
- * percentage
- */
- public void onWrite(int number, int max);
- }
-
- private final static String TAG = TrackWriter.class.getSimpleName();
+public class FileTrackExporter extends AbstractTrackExporter {
private final MyTracksProviderUtils myTracksProviderUtils;
private final Track[] tracks;
- private final TrackFormatWriter trackFormatWriter;
- private final OnWriteListener onWriteListener;
-
- private Thread writeThread;
- private boolean success = false;
+ private final TrackWriter trackWriter;
+ private final TrackExporterListener trackExporterListener;
/**
* Constructor.
@@ -72,86 +52,41 @@ public class TrackWriter {
* @param myTracksProviderUtils the my tracks provider utils
* @param tracks the tracks
* @param trackFileFormat the track file format
- * @param onWriteListener the on write listener
+ * @param trackExporterListener the track export listener
*/
- public TrackWriter(Context context, MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
- TrackFileFormat trackFileFormat, OnWriteListener onWriteListener) {
- this(myTracksProviderUtils, tracks, trackFileFormat.newFormatWriter(context), onWriteListener);
+ public FileTrackExporter(MyTracksProviderUtils myTracksProviderUtils,
+ Track[] tracks, TrackFileFormat trackFileFormat, Context context, boolean inZip,
+ TrackExporterListener trackExporterListener) {
+ this(myTracksProviderUtils, tracks, trackFileFormat.newTrackWriter(context, inZip),
+ trackExporterListener);
}
@VisibleForTesting
- public TrackWriter(MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
- TrackFormatWriter trackFormatWriter, OnWriteListener onWriteListener) {
+ public FileTrackExporter(MyTracksProviderUtils myTracksProviderUtils, Track[] tracks,
+ TrackWriter trackWriter, TrackExporterListener trackExporterListener) {
this.myTracksProviderUtils = myTracksProviderUtils;
this.tracks = tracks;
- this.trackFormatWriter = trackFormatWriter;
- this.onWriteListener = onWriteListener;
- }
-
- /**
- * Returns true if the write completed successfully.
- */
- public boolean wasSuccess() {
- return success;
+ this.trackWriter = trackWriter;
+ this.trackExporterListener = trackExporterListener;
}
- /**
- * Writes the given track to the output stream.
- *
- * @param outputStream the output stream.
- */
- public void writeTrack(final OutputStream outputStream) {
- writeThread = new Thread() {
- @Override
- public void run() {
- try {
- trackFormatWriter.prepare(outputStream);
- trackFormatWriter.writeHeader(tracks[0]);
- long startTime = tracks[0].getTripStatistics().getStartTime();
- for (int i = 0; i < tracks.length; i++) {
- writeWaypoints(tracks[i]);
- long offset = tracks[i].getTripStatistics().getStartTime() - startTime;
- writeLocations(tracks[i], offset);
- }
- trackFormatWriter.writeFooter();
- trackFormatWriter.close();
- success = true;
- } catch (InterruptedException e) {
- success = false;
- }
- }
- };
- writeThread.start();
- try {
- writeThread.join();
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for write to complete", e);
- success = false;
- }
- }
-
- /**
- * Stops any in-progress writes.
- */
- public void stopWriteTrack() {
- if (writeThread != null && writeThread.isAlive()) {
- Log.i(TAG, "Attempting to stop track write");
- writeThread.interrupt();
-
- try {
- writeThread.join();
- Log.i(TAG, "Track write stopped");
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for writer to stop", e);
- success = false;
- }
+ @Override
+ void performWrite(OutputStream outputStream) throws InterruptedException {
+ trackWriter.prepare(outputStream);
+ trackWriter.writeHeader(tracks[0]);
+ long startTime = tracks[0].getTripStatistics().getStartTime();
+ for (int i = 0; i < tracks.length; i++) {
+ writeWaypoints(tracks[i]);
+ long offset = tracks[i].getTripStatistics().getStartTime() - startTime;
+ writeLocations(tracks[i], offset);
}
+ trackWriter.writeFooter();
}
/**
* Writes the waypoints.
*/
- private void writeWaypoints(Track track) {
+ private void writeWaypoints(Track track) throws InterruptedException {
/*
* TODO: Stream through the waypoints in chunks. I am leaving the number of
* waypoints very high which should not be a problem because we don't try to
@@ -168,12 +103,15 @@ public class TrackWriter {
* first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
if (!hasWaypoints) {
- trackFormatWriter.writeBeginWaypoints();
+ trackWriter.writeBeginWaypoints();
hasWaypoints = true;
}
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
- trackFormatWriter.writeWaypoint(waypoint);
+ trackWriter.writeWaypoint(waypoint);
}
}
} finally {
@@ -182,7 +120,7 @@ public class TrackWriter {
}
}
if (hasWaypoints) {
- trackFormatWriter.writeEndWaypoints();
+ trackWriter.writeEndWaypoints();
}
}
@@ -196,62 +134,63 @@ public class TrackWriter {
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
-
+
try {
int locationNumber = 0;
while (iterator.hasNext()) {
- Location location = iterator.next();
- setLocationTime(location, offset);
if (Thread.interrupted()) {
throw new InterruptedException();
}
+ Location location = iterator.next();
+
+ setLocationTime(location, offset);
locationNumber++;
boolean isLocationValid = LocationUtils.isValidLocation(location);
boolean isSegmentValid = isLocationValid && isLastLocationValid;
if (!wroteTrack && isSegmentValid) {
- // Found the first two consecutive locations that are valid
- trackFormatWriter.writeBeginTrack(track, locationFactory.lastLocation);
+ // Found the first two consecutive locations that are valid
+ trackWriter.writeBeginTrack(track, locationFactory.lastLocation);
wroteTrack = true;
}
if (isSegmentValid) {
if (!wroteSegment) {
// Start a segment
- trackFormatWriter.writeOpenSegment();
+ trackWriter.writeOpenSegment();
wroteSegment = true;
// Write the previous location, which we had previously skipped
- trackFormatWriter.writeLocation(locationFactory.lastLocation);
+ trackWriter.writeLocation(locationFactory.lastLocation);
}
// Write the current location
- trackFormatWriter.writeLocation(location);
- if (onWriteListener != null) {
- onWriteListener.onWrite(locationNumber, track.getNumberOfPoints());
+ trackWriter.writeLocation(location);
+ if (trackExporterListener != null) {
+ trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints());
}
} else {
if (wroteSegment) {
- trackFormatWriter.writeCloseSegment();
+ trackWriter.writeCloseSegment();
wroteSegment = false;
}
}
locationFactory.swapLocations();
isLastLocationValid = isLocationValid;
}
-
+
if (wroteSegment) {
- trackFormatWriter.writeCloseSegment();
+ trackWriter.writeCloseSegment();
wroteSegment = false;
}
if (wroteTrack) {
Location lastValidTrackPoint = myTracksProviderUtils.getLastValidTrackPoint(track.getId());
setLocationTime(lastValidTrackPoint, offset);
- trackFormatWriter.writeEndTrack(track, lastValidTrackPoint);
+ trackWriter.writeEndTrack(track, lastValidTrackPoint);
} else {
// Write an empty track
- trackFormatWriter.writeBeginTrack(track, null);
- trackFormatWriter.writeEndTrack(track, null);
+ trackWriter.writeBeginTrack(track, null);
+ trackWriter.writeEndTrack(track, null);
}
} finally {
iterator.close();
@@ -269,7 +208,7 @@ public class TrackWriter {
location.setTime(location.getTime() - offset);
}
}
-
+
/**
* Track writer location factory. Keeping the last two locations.
*
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/GpxTrackWriter.java
similarity index 96%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/export/GpxTrackWriter.java
index 75d45dad5..40fc5c0f8 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/GpxTrackWriter.java
@@ -14,10 +14,11 @@
* the License.
*/
-package com.google.android.apps.mytracks.io.file;
+package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -34,7 +35,7 @@ import java.util.Locale;
*
* @author Sandor Dornbush
*/
-public class GpxTrackWriter implements TrackFormatWriter {
+public class GpxTrackWriter implements TrackWriter {
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US);
@@ -68,14 +69,6 @@ public class GpxTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(outputStream);
}
- @Override
- public void close() {
- if (printWriter != null) {
- printWriter.close();
- printWriter = null;
- }
- }
-
@Override
public void writeHeader(Track track) {
if (printWriter != null) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/KmlTrackWriter.java
similarity index 93%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/export/KmlTrackWriter.java
index 0fc315a96..c9086a4a4 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/KmlTrackWriter.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.android.apps.mytracks.io.file;
+package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
@@ -25,6 +25,7 @@ import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
+import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.GoogleEarthUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -35,6 +36,7 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
+import java.io.File;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -44,7 +46,7 @@ import java.util.ArrayList;
*
* @author Leif Hendrik Wilden
*/
-public class KmlTrackWriter implements TrackFormatWriter {
+public class KmlTrackWriter implements TrackWriter {
private static final String WAYPOINT_STYLE = "waypoint";
private static final String STATISTICS_STYLE = "statistics";
@@ -68,8 +70,10 @@ public class KmlTrackWriter implements TrackFormatWriter {
TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private final Context context;
+ private final boolean inKmz;
private final DescriptionGenerator descriptionGenerator;
private final MyTracksProviderUtils myTracksProviderUtils;
+
private PrintWriter printWriter;
private ArrayList powerList = new ArrayList();
private ArrayList cadenceList = new ArrayList();
@@ -78,13 +82,14 @@ public class KmlTrackWriter implements TrackFormatWriter {
private boolean hasCadence;
private boolean hasHeartRate;
- public KmlTrackWriter(Context context) {
- this(context, new DescriptionGeneratorImpl(context));
+ public KmlTrackWriter(Context context, boolean inKmz) {
+ this(context, inKmz, new DescriptionGeneratorImpl(context));
}
@VisibleForTesting
- KmlTrackWriter(Context context, DescriptionGenerator descriptionGenerator) {
+ KmlTrackWriter(Context context, boolean inKmz, DescriptionGenerator descriptionGenerator) {
this.context = context;
+ this.inKmz = inKmz;
this.descriptionGenerator = descriptionGenerator;
this.myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
}
@@ -99,14 +104,6 @@ public class KmlTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(outputStream);
}
- @Override
- public void close() {
- if (printWriter != null) {
- printWriter.close();
- printWriter = null;
- }
- }
-
@Override
public void writeHeader(Track track) {
if (printWriter != null) {
@@ -335,9 +332,8 @@ public class KmlTrackWriter implements TrackFormatWriter {
if (location != null) {
printWriter.println("");
printWriter.println("" + StringUtils.formatCData(name) + "");
- printWriter.println(""
- + StringUtils.formatCData(StringUtils.getCategoryDescription(category, description))
- + "");
+ printWriter.println(
+ "" + StringUtils.formatCData(description) + "");
printWriter.print("");
printWriter.print("" + location.getLongitude() + "");
printWriter.print("" + location.getLatitude() + "");
@@ -345,8 +341,18 @@ public class KmlTrackWriter implements TrackFormatWriter {
printWriter.print("" + heading + "");
printWriter.print("90");
printWriter.println("");
+ printWriter.println(""
+ + StringUtils.formatDateTimeIso8601(location.getTime()) + "");
printWriter.println("#" + styleName + "");
- printWriter.println("" + Uri.decode(photoUrl) + "");
+ writeCategory(category);
+ if (inKmz) {
+ Uri uri = Uri.parse(photoUrl);
+ printWriter.println("" + KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar
+ + uri.getLastPathSegment() + "");
+ } else {
+ printWriter.println("" + Uri.decode(photoUrl) + "");
+ }
+
printWriter.print("");
printWriter.print("10");
printWriter.print("-60");
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/export/KmzTrackExporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/KmzTrackExporter.java
new file mode 100644
index 000000000..99bdf6f17
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/KmzTrackExporter.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/SaveActivity.java
similarity index 98%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/export/SaveActivity.java
index c2d45c2ef..a8658951d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/SaveActivity.java
@@ -14,8 +14,9 @@
* the License.
*/
-package com.google.android.apps.mytracks.io.file;
+package com.google.android.apps.mytracks.io.file.export;
+import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.GoogleEarthUtils;
@@ -99,7 +100,7 @@ public class SaveActivity extends Activity {
saveAsyncTask = (SaveAsyncTask) retained;
saveAsyncTask.setActivity(this);
} else {
- saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackIds, directory);
+ saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackIds, directory, playTrack);
saveAsyncTask.execute();
}
}
@@ -121,7 +122,6 @@ public class SaveActivity extends Activity {
public void onCancel(DialogInterface dialog) {
saveAsyncTask.cancel(true);
dialog.dismiss();
- finish();
}
}, directoryDisplayName);
return progressDialog;
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/SaveAsyncTask.java
similarity index 77%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/SaveAsyncTask.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/export/SaveAsyncTask.java
index 89236f6bd..708b18ac2 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveAsyncTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/SaveAsyncTask.java
@@ -14,11 +14,12 @@
* the License.
*/
-package com.google.android.apps.mytracks.io.file;
+package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
+import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
@@ -33,7 +34,7 @@ import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.OutputStream;
+import java.io.IOException;
/**
* Async Task to save tracks to the external storage.
@@ -48,11 +49,12 @@ public class SaveAsyncTask extends AsyncTask {
private final TrackFileFormat trackFileFormat;
private final long[] trackIds;
private final File directory;
+ private final boolean playTrack;
private final Context context;
private final MyTracksProviderUtils myTracksProviderUtils;
private WakeLock wakeLock;
- private TrackWriter trackWriter;
+ private TrackExporter trackExporter;
// true if the AsyncTask has completed
private boolean completed;
@@ -74,13 +76,15 @@ public class SaveAsyncTask extends AsyncTask {
* @param trackIds the track ids to save. To save all, set to size 1 with
* trackIds[0] == -1L
* @param directory the directory to save to
+ * @param playTrack true to play track
*/
- public SaveAsyncTask(
- SaveActivity saveActivity, TrackFileFormat trackFileFormat, long[] trackIds, File directory) {
+ public SaveAsyncTask(SaveActivity saveActivity, TrackFileFormat trackFileFormat, long[] trackIds,
+ File directory, boolean playTrack) {
this.saveActivity = saveActivity;
this.trackFileFormat = trackFileFormat;
this.trackIds = trackIds;
this.directory = directory;
+ this.playTrack = playTrack;
context = saveActivity.getApplicationContext();
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
@@ -163,8 +167,9 @@ public class SaveAsyncTask extends AsyncTask {
@Override
protected void onCancelled() {
- if (trackWriter != null) {
- trackWriter.stopWriteTrack();
+ completed = true;
+ if (saveActivity != null) {
+ saveActivity.onAsyncTaskCompleted(successCount, totalCount, null);
}
}
@@ -178,18 +183,21 @@ public class SaveAsyncTask extends AsyncTask {
return false;
}
Track track = tracks[0];
+ boolean useKmz = !playTrack && trackFileFormat == TrackFileFormat.KML;
+ String extension = useKmz ? KmzTrackExporter.KMZ_EXTENSION : trackFileFormat.getExtension();
+
// Make sure the file doesn't exist yet (possibly by changing the filename)
- String fileName = FileUtils.buildUniqueFileName(
- directory, track.getName(), trackFileFormat.getExtension());
+ String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), extension);
if (fileName == null) {
Log.d(TAG, "Unable to get a unique filename for " + track.getName());
return false;
}
- trackWriter = new TrackWriter(
- context, myTracksProviderUtils, tracks, trackFileFormat, new TrackWriter.OnWriteListener() {
+ FileTrackExporter fileTrackExporter = new FileTrackExporter(myTracksProviderUtils, tracks,
+ trackFileFormat, context, useKmz, new TrackExporterListener() {
+
@Override
- public void onWrite(int number, int max) {
+ public void onProgressUpdate(int number, int max) {
/*
* If only saving one track, update the progress dialog once every
* 500 points
@@ -200,24 +208,36 @@ public class SaveAsyncTask extends AsyncTask {
}
});
- File file = null;
- try {
- file = new File(directory, fileName);
- OutputStream outputStream = new FileOutputStream(file);
- trackWriter.writeTrack(outputStream);
- } catch (FileNotFoundException e) {
- Log.d(TAG, "File not found " + fileName, e);
- return false;
- }
+ trackExporter = useKmz ? new KmzTrackExporter(myTracksProviderUtils, fileTrackExporter, tracks)
+ : fileTrackExporter;
- if (trackWriter.wasSuccess()) {
- savedPath = file.getAbsolutePath();
- } else {
- if (!file.delete()) {
- Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
+ File file = new File(directory, fileName);
+ FileOutputStream fileOutputStream = null;
+ try {
+ fileOutputStream = new FileOutputStream(file);
+ trackExporter.writeTrack(fileOutputStream);
+
+ if (trackExporter.isSuccess()) {
+ savedPath = file.getAbsolutePath();
+ return true;
+ } else {
+ if (!file.delete()) {
+ Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
+ }
+ return false;
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to open file " + file.getName(), e);
+ return false;
+ } finally {
+ if (fileOutputStream != null) {
+ try {
+ fileOutputStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to close file output stream", e);
+ }
}
}
- return trackWriter.wasSuccess();
}
/**
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TcxTrackWriter.java
similarity index 97%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/export/TcxTrackWriter.java
index 827b2a3b1..6f6a7b74d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TcxTrackWriter.java
@@ -13,13 +13,14 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.android.apps.mytracks.io.file;
+package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
@@ -40,7 +41,7 @@ import java.util.Locale;
* @author Sandor Dornbush
* @author Dominik RĀttsches
*/
-public class TcxTrackWriter implements TrackFormatWriter {
+public class TcxTrackWriter implements TrackWriter {
/**
* TCX sport type. See the TCX spec.
@@ -96,14 +97,6 @@ public class TcxTrackWriter implements TrackFormatWriter {
this.printWriter = new PrintWriter(out);
}
- @Override
- public void close() {
- if (printWriter != null) {
- printWriter.close();
- printWriter = null;
- }
- }
-
@Override
public String getExtension() {
return TrackFileFormat.TCX.getExtension();
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackExporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackExporter.java
new file mode 100644
index 000000000..6b6c6156f
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackExporter.java
@@ -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();
+
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackExporterListener.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackExporterListener.java
new file mode 100644
index 000000000..984ad4aeb
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackExporterListener.java
@@ -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);
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFormatWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackWriter.java
similarity index 94%
rename from MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFormatWriter.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackWriter.java
index 79b61b5f3..cf98cb761 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFormatWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/export/TrackWriter.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.android.apps.mytracks.io.file;
+package com.google.android.apps.mytracks.io.file.export;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
@@ -50,7 +50,7 @@ import java.io.OutputStream;
*
* @author Rodrigo Damazio
*/
-public interface TrackFormatWriter {
+public interface TrackWriter {
/**
* Gets the file extension (i.e. gpx, kml, ...)
@@ -64,11 +64,6 @@ public interface TrackFormatWriter {
*/
public void prepare(OutputStream outputStream);
- /**
- * Closes the underlying file handler.
- */
- public void close();
-
/**
* Writes the header for a file
*
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java
index eace3dd56..9d5855175 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java
@@ -18,7 +18,7 @@ package com.google.android.apps.mytracks.io.sync;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
-import com.google.android.apps.mytracks.io.file.KmlImporter;
+import com.google.android.apps.mytracks.io.file.KmlFileTrackImporter;
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.gms.auth.GoogleAuthException;
@@ -348,9 +348,9 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
if (driveFile != null) {
InputStream inputStream = downloadDriveFile(driveFile, true);
if (inputStream != null) {
- KmlImporter kmlImporter = new KmlImporter(context, -1L);
+ KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, -1L);
try {
- long[] trackIds = kmlImporter.importFile(inputStream);
+ long[] trackIds = kmlFileTrackImporter.importFile(inputStream);
if (trackIds.length == 1) {
Track track = myTracksProviderUtils.getTrack(trackIds[0]);
if (track == null) {
@@ -494,9 +494,9 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
Log.e(TAG, "Unable to update track. Input stream is null for track " + track.getName());
return false;
}
- KmlImporter kmlImporter = new KmlImporter(context, track.getId());
+ KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, track.getId());
try {
- long[] trackIds = kmlImporter.importFile(inputStream);
+ long[] trackIds = kmlFileTrackImporter.importFile(inputStream);
if (trackIds.length == 1) {
Track newTrack = myTracksProviderUtils.getTrack(trackIds[0]);
if (newTrack == null) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncUtils.java b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncUtils.java
index 54471774c..d7dd5e82f 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncUtils.java
@@ -21,7 +21,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
-import com.google.android.apps.mytracks.io.file.TrackWriter;
+import com.google.android.apps.mytracks.io.file.export.FileTrackExporter;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
@@ -441,15 +441,31 @@ public class SyncUtils {
java.io.File file = new java.io.File(directory,
FileUtils.buildUniqueFileName(directory, track.getName(), trackFileFormat.getExtension()));
- TrackWriter trackWriter = new TrackWriter(
- context, myTracksProviderUtils, new Track[] {track}, trackFileFormat, null);
+ FileTrackExporter fileTrackExporter = new FileTrackExporter(
+ myTracksProviderUtils, new Track[] { track }, trackFileFormat, context, false, null);
- trackWriter.writeTrack(new FileOutputStream(file));
- if (trackWriter.wasSuccess()) {
- return file;
- }
- Log.d(TAG, "Unable to get file for track " + track.getName());
- return null;
+ FileOutputStream fileOutputStream = null;
+ try {
+ fileOutputStream = new FileOutputStream(file);
+ fileTrackExporter.writeTrack(fileOutputStream);
+ if (fileTrackExporter.isSuccess()) {
+ return file;
+ } else {
+ if (!file.delete()) {
+ Log.d(TAG, "Unable to delete file for track " + track.getName());
+ }
+ Log.d(TAG, "Unable to get file for track " + track.getName());
+ return null;
+ }
+ } finally {
+ if (fileOutputStream != null) {
+ try {
+ fileOutputStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to close file output stream", e);
+ }
+ }
+ }
}
/**
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/BitmapLoader.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/BitmapLoader.java
new file mode 100644
index 000000000..2513e4d7b
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/BitmapLoader.java
@@ -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 {
+ private static final String TAG = BitmapLoader.class.getSimpleName();
+
+ private final WeakReference 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);
+ 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));
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java
index f29f96fec..80da1d43d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java
@@ -16,15 +16,12 @@
package com.google.android.apps.mytracks.util;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
-import android.media.ExifInterface;
+import com.google.android.apps.mytracks.services.tasks.BitmapLoader;
+
import android.net.Uri;
-import android.util.Log;
import android.widget.ImageView;
-import java.io.IOException;
+import java.lang.ref.WeakReference;
/**
* Utilities for photos.
@@ -33,8 +30,6 @@ import java.io.IOException;
*/
public class PhotoUtils {
- private static final String TAG = PhotoUtils.class.getSimpleName();
-
private PhotoUtils() {}
/**
@@ -45,108 +40,55 @@ public class PhotoUtils {
* @param displayWidth the display width
* @param displayHeight the display height
*/
- public static Bitmap setImageVew(
+ public static void setImageVew(
ImageView imageView, Uri uri, int displayWidth, int displayHeight) {
-
- // Get the image dimensions
- BitmapFactory.Options options = new BitmapFactory.Options();
-
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(uri.getPath(), options);
-
- // Set targetWidth and targetHeight
- int targetWidth = displayWidth;
- int targetHeight = displayHeight;
- if (targetHeight == 0) {
- targetHeight = (int) (targetWidth * ((float) options.outHeight / (float) options.outWidth));
+ if (cancelBitmapLoader(imageView, uri)) {
+ BitmapLoader bitmapLoader = new BitmapLoader(imageView, uri, displayWidth, displayHeight);
+ WeakReference bitmapLoaderReference = new WeakReference(
+ bitmapLoader);
+ imageView.setTag(bitmapLoaderReference);
+ bitmapLoader.execute();
}
-
- // Set imageWidth and imageHeight based on image rotation
- int rotation = getRotation(uri);
- int imageWidth;
- int imageHeight;
-
- if (rotation == 0 || rotation == 180) {
- imageWidth = options.outWidth;
- imageHeight = options.outHeight;
- } else {
- imageWidth = options.outHeight;
- imageHeight = options.outWidth;
- }
-
- // Get a scaled down version of the image
- options.inJustDecodeBounds = false;
- options.inSampleSize = getInSampleSize(imageWidth, imageHeight, targetWidth, targetHeight);
- options.inPurgeable = true;
-
- Bitmap scaledBitmap = BitmapFactory.decodeFile(uri.getPath(), options);
-
- // Get the final bitmap after rotating the scaled down image
- Bitmap bitmap;
- if (rotation == 0) {
- bitmap = scaledBitmap;
- } else {
- Matrix matrix = new Matrix();
- matrix.postRotate(rotation);
- bitmap = Bitmap.createBitmap(
- scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
- scaledBitmap.recycle();
- }
-
- imageView.setImageBitmap(bitmap);
- return bitmap;
}
/**
- * Gets the image rotation
+ * Gets the image view bitmap loader.
*
- * @param uri the image uri
+ * @param imageView the image view
*/
- private static int getRotation(Uri uri) {
- try {
- ExifInterface exifInterface = new ExifInterface(uri.getPath());
- switch (exifInterface.getAttributeInt(
- ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
- case ExifInterface.ORIENTATION_ROTATE_90:
- return 90;
- case ExifInterface.ORIENTATION_ROTATE_180:
- return 180;
- case ExifInterface.ORIENTATION_ROTATE_270:
- return 270;
- default:
- return 0;
+ public static BitmapLoader getBitmapLoader(ImageView imageView) {
+ if (imageView != null) {
+ Object object = imageView.getTag();
+ if (object instanceof WeakReference>) {
+ @SuppressWarnings("unchecked")
+ WeakReference bitmapLoaderReference = (WeakReference) object;
+ return bitmapLoaderReference.get();
}
- } catch (IOException e) {
- Log.e(TAG, "Unable to get photo orientation", e);
- return 0;
}
+ return null;
}
/**
- * Gets the in sample size.
+ * Cancels the image view bitmap loader.
*
- * @param imageWidth the image width
- * @param imageHeight the image height
- * @param targetWidth the target width
- * @param targetHeight the target height
+ * @param imageView the image view
+ * @param uri the uri
+ * @return false if the bitmap loader shouldn't be canceled. True if there is
+ * no bitmap loader or the bitmap loader is cancelled.
*/
- private static int getInSampleSize(
- int imageWidth, int imageHeight, int targetWidth, int targetHeight) {
- float widthRatio = 1;
- if (imageWidth > targetWidth) {
- widthRatio = (float) imageWidth / (float) targetWidth;
- }
+ private static boolean cancelBitmapLoader(ImageView imageView, Uri uri) {
+ BitmapLoader bitmapLoaderAsyncTask = getBitmapLoader(imageView);
- float heightRatio = 1;
- if (imageHeight > targetHeight) {
- heightRatio = (float) imageHeight / (float) targetHeight;
+ if (bitmapLoaderAsyncTask != null) {
+ if (bitmapLoaderAsyncTask.getUri().equals(uri)) {
+ // same bitmap loader is already in progress, don't cancel
+ return false;
+ } else {
+ // cancel previous bitmap loader
+ bitmapLoaderAsyncTask.cancel(true);
+ }
}
-
- /*
- * To fit within the target area, return the larger sample ratio so the
- * image will not be larger than the target dimensions. Use Math.floor to
- * not under sample.
- */
- return (int) Math.floor(Math.max(widthRatio, heightRatio));
+ // imageview has no bitmap loader, or an existing bitmap loader is cancelled
+ return true;
}
}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/AbstractTestImporter.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/AbstractTestFileTrackImporter.java
similarity index 98%
rename from MyTracksTest/src/com/google/android/apps/mytracks/io/file/AbstractTestImporter.java
rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/AbstractTestFileTrackImporter.java
index 55059418e..2334dfc66 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/AbstractTestImporter.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/AbstractTestFileTrackImporter.java
@@ -42,11 +42,11 @@ import java.util.SimpleTimeZone;
import org.easymock.Capture;
/**
- * Abstract class for testing importer.
+ * Abstract class for testing file track importers.
*
* @author Jimmy Shih.
*/
-public class AbstractTestImporter extends AndroidTestCase {
+public class AbstractTestFileTrackImporter extends AndroidTestCase {
protected static final String TRACK_NAME_0 = "blablub";
protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxImporterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxFileTrackImporterTest.java
similarity index 91%
rename from MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxImporterTest.java
rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxFileTrackImporterTest.java
index 48ed37630..f9999ca87 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxImporterTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxFileTrackImporterTest.java
@@ -31,11 +31,11 @@ import org.easymock.Capture;
import org.xml.sax.SAXException;
/**
- * Tests for {@link GpxImporter}.
+ * Tests for {@link GpxFileTrackImporter}.
*
* @author Steffen Horlacher
*/
-public class GpxImporterTest extends AbstractTestImporter {
+public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static String getNameAndDescription(String name, String description) {
return "" + "";
@@ -107,8 +107,9 @@ public class GpxImporterTest extends AbstractTestImporter {
AndroidMock.replay(myTracksProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
- GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
- long[] trackIds = gpxImporter.importFile(inputStream);
+ GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
+ getContext(), myTracksProviderUtils);
+ long[] trackIds = gpxFileTrackImporter.importFile(inputStream);
assertEquals(1, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
@@ -140,8 +141,9 @@ public class GpxImporterTest extends AbstractTestImporter {
AndroidMock.replay(myTracksProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
- GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
- long[] trackIds = gpxImporter.importFile(inputStream);
+ GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
+ getContext(), myTracksProviderUtils);
+ long[] trackIds = gpxFileTrackImporter.importFile(inputStream);
assertEquals(1, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
@@ -177,8 +179,9 @@ public class GpxImporterTest extends AbstractTestImporter {
InputStream inputStream = new ByteArrayInputStream(
VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
- GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
- long[] trackIds = gpxImporter.importFile(inputStream);
+ GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
+ getContext(), myTracksProviderUtils);
+ long[] trackIds = gpxFileTrackImporter.importFile(inputStream);
assertEquals(1, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
@@ -212,8 +215,9 @@ public class GpxImporterTest extends AbstractTestImporter {
AndroidMock.replay(myTracksProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_TWO_TRACKS_GPX.getBytes());
- GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
- long[] trackIds = gpxImporter.importFile(inputStream);
+ GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
+ getContext(), myTracksProviderUtils);
+ long[] trackIds = gpxFileTrackImporter.importFile(inputStream);
assertEquals(2, trackIds.length);
assertEquals(TRACK_ID_0, trackIds[0]);
assertEquals(TRACK_ID_1, trackIds[1]);
@@ -279,8 +283,9 @@ public class GpxImporterTest extends AbstractTestImporter {
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
try {
- GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
- gpxImporter.importFile(inputStream);
+ GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
+ getContext(), myTracksProviderUtils);
+ gpxFileTrackImporter.importFile(inputStream);
} catch (SAXException e) {
// expected
}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlImporterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlFileTrackImporterTest.java
similarity index 91%
rename from MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlImporterTest.java
rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlFileTrackImporterTest.java
index 3184ce575..391b44423 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlImporterTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlFileTrackImporterTest.java
@@ -30,11 +30,11 @@ import java.io.InputStream;
import org.easymock.Capture;
/**
- * Tests for {@link KmlImporter}.
+ * Tests for {@link KmlFileTrackImporter}.
*
* @author Jimmy Shih
*/
-public class KmlImporterTest extends AbstractTestImporter {
+public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static String getNameAndDescription(String name, String description) {
return "" to ensure that proper escaping is
// being done.
@@ -110,29 +111,28 @@ public abstract class TrackFormatWriterTest extends AndroidTestCase {
* Makes the right sequence of calls to the writer in order to write the fake
* track in {@link #track}.
*
- * @param writer the writer to write to
+ * @param trackWriter the track writer
* @return the written contents
*/
- protected String writeTrack(TrackFormatWriter writer) throws Exception {
+ protected String writeTrack(TrackWriter trackWriter) throws Exception {
OutputStream output = new ByteArrayOutputStream(BUFFER_SIZE);
- writer.prepare(output);
- writer.writeHeader(track);
- writer.writeBeginWaypoints();
- writer.writeWaypoint(wp1);
- writer.writeWaypoint(wp2);
- writer.writeEndWaypoints();
- writer.writeBeginTrack(track, location1);
- writer.writeOpenSegment();
- writer.writeLocation(location1);
- writer.writeLocation(location2);
- writer.writeCloseSegment();
- writer.writeOpenSegment();
- writer.writeLocation(location3);
- writer.writeLocation(location4);
- writer.writeCloseSegment();
- writer.writeEndTrack(track, location4);
- writer.writeFooter();
- writer.close();
+ trackWriter.prepare(output);
+ trackWriter.writeHeader(track);
+ trackWriter.writeBeginWaypoints();
+ trackWriter.writeWaypoint(wp1);
+ trackWriter.writeWaypoint(wp2);
+ trackWriter.writeEndWaypoints();
+ trackWriter.writeBeginTrack(track, location1);
+ trackWriter.writeOpenSegment();
+ trackWriter.writeLocation(location1);
+ trackWriter.writeLocation(location2);
+ trackWriter.writeCloseSegment();
+ trackWriter.writeOpenSegment();
+ trackWriter.writeLocation(location3);
+ trackWriter.writeLocation(location4);
+ trackWriter.writeCloseSegment();
+ trackWriter.writeEndTrack(track, location4);
+ trackWriter.writeFooter();
return output.toString();
}