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