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(); }