forked from upstream-mirrors/OpenTracks
Merge heads
This commit is contained in:
@@ -248,6 +248,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity"/>
|
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity"/>
|
||||||
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.AccountChooserActivity"/>
|
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.AccountChooserActivity"/>
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.SendFusionTablesActivity"/>
|
||||||
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity"/>
|
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity"/>
|
||||||
|
|
||||||
<service android:name="com.google.android.apps.mytracks.services.TrackRecordingService"
|
<service android:name="com.google.android.apps.mytracks.services.TrackRecordingService"
|
||||||
|
|||||||
@@ -741,14 +741,18 @@ limitations under the License.
|
|||||||
A marker was inserted.
|
A marker was inserted.
|
||||||
</string>
|
</string>
|
||||||
<string name="marker_label_end"
|
<string name="marker_label_end"
|
||||||
translation_description="When inserting a marker to indicate the end of a track, the label at
|
translation_description="When inserting a marker to indicate the end of a track, the marker's
|
||||||
the end of the marker name. E.g., the marker is named 'track_name (End)'.">
|
name. E.g., 'track_name (End)'.
|
||||||
(End)
|
|
||||||
|
%1$s track name. E.g., 'track_name'.">
|
||||||
|
%1$s (End)
|
||||||
</string>
|
</string>
|
||||||
<string name="marker_label_start"
|
<string name="marker_label_start"
|
||||||
translation_description="When inserting a marker to indicate the beginning of a track, the label
|
translation_description="When inserting a marker to indicate the start of a track, the marker's
|
||||||
at the end of the marker name. E.g., the marker is named 'track_name (Start)'.">
|
name. E.g., 'track_name (Start)'.
|
||||||
(Start)
|
|
||||||
|
%1$s track name. E.g., 'track_name'.">
|
||||||
|
%1$s (Start)
|
||||||
</string>
|
</string>
|
||||||
<string name="marker_list_context_menu_title"
|
<string name="marker_list_context_menu_title"
|
||||||
translation_description="In the context menu for a marker, the title indicating that all the
|
translation_description="In the context menu for a marker, the title indicating that all the
|
||||||
@@ -1172,6 +1176,13 @@ limitations under the License.
|
|||||||
dialog indicating My Tracks is preparing the track's data for upload.">
|
dialog indicating My Tracks is preparing the track's data for upload.">
|
||||||
Preparing track...
|
Preparing track...
|
||||||
</string>
|
</string>
|
||||||
|
<string name="send_google_progress_title"
|
||||||
|
translation_description="When sending a track to a Google service, the title on the progress
|
||||||
|
dialog.
|
||||||
|
|
||||||
|
%1$s: service name. 'Google Maps', 'Google Fusion Tables', or 'Google Docs'.">
|
||||||
|
Sending to %1$s. Please wait...
|
||||||
|
</string>
|
||||||
<string name="send_google_progress_reading_track"
|
<string name="send_google_progress_reading_track"
|
||||||
translation_description="When sending a track to Google, the message on the progress dialog
|
translation_description="When sending a track to Google, the message on the progress dialog
|
||||||
indicating My Tracks is reading the track from its database.">
|
indicating My Tracks is reading the track from its database.">
|
||||||
@@ -1226,11 +1237,12 @@ limitations under the License.
|
|||||||
Send to...
|
Send to...
|
||||||
</string>
|
</string>
|
||||||
<string name="send_google_track_part_label"
|
<string name="send_google_track_part_label"
|
||||||
translation_description="When sending a track to Google, the label to name segments when
|
translation_description="When sending a track to Google, the label to name segments of a large
|
||||||
splitting a large track.
|
track. E.g., 'track_name (Part 1)'
|
||||||
|
|
||||||
%1$d: part number. E.g., '1'.">
|
%1$s: track name. E.g., 'track_name'
|
||||||
(Part %1$d)
|
%2$d: part number. E.g., '1'.">
|
||||||
|
%1$s (Part %2$d)
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- Sensor State -->
|
<!-- Sensor State -->
|
||||||
|
|||||||
@@ -44,20 +44,20 @@ public abstract class Constants {
|
|||||||
public static final int GET_MAP = 1;
|
public static final int GET_MAP = 1;
|
||||||
public static final int SHOW_TRACK = 2;
|
public static final int SHOW_TRACK = 2;
|
||||||
public static final int AUTHENTICATE_TO_MAPS = 3;
|
public static final int AUTHENTICATE_TO_MAPS = 3;
|
||||||
public static final int AUTHENTICATE_TO_FUSION_TABLES = 4;
|
public static final int AUTHENTICATE_TO_DOCLIST = 4;
|
||||||
public static final int AUTHENTICATE_TO_DOCLIST = 5;
|
public static final int AUTHENTICATE_TO_TRIX = 5;
|
||||||
public static final int AUTHENTICATE_TO_TRIX = 6;
|
public static final int SHARE_GPX_FILE = 6;
|
||||||
public static final int SHARE_GPX_FILE = 7;
|
public static final int SHARE_KML_FILE = 7;
|
||||||
public static final int SHARE_KML_FILE = 8;
|
public static final int SHARE_CSV_FILE = 8;
|
||||||
public static final int SHARE_CSV_FILE = 9;
|
public static final int SHARE_TCX_FILE = 9;
|
||||||
public static final int SHARE_TCX_FILE = 10;
|
public static final int SAVE_GPX_FILE = 10;
|
||||||
public static final int SAVE_GPX_FILE = 11;
|
public static final int SAVE_KML_FILE = 11;
|
||||||
public static final int SAVE_KML_FILE = 12;
|
public static final int SAVE_CSV_FILE = 12;
|
||||||
public static final int SAVE_CSV_FILE = 13;
|
public static final int SAVE_TCX_FILE = 13;
|
||||||
public static final int SAVE_TCX_FILE = 14;
|
public static final int SHOW_WAYPOINT = 14;
|
||||||
public static final int SHOW_WAYPOINT = 15;
|
public static final int WELCOME = 15;
|
||||||
public static final int WELCOME = 16;
|
public static final int CHOOSE_ACCOUNT = 16;
|
||||||
public static final int CHOOSE_ACCOUNT = 17;
|
public static final int SEND_FUSION_TABLES = 17;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Menu ids:
|
* Menu ids:
|
||||||
|
|||||||
@@ -1,716 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
|
||||||
import com.google.android.apps.mytracks.ProgressIndicator;
|
|
||||||
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.GDataWrapper;
|
|
||||||
import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction;
|
|
||||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
|
|
||||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
import com.google.api.client.googleapis.GoogleHeaders;
|
|
||||||
import com.google.api.client.googleapis.MethodOverride;
|
|
||||||
import com.google.api.client.http.GenericUrl;
|
|
||||||
import com.google.api.client.http.HttpRequest;
|
|
||||||
import com.google.api.client.http.HttpRequestFactory;
|
|
||||||
import com.google.api.client.http.HttpResponse;
|
|
||||||
import com.google.api.client.http.HttpResponseException;
|
|
||||||
import com.google.api.client.http.HttpTransport;
|
|
||||||
import com.google.api.client.http.InputStreamContent;
|
|
||||||
import com.google.api.client.util.Strings;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper class used to transmit tracks to Google Fusion Tables.
|
|
||||||
* A new instance should be used for each upload.
|
|
||||||
*
|
|
||||||
* @author Leif Hendrik Wilden
|
|
||||||
*/
|
|
||||||
public class SendToFusionTables implements Runnable {
|
|
||||||
|
|
||||||
private static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener invoked when sending to fusion tables completes.
|
|
||||||
*/
|
|
||||||
public interface OnSendCompletedListener {
|
|
||||||
void onSendCompleted(String tableId, boolean success);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The GData service id for Fusion Tables. */
|
|
||||||
public static final String SERVICE_ID = "fusiontables";
|
|
||||||
|
|
||||||
/** The path for viewing a map visualization of a table. */
|
|
||||||
private static final String FUSIONTABLES_MAP =
|
|
||||||
"https://www.google.com/fusiontables/embedviz?" +
|
|
||||||
"viz=MAP&q=select+col0,+col1,+col2,+col3+from+%s+&h=false&" +
|
|
||||||
"lat=%f&lng=%f&z=%d&t=1&l=col2";
|
|
||||||
|
|
||||||
/** Standard base feed url for Fusion Tables. */
|
|
||||||
private static final String FUSIONTABLES_BASE_FEED_URL =
|
|
||||||
"https://www.google.com/fusiontables/api/query";
|
|
||||||
|
|
||||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
|
||||||
|
|
||||||
private static final String GDATA_VERSION = "2";
|
|
||||||
|
|
||||||
// This class reports upload status to the user as a completion percentage
|
|
||||||
// using a progress bar. Progress is defined as follows:
|
|
||||||
//
|
|
||||||
// 0% Getting track metadata
|
|
||||||
// 5% Creating Fusion Table (GData to FT server)
|
|
||||||
// 10%-90% Uploading the track data to Fusion Tables
|
|
||||||
// 95% Uploading waypoints
|
|
||||||
// 100% Done
|
|
||||||
private static final int PROGRESS_INITIALIZATION = 0;
|
|
||||||
private static final int PROGRESS_FUSION_TABLE_CREATE = 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 final Activity context;
|
|
||||||
private final AuthManager auth;
|
|
||||||
private final long trackId;
|
|
||||||
private final ProgressIndicator progressIndicator;
|
|
||||||
private final OnSendCompletedListener onCompletion;
|
|
||||||
private final StringUtils stringUtils;
|
|
||||||
private final MyTracksProviderUtils providerUtils;
|
|
||||||
|
|
||||||
// Progress status
|
|
||||||
private int totalLocationsRead;
|
|
||||||
private int totalLocationsPrepared;
|
|
||||||
private int totalLocationsUploaded;
|
|
||||||
private int totalLocations;
|
|
||||||
private int totalSegmentsUploaded;
|
|
||||||
|
|
||||||
private HttpRequestFactory httpRequestFactory;
|
|
||||||
private String tableId;
|
|
||||||
|
|
||||||
private static String MARKER_TYPE_START = "large_green";
|
|
||||||
private static String MARKER_TYPE_END = "large_red";
|
|
||||||
private static String MARKER_TYPE_WAYPOINT = "large_yellow";
|
|
||||||
|
|
||||||
public SendToFusionTables(Activity context, AuthManager auth, long trackId,
|
|
||||||
ProgressIndicator progressIndicator, OnSendCompletedListener onCompletion) {
|
|
||||||
this.context = context;
|
|
||||||
this.auth = auth;
|
|
||||||
this.trackId = trackId;
|
|
||||||
this.progressIndicator = progressIndicator;
|
|
||||||
this.onCompletion = onCompletion;
|
|
||||||
this.stringUtils = new StringUtils(context);
|
|
||||||
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
|
||||||
|
|
||||||
HttpTransport transport = ApiFeatures.getInstance().getApiAdapter().getHttpTransport();
|
|
||||||
httpRequestFactory = transport.createRequestFactory(new MethodOverride());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Log.d(Constants.TAG, "Sending to Fusion tables: trackId = " + trackId);
|
|
||||||
doUpload();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getMapVisualizationUrl(Track track) {
|
|
||||||
if (track == null || track.getStatistics() == null || track.getTableId() == null) {
|
|
||||||
Log.w(TAG, "Unable to get track URL");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(leifhendrik): Determine correct bounding box and zoom level that will show the entire track.
|
|
||||||
TripStatistics stats = track.getStatistics();
|
|
||||||
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
|
|
||||||
double lonE6 = stats.getLeft() + (stats.getRight() - stats.getLeft()) / 2;
|
|
||||||
int z = 15;
|
|
||||||
|
|
||||||
// We explicitly format with Locale.US because we need the latitude and
|
|
||||||
// longitude to be formatted in a locale-independent manner. Specifically,
|
|
||||||
// we need the decimal separator to be a period rather than a comma.
|
|
||||||
return String.format(Locale.US, FUSIONTABLES_MAP, track.getTableId(),
|
|
||||||
latE6 / 1.E6, lonE6 / 1.E6, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUpload() {
|
|
||||||
boolean success = true;
|
|
||||||
try {
|
|
||||||
progressIndicator.setProgressValue(PROGRESS_INITIALIZATION);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
context.getString(R.string.send_google_progress_reading_track));
|
|
||||||
|
|
||||||
// Get the track meta-data
|
|
||||||
Track track = providerUtils.getTrack(trackId);
|
|
||||||
if (track == null) {
|
|
||||||
Log.w(Constants.TAG, "Cannot get track.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String originalDescription = track.getDescription();
|
|
||||||
|
|
||||||
// Create a new table:
|
|
||||||
progressIndicator.setProgressValue(PROGRESS_FUSION_TABLE_CREATE);
|
|
||||||
String creatingFormat = context.getString(R.string.send_google_progress_creating);
|
|
||||||
String serviceName = context.getString(SendType.FUSION_TABLES.getServiceName());
|
|
||||||
progressIndicator.setProgressMessage(String.format(creatingFormat, serviceName));
|
|
||||||
if (!createNewTable(track) || !makeTableUnlisted()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
progressIndicator.setProgressValue(PROGRESS_UPLOAD_DATA_MIN);
|
|
||||||
String sendingFormat = context.getString(R.string.send_google_progress_sending);
|
|
||||||
progressIndicator.setProgressMessage(String.format(sendingFormat, serviceName));
|
|
||||||
|
|
||||||
// Upload all of the segments of the track plus start/end markers
|
|
||||||
if (!uploadAllTrackPoints(track, originalDescription)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
progressIndicator.setProgressValue(PROGRESS_UPLOAD_WAYPOINTS);
|
|
||||||
|
|
||||||
// Upload all the waypoints.
|
|
||||||
if (!uploadWaypoints(track)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "SendToFusionTables: Done: " + success);
|
|
||||||
progressIndicator.setProgressValue(PROGRESS_COMPLETE);
|
|
||||||
} finally {
|
|
||||||
final boolean finalSuccess = success;
|
|
||||||
context.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (onCompletion != null) {
|
|
||||||
onCompletion.onSendCompleted(tableId, finalSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new table.
|
|
||||||
* If successful sets {@link #tableId}.
|
|
||||||
*
|
|
||||||
* @return true in case of success.
|
|
||||||
*/
|
|
||||||
private boolean createNewTable(Track track) {
|
|
||||||
Log.d(Constants.TAG, "Creating a new fusion table.");
|
|
||||||
String query = "CREATE TABLE '" + sqlEscape(track.getName()) +
|
|
||||||
"' (name:STRING,description:STRING,geometry:LOCATION,marker:STRING)";
|
|
||||||
return runUpdate(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean makeTableUnlisted() {
|
|
||||||
Log.d(Constants.TAG, "Setting visibility to unlisted.");
|
|
||||||
String query = "UPDATE TABLE " + tableId + " SET VISIBILITY = UNLISTED";
|
|
||||||
return runUpdate(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats given values SQL style. Escapes single quotes with a backslash.
|
|
||||||
*
|
|
||||||
* @param values the values to format
|
|
||||||
* @return the values formatted as: ('value1','value2',...,'value_n').
|
|
||||||
*/
|
|
||||||
private static String values(String... values) {
|
|
||||||
StringBuilder builder = new StringBuilder("(");
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
builder.append(',');
|
|
||||||
}
|
|
||||||
builder.append('\'');
|
|
||||||
builder.append(sqlEscape(values[i]));
|
|
||||||
builder.append('\'');
|
|
||||||
}
|
|
||||||
builder.append(')');
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String sqlEscape(String value) {
|
|
||||||
return value.replaceAll("'", "''");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new row representing a marker.
|
|
||||||
*
|
|
||||||
* @param name the marker name
|
|
||||||
* @param description the marker description
|
|
||||||
* @param location the marker location
|
|
||||||
* @return true in case of success.
|
|
||||||
*/
|
|
||||||
private boolean createNewPoint(String name, String description, Location location,
|
|
||||||
String marker) {
|
|
||||||
Log.d(Constants.TAG, "Creating a new row with a point.");
|
|
||||||
String query = "INSERT INTO " + tableId + " (name,description,geometry,marker) VALUES "
|
|
||||||
+ values(name, description, getKmlPoint(location), marker);
|
|
||||||
return runUpdate(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new row representing a line segment.
|
|
||||||
*
|
|
||||||
* @param track the track/segment to draw
|
|
||||||
* @return true in case of success.
|
|
||||||
*/
|
|
||||||
private boolean createNewLineString(Track track) {
|
|
||||||
Log.d(Constants.TAG, "Creating a new row with a point.");
|
|
||||||
String query = "INSERT INTO " + tableId
|
|
||||||
+ " (name,description,geometry) VALUES "
|
|
||||||
+ values(track.getName(), track.getDescription(), getKmlLineString(track));
|
|
||||||
return runUpdate(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadAllTrackPoints(final Track track, String originalDescription) {
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(
|
|
||||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
|
||||||
boolean metricUnits = true;
|
|
||||||
if (preferences != null) {
|
|
||||||
metricUnits = preferences.getBoolean(context.getString(R.string.metric_units_key), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
|
||||||
try {
|
|
||||||
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
|
||||||
Log.w(Constants.TAG, "Unable to get any points to upload");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalLocationsRead = 0;
|
|
||||||
totalLocationsPrepared = 0;
|
|
||||||
totalLocationsUploaded = 0;
|
|
||||||
totalLocations = locationsCursor.getCount();
|
|
||||||
totalSegmentsUploaded = 0;
|
|
||||||
|
|
||||||
// Limit the number of elevation readings. Ideally we would want around 250.
|
|
||||||
int elevationSamplingFrequency =
|
|
||||||
Math.max(1, (int) (totalLocations / 250.0));
|
|
||||||
Log.d(Constants.TAG,
|
|
||||||
"Using elevation sampling factor: " + elevationSamplingFrequency
|
|
||||||
+ " on " + totalLocations);
|
|
||||||
double totalDistance = 0;
|
|
||||||
|
|
||||||
Vector<Double> distances = new Vector<Double>();
|
|
||||||
Vector<Double> elevations = new Vector<Double>();
|
|
||||||
DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
|
||||||
|
|
||||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
|
||||||
Location lastLocation = null;
|
|
||||||
do {
|
|
||||||
if (totalLocationsRead % 100 == 0) {
|
|
||||||
updateTrackDataUploadProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
Location loc = providerUtils.createLocation(locationsCursor);
|
|
||||||
locations.add(loc);
|
|
||||||
|
|
||||||
if (totalLocationsRead == 0) {
|
|
||||||
// Put a marker at the first point of the first valid segment:
|
|
||||||
String name = track.getName() + " " + context.getString(R.string.marker_label_start);
|
|
||||||
createNewPoint(name, "", loc, MARKER_TYPE_START);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to the elevation profile.
|
|
||||||
if (loc != null && LocationUtils.isValidLocation(loc)) {
|
|
||||||
// All points go into the smoothing buffer...
|
|
||||||
elevationBuffer.setNext(metricUnits ? loc.getAltitude()
|
|
||||||
: loc.getAltitude() * UnitConversions.M_TO_FT);
|
|
||||||
if (lastLocation != null) {
|
|
||||||
double dist = lastLocation.distanceTo(loc);
|
|
||||||
totalDistance += dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...but only a few points are really used to keep the url short.
|
|
||||||
if (totalLocationsRead % elevationSamplingFrequency == 0) {
|
|
||||||
distances.add(totalDistance);
|
|
||||||
elevations.add(elevationBuffer.getAverage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the location was not valid, it's a segment split, so make sure the
|
|
||||||
// distance between the previous segment and the new one is not accounted
|
|
||||||
// for in the next iteration.
|
|
||||||
lastLocation = loc;
|
|
||||||
|
|
||||||
// Every now and then, upload the accumulated points
|
|
||||||
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD == MAX_POINTS_PER_UPLOAD - 1) {
|
|
||||||
if (!prepareAndUploadPoints(track, locations)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalLocationsRead++;
|
|
||||||
} while (locationsCursor.moveToNext());
|
|
||||||
|
|
||||||
// Do a final upload with what's left
|
|
||||||
if (!prepareAndUploadPoints(track, locations)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put an end marker at the last point of the last valid segment:
|
|
||||||
if (lastLocation != null) {
|
|
||||||
track.setDescription("<p>" + originalDescription + "</p><p>"
|
|
||||||
+ stringUtils.generateTrackDescription(track, distances, elevations)
|
|
||||||
+ "</p>");
|
|
||||||
String name = track.getName() + " " + context.getString(R.string.marker_label_end);
|
|
||||||
return createNewPoint(name, track.getDescription(), lastLocation, MARKER_TYPE_END);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
if (locationsCursor != null) {
|
|
||||||
locationsCursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the given location to the string in the format:
|
|
||||||
* longitude,latitude[,altitude]
|
|
||||||
*
|
|
||||||
* @param location the location to be added
|
|
||||||
* @param builder the string builder to use
|
|
||||||
*/
|
|
||||||
private void appendCoordinate(Location location, StringBuilder builder) {
|
|
||||||
builder
|
|
||||||
.append(location.getLongitude())
|
|
||||||
.append(",")
|
|
||||||
.append(location.getLatitude());
|
|
||||||
if (location.hasAltitude()) {
|
|
||||||
builder.append(",");
|
|
||||||
builder.append(location.getAltitude());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a KML Point tag for the given location.
|
|
||||||
*
|
|
||||||
* @param location The location.
|
|
||||||
* @return the kml.
|
|
||||||
*/
|
|
||||||
private String getKmlPoint(Location location) {
|
|
||||||
StringBuilder builder = new StringBuilder("<Point><coordinates>");
|
|
||||||
appendCoordinate(location, builder);
|
|
||||||
builder.append("</coordinates></Point>");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a KML LineString.
|
|
||||||
*
|
|
||||||
* @param track the track.
|
|
||||||
* @return the KML LineString.
|
|
||||||
*/
|
|
||||||
private String getKmlLineString(Track track) {
|
|
||||||
StringBuilder builder = new StringBuilder("<LineString><coordinates>");
|
|
||||||
for (Location location : track.getLocations()) {
|
|
||||||
appendCoordinate(location, builder);
|
|
||||||
builder.append(' ');
|
|
||||||
}
|
|
||||||
builder.append("</coordinates></LineString>");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
|
|
||||||
updateTrackDataUploadProgress();
|
|
||||||
|
|
||||||
int numLocations = locations.size();
|
|
||||||
if (numLocations < 2) {
|
|
||||||
Log.d(Constants.TAG, "Not preparing/uploading too few points");
|
|
||||||
totalLocationsUploaded += numLocations;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare/pre-process the points
|
|
||||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
|
||||||
|
|
||||||
// Start uploading them
|
|
||||||
for (Track splitTrack : splitTracks) {
|
|
||||||
if (totalSegmentsUploaded > 1) {
|
|
||||||
splitTrack.setName(splitTrack.getName() + " "
|
|
||||||
+ String.format(
|
|
||||||
context.getString(R.string.send_google_track_part_label), totalSegmentsUploaded));
|
|
||||||
}
|
|
||||||
totalSegmentsUploaded++;
|
|
||||||
Log.d(Constants.TAG,
|
|
||||||
"SendToFusionTables: Prepared feature for upload w/ "
|
|
||||||
+ splitTrack.getLocations().size() + " points.");
|
|
||||||
|
|
||||||
// Transmit tracks via GData feed:
|
|
||||||
// -------------------------------
|
|
||||||
Log.d(Constants.TAG,
|
|
||||||
"SendToFusionTables: Uploading to table " + tableId + " w/ auth " + auth);
|
|
||||||
if (!uploadTrackPoints(splitTrack)) {
|
|
||||||
Log.e(Constants.TAG, "Uploading failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
locations.clear();
|
|
||||||
totalLocationsUploaded += numLocations;
|
|
||||||
updateTrackDataUploadProgress();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares a buffer of locations for transmission to google fusion tables.
|
|
||||||
*
|
|
||||||
* @param track the original track with meta data
|
|
||||||
* @param locations locations on the track
|
|
||||||
* @return an array of tracks each with a sub section of the points in the
|
|
||||||
* original buffer
|
|
||||||
*/
|
|
||||||
private ArrayList<Track> prepareLocations(
|
|
||||||
Track track, Iterable<Location> locations) {
|
|
||||||
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
|
||||||
|
|
||||||
// Create segments from each full track:
|
|
||||||
Track segment = new Track();
|
|
||||||
TripStatistics segmentStats = segment.getStatistics();
|
|
||||||
TripStatistics trackStats = track.getStatistics();
|
|
||||||
segment.setId(track.getId());
|
|
||||||
segment.setName(track.getName());
|
|
||||||
segment.setDescription(/* track.getDescription() */ "");
|
|
||||||
segment.setCategory(track.getCategory());
|
|
||||||
segmentStats.setStartTime(trackStats.getStartTime());
|
|
||||||
segmentStats.setStopTime(trackStats.getStopTime());
|
|
||||||
boolean startNewTrackSegment = false;
|
|
||||||
for (Location loc : locations) {
|
|
||||||
if (totalLocationsPrepared % 100 == 0) {
|
|
||||||
updateTrackDataUploadProgress();
|
|
||||||
}
|
|
||||||
if (loc.getLatitude() > 90) {
|
|
||||||
startNewTrackSegment = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startNewTrackSegment) {
|
|
||||||
// Close up the last segment.
|
|
||||||
prepareTrackSegment(segment, splitTracks);
|
|
||||||
|
|
||||||
Log.d(Constants.TAG,
|
|
||||||
"MyTracksSendToFusionTables: Starting new track segment...");
|
|
||||||
startNewTrackSegment = false;
|
|
||||||
segment = new Track();
|
|
||||||
segment.setId(track.getId());
|
|
||||||
segment.setName(track.getName());
|
|
||||||
segment.setDescription(/* track.getDescription() */ "");
|
|
||||||
segment.setCategory(track.getCategory());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loc.getLatitude() <= 90) {
|
|
||||||
segment.addLocation(loc);
|
|
||||||
if (segmentStats.getStartTime() < 0) {
|
|
||||||
segmentStats.setStartTime(loc.getTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
totalLocationsPrepared++;
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareTrackSegment(segment, splitTracks);
|
|
||||||
|
|
||||||
return splitTracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares a track segment for sending to google fusion tables.
|
|
||||||
* The main steps are:
|
|
||||||
* - correcting end time
|
|
||||||
* - decimating locations
|
|
||||||
* - splitting into smaller tracks.
|
|
||||||
*
|
|
||||||
* The final track pieces will be put in the array list splitTracks.
|
|
||||||
*
|
|
||||||
* @param segment the original segment of the track
|
|
||||||
* @param splitTracks an array of smaller track segments
|
|
||||||
*/
|
|
||||||
private void prepareTrackSegment(
|
|
||||||
Track segment, ArrayList<Track> splitTracks) {
|
|
||||||
TripStatistics segmentStats = segment.getStatistics();
|
|
||||||
if (segmentStats.getStopTime() < 0
|
|
||||||
&& segment.getLocations().size() > 0) {
|
|
||||||
segmentStats.setStopTime(segment.getLocations().size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Decimate to 2 meter precision. Fusion tables doesn't like too many
|
|
||||||
* points:
|
|
||||||
*/
|
|
||||||
LocationUtils.decimate(segment, 2.0);
|
|
||||||
|
|
||||||
/* If the track still has > 2500 points, split it in pieces: */
|
|
||||||
final int maxPoints = 2500;
|
|
||||||
if (segment.getLocations().size() > maxPoints) {
|
|
||||||
splitTracks.addAll(LocationUtils.split(segment, maxPoints));
|
|
||||||
} else if (segment.getLocations().size() >= 2) {
|
|
||||||
splitTracks.add(segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadTrackPoints(Track splitTrack) {
|
|
||||||
int numLocations = splitTrack.getLocations().size();
|
|
||||||
if (numLocations < 2) {
|
|
||||||
// Need at least two points for a polyline:
|
|
||||||
Log.w(Constants.TAG, "Not uploading too few points");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return createNewLineString(splitTrack);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads all of the waypoints associated with this track to a table.
|
|
||||||
*
|
|
||||||
* @param track The track to upload waypoints for.
|
|
||||||
*
|
|
||||||
* @return True on success.
|
|
||||||
*/
|
|
||||||
private boolean uploadWaypoints(final Track track) {
|
|
||||||
// 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 load them into objects all at the
|
|
||||||
// same time.
|
|
||||||
boolean success = true;
|
|
||||||
Cursor c = null;
|
|
||||||
try {
|
|
||||||
c = providerUtils.getWaypointsCursor(
|
|
||||||
track.getId(), 0,
|
|
||||||
Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
|
||||||
if (c != null) {
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
// This will skip the 1st waypoint (it carries the stats for the
|
|
||||||
// last segment).
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
Waypoint wpt = providerUtils.createWaypoint(c);
|
|
||||||
Log.d(Constants.TAG, "SendToFusionTables: Creating waypoint.");
|
|
||||||
success = createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(),
|
|
||||||
MARKER_TYPE_WAYPOINT);
|
|
||||||
if (!success) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!success) {
|
|
||||||
Log.w(Constants.TAG, "SendToFusionTables: upload waypoints failed.");
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
} finally {
|
|
||||||
if (c != null) {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTrackDataUploadProgress() {
|
|
||||||
// The percent of the total that represents the completed part of this
|
|
||||||
// segment. We calculate it as an absolute percentage, and then scale it
|
|
||||||
// to fit the completion percentage range alloted to track data upload.
|
|
||||||
double totalPercentage =
|
|
||||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
|
||||||
/ (totalLocations * 3.0);
|
|
||||||
|
|
||||||
double scaledPercentage = totalPercentage
|
|
||||||
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
|
|
||||||
|
|
||||||
progressIndicator.setProgressValue((int) scaledPercentage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs an update query. Handles authentication.
|
|
||||||
*
|
|
||||||
* @param query The given SQL like query
|
|
||||||
* @return true in case of success
|
|
||||||
*/
|
|
||||||
private boolean runUpdate(final String query) {
|
|
||||||
GDataWrapper<HttpRequestFactory> wrapper = new GDataWrapper<HttpRequestFactory>();
|
|
||||||
wrapper.setAuthManager(auth);
|
|
||||||
wrapper.setRetryOnAuthFailure(true);
|
|
||||||
wrapper.setClient(httpRequestFactory);
|
|
||||||
Log.d(Constants.TAG, "GData connection prepared: " + this.auth);
|
|
||||||
wrapper.runQuery(new QueryFunction<HttpRequestFactory>() {
|
|
||||||
@Override
|
|
||||||
public void query(HttpRequestFactory factory)
|
|
||||||
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException,
|
|
||||||
GDataWrapper.AuthenticationException {
|
|
||||||
GenericUrl url = new GenericUrl(FUSIONTABLES_BASE_FEED_URL);
|
|
||||||
|
|
||||||
String sql = "sql=" + URLEncoder.encode(query, "UTF-8");
|
|
||||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
|
|
||||||
InputStreamContent isc = new InputStreamContent(null, inputStream );
|
|
||||||
|
|
||||||
HttpRequest request = factory.buildPostRequest(url, isc);
|
|
||||||
|
|
||||||
GoogleHeaders headers = new GoogleHeaders();
|
|
||||||
headers.setApplicationName("Google-MyTracks-" + SystemUtils.getMyTracksVersion(context));
|
|
||||||
headers.gdataVersion = GDATA_VERSION;
|
|
||||||
headers.setGoogleLogin(auth.getAuthToken());
|
|
||||||
headers.setContentType(CONTENT_TYPE);
|
|
||||||
request.setHeaders(headers);
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "Running update query " + url.toString() + ": " + sql);
|
|
||||||
HttpResponse response;
|
|
||||||
try {
|
|
||||||
response = request.execute();
|
|
||||||
} catch (HttpResponseException e) {
|
|
||||||
throw new GDataWrapper.HttpException(e.getResponse().getStatusCode(),
|
|
||||||
e.getResponse().getStatusMessage());
|
|
||||||
}
|
|
||||||
boolean success = response.isSuccessStatusCode();
|
|
||||||
if (success) {
|
|
||||||
byte[] result = new byte[1024];
|
|
||||||
int read = response.getContent().read(result);
|
|
||||||
String s = new String(result, 0, read, "UTF8");
|
|
||||||
String[] lines = s.split(Strings.LINE_SEPARATOR);
|
|
||||||
if (lines[0].equals("tableid")) {
|
|
||||||
tableId = lines[1];
|
|
||||||
Log.d(Constants.TAG, "tableId = " + tableId);
|
|
||||||
} else {
|
|
||||||
Log.w(Constants.TAG, "Unrecognized response: " + lines[0]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.d(Constants.TAG,
|
|
||||||
"Query failed: " + response.getStatusMessage() + " ("
|
|
||||||
+ response.getStatusCode() + ")");
|
|
||||||
throw new GDataWrapper.HttpException(
|
|
||||||
response.getStatusCode(), response.getStatusMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return wrapper.getErrorType() == GDataWrapper.ERROR_NO_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -325,9 +325,8 @@ public class SendToMaps implements Runnable {
|
|||||||
progressIndicator.setProgressMessage(String.format(sendingFormat, serviceName));
|
progressIndicator.setProgressMessage(String.format(sendingFormat, serviceName));
|
||||||
for (Track splitTrack : splitTracks) {
|
for (Track splitTrack : splitTracks) {
|
||||||
if (totalSegmentsUploaded > 1) {
|
if (totalSegmentsUploaded > 1) {
|
||||||
splitTrack.setName(splitTrack.getName() + " "
|
splitTrack.setName(context.getString(
|
||||||
+ String.format(
|
R.string.send_google_track_part_label, splitTrack.getName(), totalSegmentsUploaded));
|
||||||
context.getString(R.string.send_google_track_part_label), totalSegmentsUploaded));
|
|
||||||
}
|
}
|
||||||
totalSegmentsUploaded++;
|
totalSegmentsUploaded++;
|
||||||
Log.d(TAG,
|
Log.d(TAG,
|
||||||
|
|||||||
@@ -201,9 +201,8 @@ public class MapsFacade {
|
|||||||
} else {
|
} else {
|
||||||
iconUrl = END_ICON_URL;
|
iconUrl = END_ICON_URL;
|
||||||
}
|
}
|
||||||
String title = trackName + " "
|
String title = isStart ? context.getString(R.string.marker_label_start, trackName) :
|
||||||
+ (isStart ? context.getString(R.string.marker_label_start)
|
context.getString(R.string.marker_label_end, trackName);
|
||||||
: context.getString(R.string.marker_label_end));
|
|
||||||
String description = isStart ? "" : trackDescription;
|
String description = isStart ? "" : trackDescription;
|
||||||
return buildMapsPlacemarkFeature(title, description, iconUrl, geoPoint);
|
return buildMapsPlacemarkFeature(title, description, iconUrl, geoPoint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import com.google.android.apps.mytracks.io.AuthManager;
|
|||||||
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
|
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
|
||||||
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||||
import com.google.android.apps.mytracks.io.SendToDocs;
|
import com.google.android.apps.mytracks.io.SendToDocs;
|
||||||
import com.google.android.apps.mytracks.io.SendToFusionTables;
|
|
||||||
import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
|
|
||||||
import com.google.android.apps.mytracks.io.SendToMaps;
|
import com.google.android.apps.mytracks.io.SendToMaps;
|
||||||
import com.google.android.apps.mytracks.io.maps.MapsConstants;
|
import com.google.android.apps.mytracks.io.maps.MapsConstants;
|
||||||
import com.google.android.apps.mytracks.io.maps.MapsFacade;
|
import com.google.android.apps.mytracks.io.maps.MapsFacade;
|
||||||
@@ -78,7 +76,6 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
SEND_TO_MAPS,
|
SEND_TO_MAPS,
|
||||||
SEND_TO_MAPS_DONE,
|
SEND_TO_MAPS_DONE,
|
||||||
AUTHENTICATE_FUSION_TABLES,
|
AUTHENTICATE_FUSION_TABLES,
|
||||||
SEND_TO_FUSION_TABLES,
|
|
||||||
SEND_TO_FUSION_TABLES_DONE,
|
SEND_TO_FUSION_TABLES_DONE,
|
||||||
AUTHENTICATE_DOCS,
|
AUTHENTICATE_DOCS,
|
||||||
AUTHENTICATE_TRIX,
|
AUTHENTICATE_TRIX,
|
||||||
@@ -243,8 +240,6 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
return onSendToGoogleMapsDone();
|
return onSendToGoogleMapsDone();
|
||||||
case AUTHENTICATE_FUSION_TABLES:
|
case AUTHENTICATE_FUSION_TABLES:
|
||||||
return authenticateToFusionTables();
|
return authenticateToFusionTables();
|
||||||
case SEND_TO_FUSION_TABLES:
|
|
||||||
return sendToFusionTables();
|
|
||||||
case SEND_TO_FUSION_TABLES_DONE:
|
case SEND_TO_FUSION_TABLES_DONE:
|
||||||
return onSendToFusionTablesDone();
|
return onSendToFusionTablesDone();
|
||||||
case AUTHENTICATE_DOCS:
|
case AUTHENTICATE_DOCS:
|
||||||
@@ -277,8 +272,6 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
* SendToGoogleDialog.
|
* SendToGoogleDialog.
|
||||||
*/
|
*/
|
||||||
private SendState startSend() {
|
private SendState startSend() {
|
||||||
showDialog(PROGRESS_DIALOG);
|
|
||||||
|
|
||||||
if (sendToMaps) {
|
if (sendToMaps) {
|
||||||
return SendState.AUTHENTICATE_MAPS;
|
return SendState.AUTHENTICATE_MAPS;
|
||||||
} else if (sendToFusionTables) {
|
} else if (sendToFusionTables) {
|
||||||
@@ -305,6 +298,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
|
|
||||||
private SendState authenticateToGoogleMaps() {
|
private SendState authenticateToGoogleMaps() {
|
||||||
Log.d(TAG, "SendActivity.authenticateToGoogleMaps");
|
Log.d(TAG, "SendActivity.authenticateToGoogleMaps");
|
||||||
|
showDialog(PROGRESS_DIALOG);
|
||||||
progressDialog.setProgress(0);
|
progressDialog.setProgress(0);
|
||||||
progressDialog.setMessage(getAuthenticatingProgressMessage(SendType.MAPS));
|
progressDialog.setMessage(getAuthenticatingProgressMessage(SendType.MAPS));
|
||||||
authenticate(Constants.AUTHENTICATE_TO_MAPS, MapsConstants.SERVICE_NAME);
|
authenticate(Constants.AUTHENTICATE_TO_MAPS, MapsConstants.SERVICE_NAME);
|
||||||
@@ -379,48 +373,11 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SendState authenticateToFusionTables() {
|
private SendState authenticateToFusionTables() {
|
||||||
progressDialog.setProgress(0);
|
|
||||||
progressDialog.setMessage(getAuthenticatingProgressMessage(SendType.FUSION_TABLES));
|
|
||||||
authenticate(Constants.AUTHENTICATE_TO_FUSION_TABLES, SendToFusionTables.SERVICE_ID);
|
|
||||||
// AUTHENTICATE_TO_FUSION_TABLES callback calls sendToFusionTables
|
|
||||||
return SendState.NOT_READY;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SendState sendToFusionTables() {
|
|
||||||
tracker.trackPageView("/send/fusion_tables");
|
tracker.trackPageView("/send/fusion_tables");
|
||||||
|
Intent intent = new Intent(this, SendFusionTablesActivity.class)
|
||||||
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
|
.putExtra(SendFusionTablesActivity.ACCOUNT, account)
|
||||||
@Override
|
.putExtra(SendFusionTablesActivity.TRACK_ID, sendTrackId);
|
||||||
public void onSendCompleted(String tableId, boolean success) {
|
startActivityForResult(intent, Constants.SEND_FUSION_TABLES);
|
||||||
sendToFusionTablesSuccess = success;
|
|
||||||
if (sendToFusionTablesSuccess) {
|
|
||||||
sendToFusionTablesTableId = tableId;
|
|
||||||
// Update the table id for this track:
|
|
||||||
try {
|
|
||||||
Track track = providerUtils.getTrack(sendTrackId);
|
|
||||||
if (track != null) {
|
|
||||||
track.setTableId(tableId);
|
|
||||||
providerUtils.updateTrack(track);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Updating table id failed.");
|
|
||||||
}
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
// If that fails whatever reasons we'll just log an error, but
|
|
||||||
// continue.
|
|
||||||
Log.w(TAG, "Updating table id failed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executeStateMachine(SendState.SEND_TO_FUSION_TABLES_DONE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
final SendToFusionTables sender = new SendToFusionTables(
|
|
||||||
this, getAuthManager(SendToFusionTables.SERVICE_ID), sendTrackId,
|
|
||||||
this /* progressIndicator */, onCompletion);
|
|
||||||
|
|
||||||
new Thread(sender, "SendToFusionTables").start();
|
|
||||||
|
|
||||||
return SendState.NOT_READY;
|
return SendState.NOT_READY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,6 +390,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SendState authenticateToGoogleDocs() {
|
private SendState authenticateToGoogleDocs() {
|
||||||
|
showDialog(PROGRESS_DIALOG);
|
||||||
setProgressValue(0);
|
setProgressValue(0);
|
||||||
setProgressMessage(getAuthenticatingProgressMessage(SendType.DOCS));
|
setProgressMessage(getAuthenticatingProgressMessage(SendType.DOCS));
|
||||||
authenticate(Constants.AUTHENTICATE_TO_DOCLIST, SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
|
authenticate(Constants.AUTHENTICATE_TO_DOCLIST, SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
|
||||||
@@ -483,7 +441,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
? MapsFacade.buildMapUrl(sendToMapsMapId)
|
? MapsFacade.buildMapUrl(sendToMapsMapId)
|
||||||
: null;
|
: null;
|
||||||
String fusionTablesUrl = sendToFusionTables && sendToFusionTablesSuccess
|
String fusionTablesUrl = sendToFusionTables && sendToFusionTablesSuccess
|
||||||
? getFusionTablesUrl(sendTrackId)
|
? SendFusionTablesUtils.getMapUrl(providerUtils.getTrack(sendTrackId))
|
||||||
: null;
|
: null;
|
||||||
Intent intent = new Intent(this, UploadResultActivity.class)
|
Intent intent = new Intent(this, UploadResultActivity.class)
|
||||||
.putExtra(UploadResultActivity.HAS_MAPS_RESULT, sendToMaps)
|
.putExtra(UploadResultActivity.HAS_MAPS_RESULT, sendToMaps)
|
||||||
@@ -504,12 +462,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
return SendState.DONE;
|
return SendState.DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getFusionTablesUrl(long trackId) {
|
/**
|
||||||
Track track = providerUtils.getTrack(trackId);
|
|
||||||
return SendToFusionTables.getMapVisualizationUrl(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the authentication manager which obtains an authentication
|
* Initializes the authentication manager which obtains an authentication
|
||||||
* token, prompting the user for a login and password if needed.
|
* token, prompting the user for a login and password if needed.
|
||||||
*/
|
*/
|
||||||
@@ -560,6 +513,17 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
nextState = SendState.START;
|
nextState = SendState.START;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Constants.SEND_FUSION_TABLES: {
|
||||||
|
if (resultCode == RESULT_CANCELED) {
|
||||||
|
nextState = SendState.FINISH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sendToFusionTablesSuccess = results.getBooleanExtra(
|
||||||
|
SendFusionTablesActivity.SUCCESS, false);
|
||||||
|
sendToFusionTablesTableId = results.getStringExtra(SendFusionTablesActivity.TABLE_ID);
|
||||||
|
nextState = SendState.SEND_TO_FUSION_TABLES_DONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
Log.e(TAG, "Unrequested result: " + requestCode);
|
Log.e(TAG, "Unrequested result: " + requestCode);
|
||||||
return;
|
return;
|
||||||
@@ -578,10 +542,6 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
// Authenticated with Google Maps
|
// Authenticated with Google Maps
|
||||||
nextState = SendState.PICK_MAP;
|
nextState = SendState.PICK_MAP;
|
||||||
break;
|
break;
|
||||||
case Constants.AUTHENTICATE_TO_FUSION_TABLES:
|
|
||||||
// Authenticated with Google Fusion Tables
|
|
||||||
nextState = SendState.SEND_TO_FUSION_TABLES;
|
|
||||||
break;
|
|
||||||
case Constants.AUTHENTICATE_TO_DOCLIST:
|
case Constants.AUTHENTICATE_TO_DOCLIST:
|
||||||
// Authenticated with Google Docs
|
// Authenticated with Google Docs
|
||||||
nextState = SendState.AUTHENTICATE_TRIX;
|
nextState = SendState.AUTHENTICATE_TRIX;
|
||||||
@@ -612,6 +572,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
}
|
}
|
||||||
return authManager;
|
return authManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets status information for sending to Maps/Fusion Tables/Docs.
|
* Resets status information for sending to Maps/Fusion Tables/Docs.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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.sendtogoogle;
|
||||||
|
|
||||||
|
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 Fusion Tables.
|
||||||
|
* <p>
|
||||||
|
* The activity gets recreated when the screen rotates. To support the activity
|
||||||
|
* displaying a progress dialog, we do the following:
|
||||||
|
* <ul>
|
||||||
|
* <li>use one instance of an AyncTask to send the track</li>
|
||||||
|
* <li>save that instance as the last non configuration instance of the activity
|
||||||
|
* </li>
|
||||||
|
* <li>when a new activity is created, pass the activity to the AsyncTask so
|
||||||
|
* that the AsyncTask can update the progress dialog of the activity</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author jshih@google.com (Jimmy Shih)
|
||||||
|
*/
|
||||||
|
public class SendFusionTablesActivity extends Activity {
|
||||||
|
|
||||||
|
// parameters in the input intent
|
||||||
|
public static final String ACCOUNT = "account";
|
||||||
|
public static final String TRACK_ID = "trackId";
|
||||||
|
|
||||||
|
// parameters in the output intent
|
||||||
|
public static final String SUCCESS = "success";
|
||||||
|
public static final String TABLE_ID = "tableId";
|
||||||
|
|
||||||
|
private static final int PROGRESS_DIALOG = 1;
|
||||||
|
|
||||||
|
private SendFusionTablesAsyncTask asyncTask;
|
||||||
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
Account account = intent.getParcelableExtra(ACCOUNT);
|
||||||
|
long trackId = intent.getLongExtra(TRACK_ID, -1L);
|
||||||
|
|
||||||
|
Object retained = getLastNonConfigurationInstance();
|
||||||
|
if (retained instanceof SendFusionTablesAsyncTask) {
|
||||||
|
asyncTask = (SendFusionTablesAsyncTask) retained;
|
||||||
|
asyncTask.setActivity(this);
|
||||||
|
} else {
|
||||||
|
asyncTask = new SendFusionTablesAsyncTask(this, account, trackId);
|
||||||
|
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_fusion_tables)));
|
||||||
|
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
|
||||||
|
* @param tableId tableId if available
|
||||||
|
*/
|
||||||
|
public void onAsyncTaskCompleted(boolean success, String tableId) {
|
||||||
|
Intent intent = new Intent().putExtra(SUCCESS, success).putExtra(TABLE_ID, tableId);
|
||||||
|
setResult(RESULT_OK, intent);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+617
@@ -0,0 +1,617 @@
|
|||||||
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||||
|
|
||||||
|
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.stats.DoubleBuffer;
|
||||||
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
|
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
import com.google.api.client.googleapis.GoogleHeaders;
|
||||||
|
import com.google.api.client.googleapis.MethodOverride;
|
||||||
|
import com.google.api.client.http.GenericUrl;
|
||||||
|
import com.google.api.client.http.HttpRequest;
|
||||||
|
import com.google.api.client.http.HttpRequestFactory;
|
||||||
|
import com.google.api.client.http.HttpResponse;
|
||||||
|
import com.google.api.client.http.HttpTransport;
|
||||||
|
import com.google.api.client.http.InputStreamContent;
|
||||||
|
import com.google.api.client.util.Strings;
|
||||||
|
|
||||||
|
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.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AsyncTask to send a track to Google Fusion Tables.
|
||||||
|
*
|
||||||
|
* @author jshih@google.com (Jimmy Shih)
|
||||||
|
*/
|
||||||
|
public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||||
|
|
||||||
|
private static final String APP_NAME_PREFIX = "Google-My-Tracks-";
|
||||||
|
private static final String SQL_KEY = "sql=";
|
||||||
|
private static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
|
||||||
|
private static final String SERVICE_ID = "fusiontables";
|
||||||
|
private static final String FUSION_TABLES_BASE_URL =
|
||||||
|
"https://www.google.com/fusiontables/api/query";
|
||||||
|
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
||||||
|
private static final String GDATA_VERSION = "2";
|
||||||
|
|
||||||
|
private static final int PROGRESS_CREATE_TABLE = 0;
|
||||||
|
private static final int PROGRESS_UNLIST_TABLE = 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;
|
||||||
|
|
||||||
|
// See http://support.google.com/fusiontables/bin/answer.py?hl=en&answer=185991
|
||||||
|
private static final String MARKER_TYPE_START = "large_green";
|
||||||
|
private static final String MARKER_TYPE_END = "large_red";
|
||||||
|
private static final String MARKER_TYPE_WAYPOINT = "large_yellow";
|
||||||
|
|
||||||
|
private static final String TAG = SendFusionTablesAsyncTask.class.getSimpleName();
|
||||||
|
|
||||||
|
private SendFusionTablesActivity activity;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Account account;
|
||||||
|
private final long trackId;
|
||||||
|
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||||
|
private final HttpRequestFactory httpRequestFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if can retry sending to Google Fusion Tables.
|
||||||
|
*/
|
||||||
|
private boolean canRetry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the AsyncTask has completed.
|
||||||
|
*/
|
||||||
|
private boolean completed;
|
||||||
|
|
||||||
|
// The following variables are for per upload states
|
||||||
|
private String authToken;
|
||||||
|
private boolean success;
|
||||||
|
private String tableId;
|
||||||
|
int currentSegment;
|
||||||
|
|
||||||
|
public SendFusionTablesAsyncTask(
|
||||||
|
SendFusionTablesActivity activity, Account account, long trackId) {
|
||||||
|
this.activity = activity;
|
||||||
|
this.account = account;
|
||||||
|
this.trackId = trackId;
|
||||||
|
|
||||||
|
context = activity.getApplicationContext();
|
||||||
|
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
|
||||||
|
HttpTransport transport = ApiFeatures.getInstance().getApiAdapter().getHttpTransport();
|
||||||
|
httpRequestFactory = transport.createRequestFactory(new MethodOverride());
|
||||||
|
|
||||||
|
canRetry = true;
|
||||||
|
completed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the activity associated with this AyncTask.
|
||||||
|
*
|
||||||
|
* @param activity the activity.
|
||||||
|
*/
|
||||||
|
public void setActivity(SendFusionTablesActivity activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
if (completed) {
|
||||||
|
activity.onAsyncTaskCompleted(success, tableId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
completed = true;
|
||||||
|
if (success) {
|
||||||
|
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||||
|
if (track != null) {
|
||||||
|
track.setTableId(tableId);
|
||||||
|
myTracksProviderUtils.updateTrack(track);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "No track");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activity != null) {
|
||||||
|
activity.onAsyncTaskCompleted(success, tableId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a track to Google Fusion Tables.
|
||||||
|
*
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean doUpload() {
|
||||||
|
// Reset the per upload states
|
||||||
|
authToken = null;
|
||||||
|
success = false;
|
||||||
|
tableId = null;
|
||||||
|
currentSegment = 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
authToken = AccountManager.get(context).blockingGetAuthToken(account, SERVICE_ID, false);
|
||||||
|
} catch (OperationCanceledException e) {
|
||||||
|
return retryUpload();
|
||||||
|
} catch (AuthenticatorException e) {
|
||||||
|
return retryUpload();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return retryUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||||
|
if (track == null) {
|
||||||
|
Log.d(TAG, "Track is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new table
|
||||||
|
publishProgress(PROGRESS_CREATE_TABLE);
|
||||||
|
if (!createNewTable(track)) {
|
||||||
|
// Retry upload in case the auth token is invalid
|
||||||
|
return retryUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlist table
|
||||||
|
publishProgress(PROGRESS_UNLIST_TABLE);
|
||||||
|
if (!unlistTable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload all the track points plus the start and end markers
|
||||||
|
publishProgress(PROGRESS_UPLOAD_DATA_MIN);
|
||||||
|
if (!uploadAllTrackPoints(track)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload all the waypoints
|
||||||
|
publishProgress(PROGRESS_UPLOAD_WAYPOINTS);
|
||||||
|
if (!uploadWaypoints()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
publishProgress(PROGRESS_COMPLETE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries upload. Invalidates the authToken. If can retry, invokes
|
||||||
|
* {@link SendFusionTablesAsyncTask#doUpload()}. Returns false if cannot
|
||||||
|
* retry.
|
||||||
|
*/
|
||||||
|
private boolean retryUpload() {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountManager.get(context).invalidateAuthToken(SERVICE_ID, authToken);
|
||||||
|
if (canRetry) {
|
||||||
|
canRetry = false;
|
||||||
|
return doUpload();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new table.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean createNewTable(Track track) {
|
||||||
|
String query = "CREATE TABLE '" + SendFusionTablesUtils.escapeSqlString(track.getName())
|
||||||
|
+ "' (name:STRING,description:STRING,geometry:LOCATION,marker:STRING)";
|
||||||
|
return sendQuery(query, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlists a table.
|
||||||
|
*
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean unlistTable() {
|
||||||
|
String query = "UPDATE TABLE " + tableId + " SET VISIBILITY = UNLISTED";
|
||||||
|
return sendQuery(query, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
||||||
|
Location lastLocation = null;
|
||||||
|
|
||||||
|
// Limit the number of elevation readings. Ideally we would want around
|
||||||
|
// 250.
|
||||||
|
int elevationSamplingFrequency = Math.max(1, (int) (locationsCount / 250.0));
|
||||||
|
double totalDistance = 0;
|
||||||
|
DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||||
|
Vector<Double> distances = new Vector<Double>();
|
||||||
|
Vector<Double> elevations = new Vector<Double>();
|
||||||
|
|
||||||
|
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
|
||||||
|
String name = context.getString(R.string.marker_label_start, track.getName());
|
||||||
|
if (!createNewPoint(name, "", location, MARKER_TYPE_START)) {
|
||||||
|
Log.d(TAG, "Unable to create the 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 == MAX_POINTS_PER_UPLOAD) {
|
||||||
|
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("<p>" + track.getDescription() + "</p><p>"
|
||||||
|
+ stringUtils.generateTrackDescription(track, distances, elevations) + "</p>");
|
||||||
|
String name = context.getString(R.string.marker_label_end, track.getName());
|
||||||
|
if (!createNewPoint(name, track.getDescription(), lastLocation, MARKER_TYPE_END)) {
|
||||||
|
Log.d(TAG, "Unable to create the 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
|
||||||
|
*/
|
||||||
|
private boolean prepareAndUploadPoints(Track track, List<Location> locations, boolean lastBatch) {
|
||||||
|
int numLocations = locations.size();
|
||||||
|
if (numLocations < 2) {
|
||||||
|
Log.d(TAG, "Not preparing/uploading, too few points");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare locations
|
||||||
|
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
||||||
|
|
||||||
|
boolean onlyOneSegment = lastBatch && currentSegment == 1 && splitTracks.size() == 1;
|
||||||
|
|
||||||
|
// Upload segments
|
||||||
|
for (Track splitTrack : splitTracks) {
|
||||||
|
if (!onlyOneSegment) {
|
||||||
|
splitTrack.setName(context.getString(
|
||||||
|
R.string.send_google_track_part_label, splitTrack.getName(), currentSegment));
|
||||||
|
}
|
||||||
|
if (!createNewLineString(splitTrack)) {
|
||||||
|
Log.d(TAG, "Upload points failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentSegment++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares a list of locations to send to Google Fusion Tables.
|
||||||
|
* Splits the locations into segments if necessary.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @param locations the list of locations
|
||||||
|
* @return an array of split segments.
|
||||||
|
*/
|
||||||
|
private ArrayList<Track> prepareLocations(Track track, List<Location> locations) {
|
||||||
|
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||||
|
|
||||||
|
// Create a new segment
|
||||||
|
Track segment = new Track();
|
||||||
|
segment.setId(track.getId());
|
||||||
|
segment.setName(track.getName());
|
||||||
|
segment.setDescription("");
|
||||||
|
segment.setCategory(track.getCategory());
|
||||||
|
|
||||||
|
TripStatistics segmentStats = segment.getStatistics();
|
||||||
|
TripStatistics trackStats = track.getStatistics();
|
||||||
|
segmentStats.setStartTime(trackStats.getStartTime());
|
||||||
|
segmentStats.setStopTime(trackStats.getStopTime());
|
||||||
|
boolean startNewTrackSegment = false;
|
||||||
|
for (Location loc : locations) {
|
||||||
|
// Latitude is greater than 90 if the location is invalid. Do not add to the
|
||||||
|
// segment.
|
||||||
|
if (loc.getLatitude() > 90) {
|
||||||
|
startNewTrackSegment = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startNewTrackSegment) {
|
||||||
|
// Close the last segment
|
||||||
|
prepareTrackSegment(segment, splitTracks);
|
||||||
|
|
||||||
|
startNewTrackSegment = false;
|
||||||
|
segment = new Track();
|
||||||
|
segment.setId(track.getId());
|
||||||
|
segment.setName(track.getName());
|
||||||
|
segment.setDescription("");
|
||||||
|
segment.setCategory(track.getCategory());
|
||||||
|
segmentStats = segment.getStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loc.getLatitude() <= 90) {
|
||||||
|
segment.addLocation(loc);
|
||||||
|
|
||||||
|
// For a new segment, sets its start time using the first available
|
||||||
|
// location time.
|
||||||
|
if (segmentStats.getStartTime() < 0) {
|
||||||
|
segmentStats.setStartTime(loc.getTime());
|
||||||
|
}
|
||||||
|
segmentStats.setStopTime(loc.getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareTrackSegment(segment, splitTracks);
|
||||||
|
|
||||||
|
return splitTracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares a track segment for sending to Google Fusion Tables. The main
|
||||||
|
* steps are:
|
||||||
|
* <ul>
|
||||||
|
* <li>set the stop time</li>
|
||||||
|
* <li>decimate locations precision</li>
|
||||||
|
* </ul>
|
||||||
|
* The prepared track will be added to the splitTracks.
|
||||||
|
*
|
||||||
|
* @param segment the track segment
|
||||||
|
* @param splitTracks an array of track segments
|
||||||
|
*/
|
||||||
|
private void prepareTrackSegment(Track segment, ArrayList<Track> splitTracks) {
|
||||||
|
// For a new segment, sets it stop time
|
||||||
|
TripStatistics segmentStats = segment.getStatistics();
|
||||||
|
ArrayList<Location> locations = segment.getLocations();
|
||||||
|
if (segmentStats.getStopTime() < 0 && locations.size() > 0) {
|
||||||
|
Location lastLocation = locations.get(locations.size() - 1);
|
||||||
|
segmentStats.setStopTime(lastLocation.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimate to 2 meter precision. Fusion Tables doesn't like too many
|
||||||
|
// points.
|
||||||
|
LocationUtils.decimate(segment, 2.0);
|
||||||
|
splitTracks.add(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads all the waypoints.
|
||||||
|
*
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean uploadWaypoints() {
|
||||||
|
Cursor cursor = null;
|
||||||
|
try {
|
||||||
|
cursor = myTracksProviderUtils.getWaypointsCursor(
|
||||||
|
trackId, 0, Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
// This will skip the first waypoint (it carries the stats for the
|
||||||
|
// track).
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
Waypoint wpt = myTracksProviderUtils.createWaypoint(cursor);
|
||||||
|
if (!createNewPoint(
|
||||||
|
wpt.getName(), wpt.getDescription(), wpt.getLocation(), MARKER_TYPE_WAYPOINT)) {
|
||||||
|
Log.d(TAG, "Upload waypoints failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new row in Google Fusion Tables representing a marker as a
|
||||||
|
* point.
|
||||||
|
*
|
||||||
|
* @param name the marker name
|
||||||
|
* @param description the marker description
|
||||||
|
* @param location the marker location
|
||||||
|
* @param type the marker type
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean createNewPoint(
|
||||||
|
String name, String description, Location location, String type) {
|
||||||
|
String query = "INSERT INTO " + tableId + " (name,description,geometry,marker) VALUES "
|
||||||
|
+ SendFusionTablesUtils.formatSqlValues(
|
||||||
|
name, description, SendFusionTablesUtils.getKmlPoint(location), type);
|
||||||
|
return sendQuery(query, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new row in Google Fusion Tables representing the track as a
|
||||||
|
* line segment.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean createNewLineString(Track track) {
|
||||||
|
String query = "INSERT INTO " + tableId + " (name,description,geometry) VALUES "
|
||||||
|
+ SendFusionTablesUtils.formatSqlValues(track.getName(), track.getDescription(),
|
||||||
|
SendFusionTablesUtils.getKmlLineString(track.getLocations()));
|
||||||
|
return sendQuery(query, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a query to Google Fusion Tables.
|
||||||
|
*
|
||||||
|
* @param query the Fusion Tables SQL query
|
||||||
|
* @param setTableId true to set the table id
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean sendQuery(String query, boolean setTableId) {
|
||||||
|
Log.d(TAG, "SendQuery: " + query);
|
||||||
|
|
||||||
|
if (isCancelled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GenericUrl url = new GenericUrl(FUSION_TABLES_BASE_URL);
|
||||||
|
String sql = SQL_KEY + URLEncoder.encode(query);
|
||||||
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
|
||||||
|
InputStreamContent inputStreamContent = new InputStreamContent(null, inputStream);
|
||||||
|
HttpRequest request;
|
||||||
|
try {
|
||||||
|
request = httpRequestFactory.buildPostRequest(url, inputStreamContent);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GoogleHeaders headers = new GoogleHeaders();
|
||||||
|
headers.setApplicationName(APP_NAME_PREFIX + SystemUtils.getMyTracksVersion(context));
|
||||||
|
headers.gdataVersion = GDATA_VERSION;
|
||||||
|
headers.setGoogleLogin(authToken);
|
||||||
|
headers.setContentType(CONTENT_TYPE);
|
||||||
|
request.setHeaders(headers);
|
||||||
|
|
||||||
|
HttpResponse response;
|
||||||
|
try {
|
||||||
|
response = request.execute();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
success = response.isSuccessStatusCode();
|
||||||
|
if (success) {
|
||||||
|
InputStream content;
|
||||||
|
try {
|
||||||
|
content = response.getContent();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (setTableId) {
|
||||||
|
tableId = SendFusionTablesUtils.getTableId(content);
|
||||||
|
if (tableId == null) {
|
||||||
|
Log.d(TAG, "tableId is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG,
|
||||||
|
"sendQuery failed: " + response.getStatusMessage() + ": " + response.getStatusCode());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the progress based on the number of locations uploaded.
|
||||||
|
*
|
||||||
|
* @param uploaded the number of uploaded locations
|
||||||
|
* @param total the number of total locations
|
||||||
|
*/
|
||||||
|
private void updateProgress(int uploaded, int total) {
|
||||||
|
double totalPercentage = uploaded / total;
|
||||||
|
double scaledPercentage = totalPercentage
|
||||||
|
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
|
||||||
|
publishProgress((int) scaledPercentage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+195
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* 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.sendtogoogle;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
import com.google.api.client.util.Strings;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for sending a track to Google Fusion Tables.
|
||||||
|
*
|
||||||
|
* @author jshih@google.com (Jimmy Shih)
|
||||||
|
*/
|
||||||
|
public class SendFusionTablesUtils {
|
||||||
|
|
||||||
|
private static final String UTF8 = "UTF8";
|
||||||
|
private static final String TABLE_ID = "tableid";
|
||||||
|
private static final String MAP_URL = "https://www.google.com/fusiontables/embedviz?"
|
||||||
|
+ "viz=MAP&q=select+col0,+col1,+col2,+col3+from+%s+&h=false&lat=%f&lng=%f&z=%d&t=1&l=col2";
|
||||||
|
private static final String TAG = SendFusionTablesUtils.class.getSimpleName();
|
||||||
|
|
||||||
|
private SendFusionTablesUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the url to visualize a fusion table on a map.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @return the url.
|
||||||
|
*/
|
||||||
|
public static String getMapUrl(Track track) {
|
||||||
|
if (track == null || track.getStatistics() == null || track.getTableId() == null) {
|
||||||
|
Log.e(TAG, "Invalid track.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jshih): Determine the correct bounding box and zoom level that
|
||||||
|
// will show the entire track.
|
||||||
|
TripStatistics stats = track.getStatistics();
|
||||||
|
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
|
||||||
|
double lonE6 = stats.getLeft() + (stats.getRight() - stats.getLeft()) / 2;
|
||||||
|
int z = 15;
|
||||||
|
|
||||||
|
// We explicitly format with Locale.US because we need the latitude and
|
||||||
|
// longitude to be formatted in a locale-independent manner. Specifically,
|
||||||
|
// we need the decimal separator to be a period rather than a comma.
|
||||||
|
return String.format(
|
||||||
|
Locale.US, MAP_URL, track.getTableId(), latE6 / 1.E6, lonE6 / 1.E6, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an array of values as a SQL VALUES like
|
||||||
|
* ('value1','value2',...,'value_n'). Escapes single quotes with two single
|
||||||
|
* quotes.
|
||||||
|
*
|
||||||
|
* @param values an array of values to format
|
||||||
|
* @return the formated SQL VALUES.
|
||||||
|
*/
|
||||||
|
public static String formatSqlValues(String... values) {
|
||||||
|
StringBuilder builder = new StringBuilder("(");
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
builder.append(',');
|
||||||
|
}
|
||||||
|
builder.append('\'');
|
||||||
|
builder.append(escapeSqlString(values[i]));
|
||||||
|
builder.append('\'');
|
||||||
|
}
|
||||||
|
builder.append(")");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a SQL string. Escapes single quotes with two single quotes.
|
||||||
|
*
|
||||||
|
* @param string the string
|
||||||
|
* @return the escaped string.
|
||||||
|
*/
|
||||||
|
public static String escapeSqlString(String string) {
|
||||||
|
return string.replaceAll("'", "''");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a KML Point value representing a location.
|
||||||
|
*
|
||||||
|
* @param location the location
|
||||||
|
* @return the KML Point value.
|
||||||
|
*/
|
||||||
|
public static String getKmlPoint(Location location) {
|
||||||
|
StringBuilder builder = new StringBuilder("<Point><coordinates>");
|
||||||
|
if (location != null) {
|
||||||
|
appendLocation(location, builder);
|
||||||
|
}
|
||||||
|
builder.append("</coordinates></Point>");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a KML LineString value representing an array of locations.
|
||||||
|
*
|
||||||
|
* @param locations the locations.
|
||||||
|
* @return the KML LineString value.
|
||||||
|
*/
|
||||||
|
public static String getKmlLineString(ArrayList<Location> locations) {
|
||||||
|
StringBuilder builder = new StringBuilder("<LineString><coordinates>");
|
||||||
|
if (locations != null) {
|
||||||
|
for (int i = 0; i < locations.size(); i++) {
|
||||||
|
if (i != 0) {
|
||||||
|
builder.append(' ');
|
||||||
|
}
|
||||||
|
appendLocation(locations.get(i), builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.append("</coordinates></LineString>");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a location to a string builder using "longitude,latitude[,altitude]" format.
|
||||||
|
*
|
||||||
|
* @param location the location
|
||||||
|
* @param builder the string builder
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static void appendLocation(Location location, StringBuilder builder) {
|
||||||
|
builder.append(location.getLongitude()).append(",").append(location.getLatitude());
|
||||||
|
if (location.hasAltitude()) {
|
||||||
|
builder.append(",");
|
||||||
|
builder.append(location.getAltitude());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the table id from an input streawm.
|
||||||
|
*
|
||||||
|
* @param inputStream input stream
|
||||||
|
* @return table id or null if not available.
|
||||||
|
*/
|
||||||
|
public static String getTableId(InputStream inputStream) {
|
||||||
|
if (inputStream == null) {
|
||||||
|
Log.d(TAG, "inputStream is null");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] result = new byte[1024];
|
||||||
|
int read;
|
||||||
|
try {
|
||||||
|
read = inputStream.read(result);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (read == -1) {
|
||||||
|
Log.d(TAG, "no data read");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String s;
|
||||||
|
try {
|
||||||
|
s = new String(result, 0, read, UTF8);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] lines = s.split(Strings.LINE_SEPARATOR);
|
||||||
|
if (lines.length > 1 && lines[0].equals(TABLE_ID)) {
|
||||||
|
// returns the next line
|
||||||
|
return lines[1];
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Response is not valid: " + s);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+285
@@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
* 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.sendtogoogle;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils}.
|
||||||
|
*
|
||||||
|
* @author jshih@google.com (Jimmy Shih)
|
||||||
|
*/
|
||||||
|
public class SendFusionTablesUtilsTest extends TestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getMapUrl(Track)} with null track.
|
||||||
|
*/
|
||||||
|
public void testGetMapUrl_null_track() {
|
||||||
|
assertEquals(null, SendFusionTablesUtils.getMapUrl(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getMapUrl(Track)} with null table id.
|
||||||
|
*/
|
||||||
|
public void testGetMapUrl_null_table_id() {
|
||||||
|
Track track = new Track();
|
||||||
|
TripStatistics stats = new TripStatistics();
|
||||||
|
stats.setBounds((int) 100.E6, (int) 10.E6, (int) 50.E6, (int) 5.E6);
|
||||||
|
track.setStatistics(stats);
|
||||||
|
track.setTableId(null);
|
||||||
|
assertEquals(null, SendFusionTablesUtils.getMapUrl(track));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getMapUrl(Track)} with null stats.
|
||||||
|
*/
|
||||||
|
public void testGetMapUrl_null_stats() {
|
||||||
|
Track track = new Track();
|
||||||
|
track.setStatistics(null);
|
||||||
|
track.setTableId("123");
|
||||||
|
assertEquals(null, SendFusionTablesUtils.getMapUrl(track));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getMapUrl(Track)} with a valid track.
|
||||||
|
*/
|
||||||
|
public void testGetMapUrl_valid_track() {
|
||||||
|
Track track = new Track();
|
||||||
|
TripStatistics stats = new TripStatistics();
|
||||||
|
stats.setBounds((int) 100.E6, (int) 10.E6, (int) 50.E6, (int) 5.E6);
|
||||||
|
track.setStatistics(stats);
|
||||||
|
track.setTableId("123");
|
||||||
|
assertEquals(
|
||||||
|
"https://www.google.com/fusiontables/embedviz?"
|
||||||
|
+ "viz=MAP&q=select+col0,+col1,+col2,+col3+from+123+&h=false&lat=7.500000&lng=75.000000"
|
||||||
|
+ "&z=15&t=1&l=col2", SendFusionTablesUtils.getMapUrl(track));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests (@link {@link SendFusionTablesUtils#formatSqlValues(String...)} with
|
||||||
|
* no value.
|
||||||
|
*/
|
||||||
|
public void testFormatSqlValues_no_value() {
|
||||||
|
assertEquals("()", SendFusionTablesUtils.formatSqlValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests (@link {@link SendFusionTablesUtils#formatSqlValues(String...)} with
|
||||||
|
* an empty string.
|
||||||
|
*/
|
||||||
|
public void testFormatSqlValues_empty_string() {
|
||||||
|
assertEquals("(\'\')", SendFusionTablesUtils.formatSqlValues(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests (@link {@link SendFusionTablesUtils#formatSqlValues(String...)} with
|
||||||
|
* one value.
|
||||||
|
*/
|
||||||
|
public void testFormatSqlValues_one_value() {
|
||||||
|
assertEquals("(\'first\')", SendFusionTablesUtils.formatSqlValues("first"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests (@link {@link SendFusionTablesUtils#formatSqlValues(String...)} with
|
||||||
|
* two values.
|
||||||
|
*/
|
||||||
|
public void testFormatSqlValues_two_values() {
|
||||||
|
assertEquals(
|
||||||
|
"(\'first\',\'second\')", SendFusionTablesUtils.formatSqlValues("first", "second"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#escapeSqlString(String)} with a value
|
||||||
|
* containing several single quotes.
|
||||||
|
*/
|
||||||
|
public void testEscapeSqValuel() {
|
||||||
|
String value = "let's'";
|
||||||
|
assertEquals("let''s''", SendFusionTablesUtils.escapeSqlString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getKmlPoint(Location)} with a null
|
||||||
|
* point.
|
||||||
|
*/
|
||||||
|
public void testGetKmlPoint_null_point() {
|
||||||
|
assertEquals(
|
||||||
|
"<Point><coordinates></coordinates></Point>", SendFusionTablesUtils.getKmlPoint(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getKmlPoint(Location)} with a valid
|
||||||
|
* point.
|
||||||
|
*/
|
||||||
|
public void testGetKmlPoint_valid_point() {
|
||||||
|
Location location = new Location("test");
|
||||||
|
location.setLongitude(10.1);
|
||||||
|
location.setLatitude(20.2);
|
||||||
|
location.setAltitude(30.3);
|
||||||
|
assertEquals("<Point><coordinates>10.1,20.2,30.3</coordinates></Point>",
|
||||||
|
SendFusionTablesUtils.getKmlPoint(location));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getKmlLineString(ArrayList)} with a null
|
||||||
|
* locations.
|
||||||
|
*/
|
||||||
|
public void testKmlLineString_null_locations() {
|
||||||
|
assertEquals("<LineString><coordinates></coordinates></LineString>",
|
||||||
|
SendFusionTablesUtils.getKmlLineString(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getKmlLineString(ArrayList)} with no
|
||||||
|
* location.
|
||||||
|
*/
|
||||||
|
public void testKmlLineString_no_location() {
|
||||||
|
ArrayList<Location> locations = new ArrayList<Location>();
|
||||||
|
assertEquals("<LineString><coordinates></coordinates></LineString>",
|
||||||
|
SendFusionTablesUtils.getKmlLineString(locations));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getKmlLineString(ArrayList)} with one
|
||||||
|
* location.
|
||||||
|
*/
|
||||||
|
public void testKmlLineString_one_location() {
|
||||||
|
ArrayList<Location> locations = new ArrayList<Location>();
|
||||||
|
Location location = new Location("test");
|
||||||
|
location.setLongitude(10.1);
|
||||||
|
location.setLatitude(20.2);
|
||||||
|
location.setAltitude(30.3);
|
||||||
|
locations.add(location);
|
||||||
|
assertEquals("<LineString><coordinates>10.1,20.2,30.3</coordinates></LineString>",
|
||||||
|
SendFusionTablesUtils.getKmlLineString(locations));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getKmlLineString(ArrayList)} with two
|
||||||
|
* locations.
|
||||||
|
*/
|
||||||
|
public void testKmlLineString_two_locations() {
|
||||||
|
ArrayList<Location> locations = new ArrayList<Location>();
|
||||||
|
Location location1 = new Location("test");
|
||||||
|
location1.setLongitude(10.1);
|
||||||
|
location1.setLatitude(20.2);
|
||||||
|
location1.setAltitude(30.3);
|
||||||
|
locations.add(location1);
|
||||||
|
|
||||||
|
Location location2 = new Location("test");
|
||||||
|
location2.setLongitude(1.1);
|
||||||
|
location2.setLatitude(2.2);
|
||||||
|
location2.removeAltitude();
|
||||||
|
locations.add(location2);
|
||||||
|
|
||||||
|
assertEquals("<LineString><coordinates>10.1,20.2,30.3 1.1,2.2</coordinates></LineString>",
|
||||||
|
SendFusionTablesUtils.getKmlLineString(locations));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#appendLocation(Location, StringBuilder)}
|
||||||
|
* with no altitude.
|
||||||
|
*/
|
||||||
|
public void testAppendLocation_no_altitude() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
Location location = new Location("test");
|
||||||
|
location.setLongitude(10.1);
|
||||||
|
location.setLatitude(20.2);
|
||||||
|
location.removeAltitude();
|
||||||
|
SendFusionTablesUtils.appendLocation(location, builder);
|
||||||
|
assertEquals("10.1,20.2", builder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#appendLocation(Location, StringBuilder)}
|
||||||
|
* with altitude.
|
||||||
|
*/
|
||||||
|
public void testAppendLocation_has_altitude() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
Location location = new Location("test");
|
||||||
|
location.setLongitude(10.1);
|
||||||
|
location.setLatitude(20.2);
|
||||||
|
location.setAltitude(30.3);
|
||||||
|
SendFusionTablesUtils.appendLocation(location, builder);
|
||||||
|
assertEquals("10.1,20.2,30.3", builder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getTableId(InputStream)} with a null
|
||||||
|
* inputstream.
|
||||||
|
*/
|
||||||
|
public void testGetTableId_null() {
|
||||||
|
assertEquals(null, SendFusionTablesUtils.getTableId(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getTableId(InputStream)} with an
|
||||||
|
* inputstream containing no data.
|
||||||
|
*/
|
||||||
|
public void testGetTableId_no_data() {
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(new byte[0]);
|
||||||
|
assertEquals(null, SendFusionTablesUtils.getTableId(inputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getTableId(InputStream)} with an empty
|
||||||
|
* inputstream.
|
||||||
|
*/
|
||||||
|
public void testGetTableId_empty() {
|
||||||
|
String string = "";
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(string.getBytes());
|
||||||
|
assertEquals(null, SendFusionTablesUtils.getTableId(inputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getTableId(InputStream)} with an one
|
||||||
|
* line inputstream.
|
||||||
|
*/
|
||||||
|
public void testGetTableId_one_line() {
|
||||||
|
String string = "tableid";
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(string.getBytes());
|
||||||
|
assertEquals(null, SendFusionTablesUtils.getTableId(inputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getTableId(InputStream)} with an
|
||||||
|
* inputstream not containing "tableid".
|
||||||
|
*/
|
||||||
|
public void testGetTableId_no_table_id() {
|
||||||
|
String string = "error\n123";
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(string.getBytes());
|
||||||
|
assertEquals(null, SendFusionTablesUtils.getTableId(inputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendFusionTablesUtils#getTableId(InputStream)} with a valid
|
||||||
|
* inputstream.
|
||||||
|
*/
|
||||||
|
public void testGetTableId() {
|
||||||
|
String string = "tableid\n123";
|
||||||
|
InputStream inputStream = null;
|
||||||
|
inputStream = new ByteArrayInputStream(string.getBytes());
|
||||||
|
assertEquals("123", SendFusionTablesUtils.getTableId(inputStream));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user