locations) {
- MapsFeature mapsFeature = new MapsFeature();
- mapsFeature.generateAndroidId();
- mapsFeature.setType(MapsFeature.LINE);
- if (TextUtils.isEmpty(trackName)) {
- // Features must have a name (otherwise GData upload may fail):
- mapsFeature.setTitle("-");
- } else {
- mapsFeature.setTitle(trackName);
- }
- mapsFeature.setColor(0x80FF0000);
- for (Location loc : locations) {
- mapsFeature.addPoint(getGeoPoint(loc));
- }
- return mapsFeature;
- }
-
- /**
- * Cleans up after a series of uploads.
- * This closes the connection to Maps and resets retry counters.
- */
- public void cleanUp() {
- wrapper.cleanUp();
- }
-
- private static GeoPoint getGeoPoint(Location location) {
- return new GeoPoint((int) (location.getLatitude() * 1E6),
- (int) (location.getLongitude() * 1E6));
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/MapsGDataConverter.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/MapsGDataConverter.java
index 6004fa469..573e4bdf6 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/maps/MapsGDataConverter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/maps/MapsGDataConverter.java
@@ -52,7 +52,7 @@ class MapsGDataConverter {
MapFeatureEntry entry = new MapFeatureEntry();
entry.setEditUri(metadata.getGDataEditUri());
entry.setTitle(metadata.getTitle());
- entry.setContent(metadata.getDescription());
+ entry.setSummary(metadata.getDescription());
entry.setPrivacy(metadata.getSearchable() ? "public" : "unlisted");
entry.setAuthor("android");
entry.setEmail("nobody@google.com");
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/MapsGDataWrapper.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/MapsGDataWrapper.java
deleted file mode 100644
index 1d2631a9f..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/io/maps/MapsGDataWrapper.java
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2010 Google Inc. All Rights Reserved.
-package com.google.android.apps.mytracks.io.maps;
-
-import com.google.android.apps.mytracks.io.AuthManager;
-import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
-import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
-import com.google.android.common.gdata.AndroidXmlParserFactory;
-import com.google.wireless.gdata.client.GDataClient;
-import com.google.wireless.gdata.client.HttpException;
-import com.google.wireless.gdata.parser.xml.SimplePullParser.ParseException;
-import com.google.wireless.gdata2.ConflictDetectedException;
-import com.google.wireless.gdata2.client.AuthenticationException;
-
-import android.content.Context;
-import android.util.Log;
-
-import java.io.IOException;
-
-/**
- * MapsGDataWrapper provides a wrapper around GData operations that maintains
- * the GData client, and provides a method to run gdata queries with proper
- * error handling. After a query is run, the wrapper can be queried about the
- * error that occurred.
- */
-class MapsGDataWrapper {
- /**
- * A QueryFunction is passed in when executing a query. The query function
- * of the class is called with the GData client as a parameter. The
- * function should execute whatever operations it desires on the client
- * without concern for whether the client will throw an error.
- */
- interface QueryFunction {
- public abstract void query(MapsClient client)
- throws AuthenticationException, IOException, ParseException,
- ConflictDetectedException, Exception;
- }
-
- // The types of error that may be encountered
- /** No error occurred. */
- public static final int ERROR_NO_ERROR = 0;
- /** There was an authentication error, the auth token may be invalid. */
- public static final int ERROR_AUTH = 1;
- /** There was an internal error on the server side. */
- public static final int ERROR_INTERNAL = 2;
- /** There was an error connecting to the server. */
- public static final int ERROR_CONNECTION = 3;
- /** The item queried did not exit. */
- public static final int ERROR_NOT_FOUND = 4;
- /** There was an error parsing or serializing locally. */
- public static final int ERROR_LOCAL = 5;
- /** There was a conflict, update the entry and try again. */
- public static final int ERROR_CONFLICT = 6;
- /**
- * A query was run after cleaning up the wrapper, so the client was invalid.
- */
- public static final int ERROR_CLEANED_UP = 7;
- /** An unknown error occurred. */
- public static final int ERROR_UNKNOWN = 100;
-
- private final GDataClient androidGdataClient;
- private final AuthManager auth;
- private final MapsClient client;
-
- private String errorMessage;
- private int errorType;
- private boolean retryOnAuthFailure;
- private int retriesPending;
- private boolean cleanupCalled;
-
- public MapsGDataWrapper(Context context, AuthManager auth) {
- androidGdataClient = GDataClientFactory.getGDataClient(context);
- this.auth = auth;
- client =
- new MapsClient(androidGdataClient, new XmlMapsGDataParserFactory(
- new AndroidXmlParserFactory()));
- errorType = ERROR_NO_ERROR;
- errorMessage = null;
- retryOnAuthFailure = false;
- retriesPending = 0;
- cleanupCalled = false;
- }
-
- public boolean runQuery(final QueryFunction query) {
- if (client == null) {
- errorType = ERROR_CLEANED_UP;
- errorMessage = "GData Wrapper has already been cleaned up!";
- return false;
- }
- try {
- query.query(client);
- errorType = ERROR_NO_ERROR;
- errorMessage = null;
- return true;
- } catch (AuthenticationException e) {
- Log.e(MapsConstants.TAG, "Exception", e);
- errorType = ERROR_AUTH;
- errorMessage = e.getMessage();
- } catch (HttpException e) {
- Log.e(MapsConstants.TAG, "HttpException", e);
- errorMessage = e.getMessage();
- if (errorMessage != null && errorMessage.contains("401")) {
- errorType = ERROR_AUTH;
- } else {
- errorType = ERROR_CONNECTION;
- }
- } catch (IOException e) {
- Log.e(MapsConstants.TAG, "Exception", e);
- errorMessage = e.getMessage();
- if (errorMessage != null && errorMessage.contains("503")) {
- errorType = ERROR_INTERNAL;
- } else {
- errorType = ERROR_CONNECTION;
- }
- } catch (ParseException e) {
- Log.e(MapsConstants.TAG, "Exception", e);
- errorType = ERROR_LOCAL;
- errorMessage = e.getMessage();
- } catch (ConflictDetectedException e) {
- Log.e(MapsConstants.TAG, "Exception", e);
- errorType = ERROR_CONFLICT;
- errorMessage = e.getMessage();
- } catch (Exception e) {
- Log.e(MapsConstants.TAG, "Exception", e);
- errorType = ERROR_UNKNOWN;
- errorMessage = e.getMessage();
- e.printStackTrace();
- }
- Log.d(MapsConstants.TAG, "GData error encountered: " + errorMessage);
- if (errorType == ERROR_AUTH && auth != null) {
- AuthCallback whenFinished = null;
- if (retryOnAuthFailure) {
- retriesPending++;
- whenFinished = new AuthCallback() {
- @Override
- public void onAuthResult(boolean success) {
- retriesPending--;
- retryOnAuthFailure = false;
- runQuery(query);
- if (cleanupCalled && retriesPending == 0) {
- cleanUp();
- }
- }
- };
- }
- auth.invalidateAndRefresh(whenFinished);
- }
- return false;
- }
-
- public int getErrorType() {
- return errorType;
- }
-
- public String getErrorMessage() {
- return errorMessage;
- }
-
- // cleanUp must be called when done using this wrapper to close the client.
- // Note that the cleanup will be delayed if auth failure retries were
- // requested and there is a pending retry.
- public void cleanUp() {
- if (retriesPending == 0 && !cleanupCalled) {
- androidGdataClient.close();
- }
- cleanupCalled = true;
- }
-
- public void setRetryOnAuthFailure(boolean retry) {
- retryOnAuthFailure = retry;
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsActivity.java
new file mode 100644
index 000000000..93a1144a6
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsActivity.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2012 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.maps;
+
+import com.google.android.maps.mytracks.R;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * An activity to send a track to Google Maps.
+ *
+ * @author Jimmy Shih
+ */
+public class SendMapsActivity extends Activity {
+
+ // parameters in the input intent
+ public static final String ACCOUNT = "account";
+ public static final String TRACK_ID = "trackId";
+ public static final String MAP_ID = "mapId";
+
+ // parameters in the output intent
+ public static final String SUCCESS = "success";
+
+ private static final int PROGRESS_DIALOG = 1;
+
+ private SendMapsAsyncTask asyncTask;
+ private ProgressDialog progressDialog;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Object retained = getLastNonConfigurationInstance();
+ if (retained instanceof SendMapsAsyncTask) {
+ asyncTask = (SendMapsAsyncTask) retained;
+ asyncTask.setActivity(this);
+ } else {
+ Intent intent = getIntent();
+ Account account = intent.getParcelableExtra(ACCOUNT);
+ if (account == null) {
+ setResult(RESULT_OK, new Intent().putExtra(SUCCESS, false));
+ finish();
+ return;
+ }
+ long trackId = intent.getLongExtra(TRACK_ID, -1L);
+ if (trackId == -1L) {
+ setResult(RESULT_OK, new Intent().putExtra(SUCCESS, false));
+ finish();
+ return;
+ }
+ String mapId = intent.getStringExtra(MAP_ID);
+
+ asyncTask = new SendMapsAsyncTask(this, account, trackId, mapId);
+ asyncTask.execute();
+ }
+ }
+
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ asyncTask.setActivity(null);
+ return asyncTask;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case PROGRESS_DIALOG:
+ progressDialog = new ProgressDialog(this);
+ progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ progressDialog.setIcon(android.R.drawable.ic_dialog_info);
+ progressDialog.setTitle(
+ getString(R.string.send_google_progress_title, getString(R.string.send_google_maps)));
+ progressDialog.setMax(100);
+ progressDialog.setProgress(0);
+ progressDialog.setCancelable(true);
+ progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ asyncTask.cancel(true);
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+ return progressDialog;
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Invokes when the associated AsyncTask completes.
+ *
+ * @param success true if success
+ */
+ public void onAsyncTaskCompleted(boolean success) {
+ setResult(RESULT_OK, new Intent().putExtra(SUCCESS, success));
+ finish();
+ }
+
+ /**
+ * Shows the progress dialog.
+ */
+ public void showProgressDialog() {
+ showDialog(PROGRESS_DIALOG);
+ }
+
+ /**
+ * Sets the progress dialog value.
+ */
+ public void setProgressDialogValue(int value) {
+ if (progressDialog != null) {
+ progressDialog.setProgress(value);
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java
new file mode 100644
index 000000000..4c7e2a9ef
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright 2012 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.maps;
+
+import com.google.android.apps.mytracks.Constants;
+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 com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
+import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
+import com.google.android.apps.mytracks.stats.DoubleBuffer;
+import com.google.android.apps.mytracks.util.LocationUtils;
+import com.google.android.apps.mytracks.util.StringUtils;
+import com.google.android.apps.mytracks.util.UnitConversions;
+import com.google.android.common.gdata.AndroidXmlParserFactory;
+import com.google.android.maps.mytracks.R;
+import com.google.wireless.gdata.client.GDataClient;
+import com.google.wireless.gdata.client.HttpException;
+import com.google.wireless.gdata.parser.ParseException;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.location.Location;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * AsyncTask to send a track to Google Maps.
+ *
+ * IMPORTANT: While this code is Apache-licensed, please notice that usage of
+ * the Google Maps servers through this API is only allowed for the My Tracks
+ * application. Other applications looking to upload maps data should look into
+ * using the Google Fusion Tables API.
+ *
+ * @author Jimmy Shih
+ */
+public class SendMapsAsyncTask extends AsyncTask {
+ private static final String START_ICON_URL =
+ "http://maps.google.com/mapfiles/ms/micons/green-dot.png";
+ private static final String END_ICON_URL =
+ "http://maps.google.com/mapfiles/ms/micons/red-dot.png";
+ private static final int MAX_POINTS_PER_UPLOAD = 500;
+
+ private static final int PROGRESS_FETCH_MAP_ID = 5;
+ private static final int PROGRESS_UPLOAD_DATA_MIN = 10;
+ private static final int PROGRESS_UPLOAD_DATA_MAX = 90;
+ private static final int PROGRESS_UPLOAD_WAYPOINTS = 95;
+ private static final int PROGRESS_COMPLETE = 100;
+
+ private static final String TAG = SendMapsAsyncTask.class.getSimpleName();
+
+ private SendMapsActivity activity;
+ private final Account account;
+ private final long trackId;
+ private final String chooseMapId;
+ private final Context context;
+ private final MyTracksProviderUtils myTracksProviderUtils;
+ private final GDataClient gDataClient;
+ private final MapsClient mapsClient;
+
+ /**
+ * True if can retry sending to Google Maps.
+ */
+ private boolean canRetry;
+
+ /**
+ * True if the AsyncTask has completed.
+ */
+ private boolean completed;
+
+ /**
+ * True if the result is success.
+ */
+ private boolean success;
+
+ // The following variables are for per upload states
+ private MapsGDataConverter mapsGDataConverter;
+ private String authToken;
+ private String mapId;
+ int currentSegment;
+
+ public SendMapsAsyncTask(
+ SendMapsActivity activity, Account account, long trackId, String chooseMapId) {
+ this.activity = activity;
+ this.account = account;
+ this.trackId = trackId;
+ this.chooseMapId = chooseMapId;
+
+ context = activity.getApplicationContext();
+ myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
+ gDataClient = GDataClientFactory.getGDataClient(context);
+ mapsClient = new MapsClient(
+ gDataClient, new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
+
+ canRetry = true;
+ completed = false;
+ success = false;
+ }
+
+ /**
+ * Sets the activity associated with this AyncTask.
+ *
+ * @param activity the activity.
+ */
+ public void setActivity(SendMapsActivity activity) {
+ this.activity = activity;
+ if (completed && activity != null) {
+ activity.onAsyncTaskCompleted(success);
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ activity.showProgressDialog();
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ return doUpload();
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ if (activity != null) {
+ activity.setProgressDialogValue(values[0]);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ closeClient();
+ success = result;
+ completed = true;
+ if (success) {
+ Track track = myTracksProviderUtils.getTrack(trackId);
+ if (track != null) {
+ track.setMapId(mapId);
+ myTracksProviderUtils.updateTrack(track);
+ } else {
+ Log.d(TAG, "No track");
+ }
+ }
+ if (activity != null) {
+ activity.onAsyncTaskCompleted(success);
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ closeClient();
+ }
+
+ /**
+ * Closes the gdata client.
+ */
+ private void closeClient() {
+ if (gDataClient != null) {
+ gDataClient.close();
+ }
+ }
+
+ /**
+ * Uploads a track to Google Maps.
+ *
+ * @return true if success.
+ */
+ private boolean doUpload() {
+ // Reset the per upload states
+ mapsGDataConverter = null;
+ authToken = null;
+ mapId = null;
+ currentSegment = 1;
+
+ // Create a maps gdata converter
+ try {
+ mapsGDataConverter = new MapsGDataConverter();
+ } catch (XmlPullParserException e) {
+ Log.d(TAG, "Unable to create a maps gdata converter", e);
+ return false;
+ }
+
+ // Get auth token
+ try {
+ authToken = AccountManager.get(context).blockingGetAuthToken(
+ account, MapsConstants.SERVICE_NAME, false);
+ } catch (OperationCanceledException e) {
+ Log.d(TAG, "Unable to get auth token", e);
+ return retryUpload();
+ } catch (AuthenticatorException e) {
+ Log.d(TAG, "Unable to get auth token", e);
+ return retryUpload();
+ } catch (IOException e) {
+ Log.d(TAG, "Unable to get auth token", e);
+ return retryUpload();
+ }
+
+ // Get the track
+ Track track = myTracksProviderUtils.getTrack(trackId);
+ if (track == null) {
+ Log.d(TAG, "Track is null");
+ return false;
+ }
+
+ // Fetch the mapId, create a new map if necessary
+ publishProgress(PROGRESS_FETCH_MAP_ID);
+ if (!fetchSendMapId(track)) {
+ Log.d("TAG", "Unable to upload all track points");
+ return retryUpload();
+ }
+
+ // Upload all the track points plus the start and end markers
+ publishProgress(PROGRESS_UPLOAD_DATA_MIN);
+ if (!uploadAllTrackPoints(track)) {
+ Log.d("TAG", "Unable to upload all track points");
+ return retryUpload();
+ }
+
+ // Upload all the waypoints
+ publishProgress(PROGRESS_UPLOAD_WAYPOINTS);
+ if (!uploadWaypoints()) {
+ Log.d("TAG", "Unable to upload waypoints");
+ return false;
+ }
+
+ publishProgress(PROGRESS_COMPLETE);
+ return true;
+ }
+
+ /**
+ * Retries upload. Invalidates the authToken. If can retry, invokes
+ * {@link SendMapsAsyncTask#doUpload()}. Returns false if cannot retry.
+ */
+ private boolean retryUpload() {
+ if (isCancelled()) {
+ return false;
+ }
+
+ AccountManager.get(context).invalidateAuthToken(MapsConstants.SERVICE_NAME, authToken);
+ if (canRetry) {
+ canRetry = false;
+ return doUpload();
+ }
+ return false;
+ }
+
+ /**
+ * Fetches the {@link SendMapsAsyncTask#mapId} instance variable for
+ * sending a track to Google Maps.
+ *
+ * @param track the Track
+ * @return true if able to fetch the mapId variable.
+ */
+ private boolean fetchSendMapId(Track track) {
+ if (isCancelled()) {
+ return false;
+ }
+
+ if (chooseMapId != null) {
+ mapId = chooseMapId;
+ return true;
+ } else {
+ SharedPreferences sharedPreferences = context.getSharedPreferences(
+ Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
+ boolean mapPublic = sharedPreferences.getBoolean(
+ context.getString(R.string.default_map_public_key), true);
+ try {
+ String description = track.getCategory() + "\n" + track.getDescription() + "\n"
+ + StringUtils.getCreatedByMyTracks(context, false);
+ mapId = SendMapsUtils.createNewMap(
+ track.getName(), description, mapPublic, mapsClient, authToken);
+ } catch (ParseException e) {
+ Log.d(TAG, "Unable to create a new map", e);
+ return false;
+ } catch (HttpException e) {
+ Log.d(TAG, "Unable to create a new map", e);
+ return false;
+ } catch (IOException e) {
+ Log.d(TAG, "Unable to create a new map", e);
+ return false;
+ }
+ return mapId != null;
+ }
+ }
+
+ /**
+ * Uploads all the points in a track.
+ *
+ * @param track the track
+ * @return true if success.
+ */
+ private boolean uploadAllTrackPoints(Track track) {
+ Cursor locationsCursor = null;
+ try {
+ SharedPreferences prefs = context.getSharedPreferences(
+ Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
+ boolean metricUnits = prefs.getBoolean(context.getString(R.string.metric_units_key), true);
+
+ locationsCursor = myTracksProviderUtils.getLocationsCursor(trackId, 0, -1, false);
+ if (locationsCursor == null) {
+ Log.d(TAG, "Location cursor is null");
+ return false;
+ }
+ int locationsCount = locationsCursor.getCount();
+ List locations = new ArrayList(MAX_POINTS_PER_UPLOAD);
+ Location lastLocation = null;
+
+ // Limit the number of elevation readings to 250.
+ int elevationSamplingFrequency = Math.max(1, (int) (locationsCount / 250.0));
+ double totalDistance = 0;
+ DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
+ Vector distances = new Vector();
+ Vector elevations = new Vector();
+
+ for (int i = 0; i < locationsCount; i++) {
+ locationsCursor.moveToPosition(i);
+
+ Location location = myTracksProviderUtils.createLocation(locationsCursor);
+ locations.add(location);
+
+ int readCount = i + 1;
+
+ if (readCount == 1) {
+ // Create a start marker
+ if (!uploadMarker(context.getString(R.string.marker_label_start, track.getName()), "",
+ START_ICON_URL, location)) {
+ Log.d(TAG, "Unable to create a start marker");
+ return false;
+ }
+ }
+
+ // Add to the distances and elevations vectors
+ if (LocationUtils.isValidLocation(location)) {
+ // All points go into the smoothing buffer
+ elevationBuffer.setNext(metricUnits ? location.getAltitude()
+ : location.getAltitude() * UnitConversions.M_TO_FT);
+ if (lastLocation != null) {
+ totalDistance += (double) lastLocation.distanceTo(location);
+ }
+ if (readCount % elevationSamplingFrequency == 0) {
+ distances.add(totalDistance);
+ elevations.add(elevationBuffer.getAverage());
+ }
+ }
+
+ // Upload periodically
+ if (readCount % MAX_POINTS_PER_UPLOAD == 0) {
+ if (!prepareAndUploadPoints(track, locations, false)) {
+ Log.d(TAG, "Unable to upload points");
+ return false;
+ }
+ updateProgress(readCount, locationsCount);
+ locations.clear();
+ }
+ lastLocation = location;
+ }
+
+ // Do a final upload with the remaining locations
+ if (!prepareAndUploadPoints(track, locations, true)) {
+ Log.d(TAG, "Unable to upload points");
+ return false;
+ }
+
+ // Create an end marker
+ if (lastLocation != null) {
+ StringUtils stringUtils = new StringUtils(context);
+ track.setDescription("" + track.getDescription() + "
"
+ + stringUtils.generateTrackDescription(track, distances, elevations) + "
");
+ if (!uploadMarker(context.getString(R.string.marker_label_end, track.getName()),
+ track.getDescription(), END_ICON_URL, lastLocation)) {
+ Log.d(TAG, "Unable to create an end marker");
+ return false;
+ }
+ }
+ return true;
+ } finally {
+ if (locationsCursor != null) {
+ locationsCursor.close();
+ }
+ }
+ }
+
+ /**
+ * Prepares and uploads a list of locations from a track.
+ *
+ * @param track the track
+ * @param locations the locations from the track
+ * @param lastBatch true if it is the last batch of locations
+ * @return true if success.
+ */
+ private boolean prepareAndUploadPoints(Track track, List locations, boolean lastBatch) {
+ // Prepare locations
+ ArrayList