forked from upstream-mirrors/OpenTracks
Merge from head.
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.mymaps;
|
||||
|
||||
import com.google.wireless.gdata.maps.MapsClient;
|
||||
|
||||
/**
|
||||
* Constants for My Maps.
|
||||
*/
|
||||
public class MyMapsConstants {
|
||||
public static final String MAPSHOP_BASE_URL =
|
||||
"http://maps.google.com/maps/ms";
|
||||
public static final String MAPSHOP_SERVICE = MapsClient.SERVICE;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
*/
|
||||
private MyMapsConstants() { }
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.mymaps;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* MyMapsFeature contains all of the data associated with a feature in My Maps,
|
||||
* where a feature is a marker, line, or shape. Some of the data is stored in a
|
||||
* {@link MyMapsFeatureMetadata} object so that it can be more efficiently
|
||||
* transmitted to other activities.
|
||||
*/
|
||||
public class MyMapsFeature implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 8439035544430497236L;
|
||||
|
||||
/** A marker feature displays an icon at a single point on the map. */
|
||||
public static final int MARKER = 0;
|
||||
|
||||
/**
|
||||
* A line feature displays a line connecting a set of points on the map.
|
||||
*/
|
||||
public static final int LINE = 1;
|
||||
|
||||
/**
|
||||
* A shape feature displays a border defined by connecting a set of points,
|
||||
* including connecting the last to the first, and displays the area
|
||||
* confined by this border.
|
||||
*/
|
||||
public static final int SHAPE = 2;
|
||||
|
||||
/** The local feature id for this feature, if needed. */
|
||||
private String androidId;
|
||||
|
||||
/**
|
||||
* The latitudes of the points of this feature in order, specified in
|
||||
* millionths of a degree north.
|
||||
*/
|
||||
private final Vector<Integer> latitudeE6 = new Vector<Integer>();
|
||||
|
||||
/**
|
||||
* The longitudes of the points of this feature in order, specified in
|
||||
* millionths of a degree east.
|
||||
*/
|
||||
private final Vector<Integer> longitudeE6 = new Vector<Integer>();
|
||||
|
||||
/** The metadata of this feature in a format efficient for transmission. */
|
||||
private MyMapsFeatureMetadata featureInfo = new MyMapsFeatureMetadata();
|
||||
|
||||
/**
|
||||
* Initializes a valid but empty feature. It will default to a
|
||||
* {@link #MARKER} with a blue placemark with a dot as an icon at the
|
||||
* location (0, 0).
|
||||
*/
|
||||
public MyMapsFeature() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new point to the end of this feature.
|
||||
*
|
||||
* @param point The new point to add
|
||||
*/
|
||||
public synchronized void addPoint(GeoPoint point) {
|
||||
latitudeE6.add(point.getLatitudeE6());
|
||||
longitudeE6.add(point.getLongitudeE6());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new local id for this feature based on the current time and
|
||||
* a random number.
|
||||
*/
|
||||
public synchronized void generateAndroidId() {
|
||||
Long time = System.currentTimeMillis();
|
||||
Integer random = (int) (Math.random() * 10000);
|
||||
androidId = time.toString() + "." + random.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current local id for this feature if one is available.
|
||||
*
|
||||
* @return The local id for this feature
|
||||
*/
|
||||
public String getAndroidId() {
|
||||
return androidId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current (html) description of this feature. The description
|
||||
* is stored in the feature metadata.
|
||||
*
|
||||
* @return The description of this feature
|
||||
*/
|
||||
public String getDescription() {
|
||||
return featureInfo.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this feature. That description is stored in the
|
||||
* feature metadata.
|
||||
*
|
||||
* @param description The new description of this feature
|
||||
*/
|
||||
public synchronized void setDescription(String description) {
|
||||
featureInfo.setDescription(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the point at the given index for this feature.
|
||||
*
|
||||
* @param index The index of the point desired
|
||||
* @return A {@link GeoPoint} representing the point or null if that point
|
||||
* doesn't exist
|
||||
*/
|
||||
public GeoPoint getPoint(int index) {
|
||||
if (latitudeE6.size() <= index) {
|
||||
return null;
|
||||
}
|
||||
return new GeoPoint(latitudeE6.get(index), longitudeE6.get(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of points in this feature and return that count.
|
||||
*
|
||||
* @return The number of points in this feature
|
||||
*/
|
||||
public int getPointCount() {
|
||||
return latitudeE6.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the title of this feature. That title is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @return the current title of this feature
|
||||
*/
|
||||
public String getTitle() {
|
||||
return featureInfo.getTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type of this feature. That type is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @return One of {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
|
||||
* identifying the type of this feature
|
||||
*/
|
||||
public int getType() {
|
||||
return featureInfo.getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current color of this feature as an ARGB color integer.
|
||||
* That color is stored in the feature metadata.
|
||||
*
|
||||
* @return The ARGB color of this feature
|
||||
*/
|
||||
public int getColor() {
|
||||
return featureInfo.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current line width of this feature. That line width is
|
||||
* stored in the feature metadata.
|
||||
*
|
||||
* @return The line width of this feature
|
||||
*/
|
||||
public int getLineWidth() {
|
||||
return featureInfo.getLineWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current fill color of this feature as an ARGB color
|
||||
* integer. That color is stored in the feature metadata.
|
||||
*
|
||||
* @return The ARGB fill color of this feature
|
||||
*/
|
||||
public int getFillColor() {
|
||||
return featureInfo.getFillColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current icon url of this feature. That icon url is stored
|
||||
* in the feature metadata.
|
||||
*
|
||||
* @return The icon url for this feature
|
||||
*/
|
||||
public String getIconUrl() {
|
||||
return featureInfo.getIconUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this feature. That title is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @param title The new title of this feature
|
||||
*/
|
||||
public synchronized void setTitle(String title) {
|
||||
featureInfo.setTitle(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of this feature. That type is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @param type The new type of the feature. That type must be one of
|
||||
* {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
|
||||
*/
|
||||
public synchronized void setType(int type) {
|
||||
featureInfo.setType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ARGB color of this feature. That color is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @param color The new ARGB color of this feature
|
||||
*/
|
||||
public synchronized void setColor(int color) {
|
||||
featureInfo.setColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the icon url of this feature. That icon url is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @param url The new icon url of the feature
|
||||
*/
|
||||
public synchronized void setIconUrl(String url) {
|
||||
featureInfo.setIconUrl(url);
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.mymaps;
|
||||
|
||||
/**
|
||||
* Metadata about a maps feature.
|
||||
*/
|
||||
public class MyMapsFeatureMetadata {
|
||||
|
||||
private static final String BLUE_DOT_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/blue-dot.png";
|
||||
private static final int DEFAULT_COLOR = 0x800000FF;
|
||||
private static final int DEFAULT_FILL_COLOR = 0xC00000FF;
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private int type;
|
||||
private int color;
|
||||
private int lineWidth;
|
||||
private int fillColor;
|
||||
private String iconUrl;
|
||||
|
||||
public MyMapsFeatureMetadata() {
|
||||
title = "";
|
||||
description = "";
|
||||
type = MyMapsFeature.MARKER;
|
||||
color = DEFAULT_COLOR;
|
||||
lineWidth = 5;
|
||||
fillColor = DEFAULT_FILL_COLOR;
|
||||
iconUrl = BLUE_DOT_URL;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getLineWidth() {
|
||||
return lineWidth;
|
||||
}
|
||||
|
||||
public void setLineWidth(int width) {
|
||||
lineWidth = width;
|
||||
}
|
||||
|
||||
public int getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
public void setFillColor(int color) {
|
||||
fillColor = color;
|
||||
}
|
||||
|
||||
public String getIconUrl() {
|
||||
return iconUrl;
|
||||
}
|
||||
|
||||
public void setIconUrl(String url) {
|
||||
iconUrl = new String(url);
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.mymaps;
|
||||
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.data.StringUtils;
|
||||
import com.google.wireless.gdata.maps.MapFeatureEntry;
|
||||
import com.google.wireless.gdata.maps.MapsClient;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* Converter from GData objects to MyMaps objects.
|
||||
*/
|
||||
public class MyMapsGDataConverter {
|
||||
|
||||
private final XmlSerializer xmlSerializer;
|
||||
|
||||
public MyMapsGDataConverter() throws XmlPullParserException {
|
||||
xmlSerializer = XmlPullParserFactory.newInstance().newSerializer();
|
||||
}
|
||||
|
||||
public static MyMapsMapMetadata getMapMetadataForEntry(
|
||||
MapFeatureEntry entry) {
|
||||
MyMapsMapMetadata metadata = new MyMapsMapMetadata();
|
||||
if ("public".equals(entry.getPrivacy())) {
|
||||
metadata.setSearchable(true);
|
||||
} else {
|
||||
metadata.setSearchable(false);
|
||||
}
|
||||
metadata.setTitle(entry.getTitle());
|
||||
|
||||
String desc = entry.getContent();
|
||||
if (desc.length() > 12) {
|
||||
metadata.setDescription(desc.substring(9, desc.length() - 3));
|
||||
}
|
||||
metadata.setGDataEditUri(entry.getEditUri());
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public static String getMapidForEntry(Entry entry) {
|
||||
return MapsClient.getMapIdFromMapEntryId(entry.getId());
|
||||
}
|
||||
|
||||
public static Entry getMapEntryForMetadata(MyMapsMapMetadata metadata) {
|
||||
MapFeatureEntry entry = new MapFeatureEntry();
|
||||
entry.setEditUri(metadata.getGDataEditUri());
|
||||
entry.setTitle(metadata.getTitle());
|
||||
entry.setContent(metadata.getDescription());
|
||||
entry.setPrivacy(metadata.getSearchable() ? "public" : "unlisted");
|
||||
entry.setAuthor("android");
|
||||
entry.setEmail("nobody@google.com");
|
||||
return entry;
|
||||
}
|
||||
|
||||
public MapFeatureEntry getEntryForFeature(MyMapsFeature feature) {
|
||||
MapFeatureEntry entry = new MapFeatureEntry();
|
||||
entry.setTitle(feature.getTitle());
|
||||
entry.setAuthor("android");
|
||||
entry.setEmail("nobody@google.com");
|
||||
entry.setCategoryScheme("http://schemas.google.com/g/2005#kind");
|
||||
entry.setCategory("http://schemas.google.com/g/2008#mapfeature");
|
||||
entry.setEditUri("");
|
||||
if (!StringUtils.isEmpty(feature.getAndroidId())) {
|
||||
entry.setAttribute("_androidId", feature.getAndroidId());
|
||||
}
|
||||
try {
|
||||
StringWriter writer = new StringWriter();
|
||||
xmlSerializer.setOutput(writer);
|
||||
xmlSerializer.startTag(null, "Placemark");
|
||||
xmlSerializer.attribute(null, "xmlns", "http://earth.google.com/kml/2.2");
|
||||
xmlSerializer.startTag(null, "Style");
|
||||
if (feature.getType() == MyMapsFeature.MARKER) {
|
||||
xmlSerializer.startTag(null, "IconStyle");
|
||||
xmlSerializer.startTag(null, "Icon");
|
||||
xmlSerializer.startTag(null, "href");
|
||||
xmlSerializer.text(feature.getIconUrl());
|
||||
xmlSerializer.endTag(null, "href");
|
||||
xmlSerializer.endTag(null, "Icon");
|
||||
xmlSerializer.endTag(null, "IconStyle");
|
||||
} else {
|
||||
xmlSerializer.startTag(null, "LineStyle");
|
||||
xmlSerializer.startTag(null, "color");
|
||||
int color = feature.getColor();
|
||||
// Reverse the color because KML is ABGR and Android is ARGB
|
||||
xmlSerializer.text(Integer.toHexString(
|
||||
Color.argb(Color.alpha(color), Color.blue(color),
|
||||
Color.green(color), Color.red(color))));
|
||||
xmlSerializer.endTag(null, "color");
|
||||
xmlSerializer.startTag(null, "width");
|
||||
xmlSerializer.text(Integer.toString(feature.getLineWidth()));
|
||||
xmlSerializer.endTag(null, "width");
|
||||
xmlSerializer.endTag(null, "LineStyle");
|
||||
|
||||
if (feature.getType() == MyMapsFeature.SHAPE) {
|
||||
xmlSerializer.startTag(null, "PolyStyle");
|
||||
xmlSerializer.startTag(null, "color");
|
||||
int fcolor = feature.getFillColor();
|
||||
// Reverse the color because KML is ABGR and Android is ARGB
|
||||
xmlSerializer.text(Integer.toHexString(Color.argb(Color.alpha(fcolor),
|
||||
Color.blue(fcolor), Color.green(fcolor), Color.red(fcolor))));
|
||||
xmlSerializer.endTag(null, "color");
|
||||
xmlSerializer.startTag(null, "fill");
|
||||
xmlSerializer.text("1");
|
||||
xmlSerializer.endTag(null, "fill");
|
||||
xmlSerializer.startTag(null, "outline");
|
||||
xmlSerializer.text("1");
|
||||
xmlSerializer.endTag(null, "outline");
|
||||
xmlSerializer.endTag(null, "PolyStyle");
|
||||
}
|
||||
}
|
||||
xmlSerializer.endTag(null, "Style");
|
||||
xmlSerializer.startTag(null, "name");
|
||||
xmlSerializer.text(feature.getTitle());
|
||||
xmlSerializer.endTag(null, "name");
|
||||
xmlSerializer.startTag(null, "description");
|
||||
xmlSerializer.cdsect(feature.getDescription());
|
||||
xmlSerializer.endTag(null, "description");
|
||||
StringBuilder pointBuilder = new StringBuilder();
|
||||
for (int i = 0; i < feature.getPointCount(); ++i) {
|
||||
if (i > 0) {
|
||||
pointBuilder.append('\n');
|
||||
}
|
||||
pointBuilder.append(feature.getPoint(i).getLongitudeE6() / 1e6);
|
||||
pointBuilder.append(',');
|
||||
pointBuilder.append(feature.getPoint(i).getLatitudeE6() / 1e6);
|
||||
pointBuilder.append(",0.000000");
|
||||
}
|
||||
String pointString = pointBuilder.toString();
|
||||
if (feature.getType() == MyMapsFeature.MARKER) {
|
||||
xmlSerializer.startTag(null, "Point");
|
||||
xmlSerializer.startTag(null, "coordinates");
|
||||
xmlSerializer.text(pointString);
|
||||
xmlSerializer.endTag(null, "coordinates");
|
||||
xmlSerializer.endTag(null, "Point");
|
||||
} else if (feature.getType() == MyMapsFeature.LINE) {
|
||||
xmlSerializer.startTag(null, "LineString");
|
||||
xmlSerializer.startTag(null, "tessellate");
|
||||
xmlSerializer.text("1");
|
||||
xmlSerializer.endTag(null, "tessellate");
|
||||
xmlSerializer.startTag(null, "coordinates");
|
||||
xmlSerializer.text(pointString);
|
||||
xmlSerializer.endTag(null, "coordinates");
|
||||
xmlSerializer.endTag(null, "LineString");
|
||||
} else {
|
||||
xmlSerializer.startTag(null, "Polygon");
|
||||
xmlSerializer.startTag(null, "outerBoundaryIs");
|
||||
xmlSerializer.startTag(null, "LinearRing");
|
||||
xmlSerializer.startTag(null, "tessellate");
|
||||
xmlSerializer.text("1");
|
||||
xmlSerializer.endTag(null, "tessellate");
|
||||
xmlSerializer.startTag(null, "coordinates");
|
||||
xmlSerializer.text(pointString + "\n"
|
||||
+ Double.toString(feature.getPoint(0).getLongitudeE6() / 1e6)
|
||||
+ ","
|
||||
+ Double.toString(feature.getPoint(0).getLatitudeE6() / 1e6)
|
||||
+ ",0.000000");
|
||||
xmlSerializer.endTag(null, "coordinates");
|
||||
xmlSerializer.endTag(null, "LinearRing");
|
||||
xmlSerializer.endTag(null, "outerBoundaryIs");
|
||||
xmlSerializer.endTag(null, "Polygon");
|
||||
}
|
||||
xmlSerializer.endTag(null, "Placemark");
|
||||
xmlSerializer.flush();
|
||||
entry.setContent(writer.toString());
|
||||
Log.d("My Google Maps", "Generated kml:\n" + entry.getContent());
|
||||
Log.d("My Google Maps", "Edit URI: " + entry.getEditUri());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -1,190 +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.mymaps;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
|
||||
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.maps.MapsClient;
|
||||
import com.google.wireless.gdata.maps.xml.XmlMapsGDataParserFactory;
|
||||
import com.google.wireless.gdata.parser.xml.SimplePullParser.ParseException;
|
||||
import com.google.wireless.gdata2.ConflictDetectedException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MyMapsGDataWrapper provides a wrapper around GData operations that maintains
|
||||
* the GData client, and provides a method to run gdata queries with proper
|
||||
* error handling. After a query is run, the wrapper can be queried about the
|
||||
* error that occurred.
|
||||
*/
|
||||
public class MyMapsGDataWrapper {
|
||||
/**
|
||||
* A QueryFunction is passed in when executing a query. The query function
|
||||
* of the class is called with the GData client as a parameter. The
|
||||
* function should execute whatever operations it desires on the client
|
||||
* without concern for whether the client will throw an error.
|
||||
*/
|
||||
public interface QueryFunction {
|
||||
public abstract void query(MapsClient client)
|
||||
throws AuthenticationException, IOException, ParseException,
|
||||
ConflictDetectedException, Exception;
|
||||
}
|
||||
|
||||
// The types of error that may be encountered
|
||||
/** No error occurred. */
|
||||
public static final int ERROR_NO_ERROR = 0;
|
||||
/** There was an authentication error, the auth token may be invalid. */
|
||||
public static final int ERROR_AUTH = 1;
|
||||
/** There was an internal error on the server side. */
|
||||
public static final int ERROR_INTERNAL = 2;
|
||||
/** There was an error connecting to the server. */
|
||||
public static final int ERROR_CONNECTION = 3;
|
||||
/** The item queried did not exit. */
|
||||
public static final int ERROR_NOT_FOUND = 4;
|
||||
/** There was an error parsing or serializing locally. */
|
||||
public static final int ERROR_LOCAL = 5;
|
||||
/** There was a conflict, update the entry and try again. */
|
||||
public static final int ERROR_CONFLICT = 6;
|
||||
/**
|
||||
* A query was run after cleaning up the wrapper, so the client was invalid.
|
||||
*/
|
||||
public static final int ERROR_CLEANED_UP = 7;
|
||||
/** An unknown error occurred. */
|
||||
public static final int ERROR_UNKNOWN = 100;
|
||||
|
||||
private final GDataClient androidGdataClient;
|
||||
private final MapsClient client;
|
||||
|
||||
private String errorMessage;
|
||||
private int errorType;
|
||||
private AuthManager auth;
|
||||
private boolean retryOnAuthFailure;
|
||||
private int retriesPending;
|
||||
private boolean cleanupCalled;
|
||||
|
||||
public MyMapsGDataWrapper(Context context) {
|
||||
androidGdataClient = GDataClientFactory.getGDataClient(context);
|
||||
client =
|
||||
new MapsClient(androidGdataClient, new XmlMapsGDataParserFactory(
|
||||
new AndroidXmlParserFactory()));
|
||||
errorType = ERROR_NO_ERROR;
|
||||
errorMessage = null;
|
||||
auth = null;
|
||||
retryOnAuthFailure = false;
|
||||
retriesPending = 0;
|
||||
cleanupCalled = false;
|
||||
}
|
||||
|
||||
public boolean runQuery(final QueryFunction query) {
|
||||
if (client == null) {
|
||||
errorType = ERROR_CLEANED_UP;
|
||||
errorMessage = "GData Wrapper has already been cleaned up!";
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
query.query(client);
|
||||
errorType = ERROR_NO_ERROR;
|
||||
errorMessage = null;
|
||||
return true;
|
||||
} catch (AuthenticationException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_AUTH;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (HttpException e) {
|
||||
Log.e(MyTracksConstants.TAG, "HttpException", e);
|
||||
errorMessage = e.getMessage();
|
||||
if (errorMessage != null && errorMessage.contains("401")) {
|
||||
errorType = ERROR_AUTH;
|
||||
} else {
|
||||
errorType = ERROR_CONNECTION;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||
errorMessage = e.getMessage();
|
||||
if (errorMessage != null && errorMessage.contains("503")) {
|
||||
errorType = ERROR_INTERNAL;
|
||||
} else {
|
||||
errorType = ERROR_CONNECTION;
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_LOCAL;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (ConflictDetectedException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_CONFLICT;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (Exception e) {
|
||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_UNKNOWN;
|
||||
errorMessage = e.getMessage();
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.d(MyTracksConstants.TAG, "GData error encountered: " + errorMessage);
|
||||
if (errorType == ERROR_AUTH && auth != null) {
|
||||
Runnable whenFinished = null;
|
||||
if (retryOnAuthFailure) {
|
||||
retriesPending++;
|
||||
whenFinished = new Runnable() {
|
||||
public void run() {
|
||||
retriesPending--;
|
||||
retryOnAuthFailure = false;
|
||||
runQuery(query);
|
||||
if (cleanupCalled && retriesPending == 0) {
|
||||
cleanUp();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
auth.invalidateAndRefresh(whenFinished);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getErrorType() {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
// cleanUp must be called when done using this wrapper to close the client.
|
||||
// Note that the cleanup will be delayed if auth failure retries were
|
||||
// requested and there is a pending retry.
|
||||
public void cleanUp() {
|
||||
if (retriesPending == 0 && !cleanupCalled) {
|
||||
androidGdataClient.close();
|
||||
}
|
||||
cleanupCalled = true;
|
||||
}
|
||||
|
||||
public void setAuthManager(AuthManager newAuth) {
|
||||
this.auth = newAuth;
|
||||
}
|
||||
|
||||
public void setRetryOnAuthFailure(boolean retry) {
|
||||
retryOnAuthFailure = retry;
|
||||
}
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.mymaps;
|
||||
|
||||
import com.google.android.accounts.Account;
|
||||
import com.google.android.apps.mymaps.MyMapsGDataWrapper.QueryFunction;
|
||||
import com.google.android.apps.mytracks.AccountChooser;
|
||||
import com.google.android.apps.mytracks.MyTracks;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.maps.MapFeatureEntry;
|
||||
import com.google.wireless.gdata.maps.MapsClient;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.DataSetObserver;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnCreateContextMenuListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Activity which displays the list of "my maps" maps.
|
||||
*/
|
||||
public class MyMapsList extends Activity implements ListAdapter {
|
||||
|
||||
private static final int MENU_OPEN = 0;
|
||||
private static final int MENU_SHARE = 2;
|
||||
private static final int GET_LOGIN = 1;
|
||||
|
||||
private LookupThread listLookup;
|
||||
private Vector<String[]> mapsList;
|
||||
private Vector<Boolean> publicList;
|
||||
private Set<DataSetObserver> observerSet;
|
||||
private Handler loadHandler;
|
||||
private int contextPosition;
|
||||
private AuthManager auth;
|
||||
|
||||
/**
|
||||
* Thread which looks up the maps list.
|
||||
*/
|
||||
private class LookupThread extends Thread {
|
||||
private boolean finished = false;
|
||||
private boolean success = true;
|
||||
|
||||
private GDataParser listParser;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
finished = false;
|
||||
listParser = null;
|
||||
MyMapsGDataWrapper wrapper = new MyMapsGDataWrapper(MyMapsList.this);
|
||||
wrapper.setAuthManager(auth);
|
||||
wrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
success = wrapper.runQuery(new QueryFunction() {
|
||||
public void query(MapsClient client) throws IOException, Exception {
|
||||
listParser = client.getParserForFeed(
|
||||
MapFeatureEntry.class, MapsClient.getMapsFeed(),
|
||||
auth.getAuthToken());
|
||||
listParser.init();
|
||||
while (listParser.hasMoreData()) {
|
||||
MapFeatureEntry entry =
|
||||
(MapFeatureEntry) listParser.readNextEntry(null);
|
||||
MyMapsMapMetadata metadata =
|
||||
MyMapsGDataConverter.getMapMetadataForEntry(entry);
|
||||
String mapid = MyMapsGDataConverter.getMapidForEntry(entry);
|
||||
synchronized (mapsList) {
|
||||
// Search through the maps list to see if it has the mapid and
|
||||
// remove it if so, so that we can replace it with updated info
|
||||
for (int i = 0; i < mapsList.size(); ++i) {
|
||||
if (mapid.equals(mapsList.get(i)[0])) {
|
||||
mapsList.remove(i);
|
||||
publicList.remove(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
mapsList.add(new String[] {
|
||||
mapid, metadata.getTitle(), metadata.getDescription() });
|
||||
publicList.add(metadata.getSearchable());
|
||||
}
|
||||
}
|
||||
listParser.close();
|
||||
listParser = null;
|
||||
success = true;
|
||||
finished = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (listParser != null) {
|
||||
listParser.close();
|
||||
}
|
||||
wrapper.cleanUp();
|
||||
if (success || wrapper.getErrorType() != MyMapsGDataWrapper.ERROR_AUTH) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
public boolean succeeded() {
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
private final Runnable checkLoaded = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (listLookup.isFinished()) {
|
||||
findViewById(R.id.loading).setVisibility(View.GONE);
|
||||
if (!listLookup.succeeded()) {
|
||||
findViewById(R.id.failed).setVisibility(View.VISIBLE);
|
||||
}
|
||||
TextView emptyView = (TextView) findViewById(R.id.mapslist_empty);
|
||||
ListView list = (ListView) findViewById(R.id.maplist);
|
||||
list.setEmptyView(emptyView);
|
||||
} else {
|
||||
loadHandler.postDelayed(checkLoaded, 500);
|
||||
}
|
||||
Iterator<DataSetObserver> iter = observerSet.iterator();
|
||||
while (iter.hasNext()) {
|
||||
iter.next().onChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final OnItemClickListener clickListener = new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position,
|
||||
long id) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra("mapid", (String) getItem(position));
|
||||
MyMapsList.this.setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
private final OnCreateContextMenuListener contextMenuListener =
|
||||
new OnCreateContextMenuListener() {
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
AdapterView.AdapterContextMenuInfo info =
|
||||
(AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
contextPosition = info.position;
|
||||
menu.add(0, MENU_OPEN, 0, R.string.open_map);
|
||||
menu.add(0, MENU_SHARE, 0, R.string.share_map);
|
||||
}
|
||||
};
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
auth = AuthManagerFactory.getAuthManager(this, GET_LOGIN, null, true,
|
||||
MapsClient.SERVICE);
|
||||
|
||||
setContentView(R.layout.list);
|
||||
|
||||
ListView list = (ListView) findViewById(R.id.maplist);
|
||||
list.setOnItemClickListener(clickListener);
|
||||
list.setOnCreateContextMenuListener(contextMenuListener);
|
||||
|
||||
mapsList = new Vector<String[]>();
|
||||
publicList = new Vector<Boolean>();
|
||||
|
||||
listLookup = new LookupThread();
|
||||
// TODO fix this for non-froyo devices.
|
||||
if (AuthManagerFactory.useModernAuthManager()) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MyTracks.getInstance().getAccountChooser().chooseAccount(
|
||||
MyMapsList.this,
|
||||
new AccountChooser.AccountHandler() {
|
||||
@Override
|
||||
public void handleAccountSelected(Account account) {
|
||||
if (account != null) {
|
||||
// The user did not quit and there was a valid google
|
||||
// account.
|
||||
doLogin(account);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
doLogin(null);
|
||||
}
|
||||
|
||||
observerSet = new HashSet<DataSetObserver>();
|
||||
|
||||
list.setAdapter(this);
|
||||
|
||||
loadHandler = new Handler();
|
||||
loadHandler.postDelayed(checkLoaded, 500);
|
||||
}
|
||||
|
||||
private void doLogin(final Account account) {
|
||||
auth.doLogin(new Runnable() {
|
||||
public void run() {
|
||||
listLookup.start();
|
||||
}
|
||||
}, account);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
if (requestCode == GET_LOGIN) {
|
||||
auth.authResult(resultCode, data);
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case MENU_OPEN:
|
||||
clickListener.onItemClick(null, null, contextPosition, 0);
|
||||
return true;
|
||||
case MENU_SHARE:
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
|
||||
getText(R.string.share_map_subject));
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, String.format(
|
||||
getText(R.string.share_map_body_format).toString(),
|
||||
mapsList.get(contextPosition)[1],
|
||||
MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid="
|
||||
+ mapsList.get(contextPosition)[0]));
|
||||
startActivity(Intent.createChooser(shareIntent,
|
||||
getText(R.string.share_map).toString()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mapsList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mapsList.get(position)[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView =
|
||||
getLayoutInflater().inflate(R.layout.listitem, parent, false);
|
||||
}
|
||||
|
||||
String[] map = mapsList.get(position);
|
||||
((TextView) convertView.findViewById(R.id.maplistitem)).setText(map[1]);
|
||||
((TextView) convertView.findViewById(R.id.maplistdesc)).setText(map[2]);
|
||||
TextView publicUnlisted =
|
||||
(TextView) convertView.findViewById(R.id.maplistpublic);
|
||||
if (publicList.get(position)) {
|
||||
publicUnlisted.setTextColor(Color.RED);
|
||||
publicUnlisted.setText(R.string.public_map);
|
||||
} else {
|
||||
publicUnlisted.setTextColor(Color.GREEN);
|
||||
publicUnlisted.setText(R.string.unlisted_map);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return mapsList.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
observerSet.add(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
observerSet.remove(observer);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.mymaps;
|
||||
|
||||
/**
|
||||
* Metadata about a "my maps" map.
|
||||
*/
|
||||
public class MyMapsMapMetadata {
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private String gdataEditUri;
|
||||
private boolean searchable;
|
||||
|
||||
public MyMapsMapMetadata() {
|
||||
title = "";
|
||||
description = "";
|
||||
gdataEditUri = "";
|
||||
searchable = false;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = new String(title);
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = new String(description);
|
||||
}
|
||||
|
||||
public boolean getSearchable() {
|
||||
return searchable;
|
||||
}
|
||||
|
||||
public void setSearchable(boolean searchable) {
|
||||
this.searchable = searchable;
|
||||
}
|
||||
|
||||
public String getGDataEditUri() {
|
||||
return gdataEditUri;
|
||||
}
|
||||
|
||||
public void setGDataEditUri(String editUri) {
|
||||
this.gdataEditUri = new String(editUri);
|
||||
}
|
||||
}
|
||||
@@ -85,13 +85,13 @@ public class DialogManager {
|
||||
builder.setTitle("Title");
|
||||
builder.setMessage("Message");
|
||||
builder.setPositiveButton(activity.getString(R.string.ok), null);
|
||||
builder.setNeutralButton(activity.getString(R.string.share_map),
|
||||
builder.setNeutralButton(activity.getString(R.string.share_track),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
activity.shareLinkToMyMap(activity.getSendToMyMapsMapId());
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
activity.shareLinkToFusionTable(activity.sendToTrackId);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
sendToGoogleResultDialog = builder.create();
|
||||
return sendToGoogleResultDialog;
|
||||
case DIALOG_WRITE_PROGRESS:
|
||||
@@ -119,9 +119,9 @@ public class DialogManager {
|
||||
sendToGoogleResultDialog.setIcon(success
|
||||
? android.R.drawable.ic_dialog_info
|
||||
: android.R.drawable.ic_dialog_alert);
|
||||
sendToGoogleResultDialog.setMessage(activity.getMapsResultMessage());
|
||||
sendToGoogleResultDialog.setMessage(activity.getFusionTablesResultMessage());
|
||||
|
||||
boolean canShare = activity.getSendToMyMapsMapId() != null;
|
||||
boolean canShare = activity.getSendToFusionTablesTableId() != null;
|
||||
View share =
|
||||
sendToGoogleResultDialog.findViewById(android.R.id.button3);
|
||||
if (share != null) {
|
||||
|
||||
@@ -21,8 +21,6 @@ import static com.google.android.apps.mytracks.DialogManager.DIALOG_SEND_TO_GOOG
|
||||
import static com.google.android.apps.mytracks.DialogManager.DIALOG_WRITE_PROGRESS;
|
||||
|
||||
import com.google.android.accounts.Account;
|
||||
import com.google.android.apps.mymaps.MyMapsConstants;
|
||||
import com.google.android.apps.mymaps.MyMapsList;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||
@@ -30,11 +28,11 @@ import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||
import com.google.android.apps.mytracks.io.GpxImporter;
|
||||
import com.google.android.apps.mytracks.io.SendToDocs;
|
||||
import com.google.android.apps.mytracks.io.SendToMyMaps;
|
||||
import com.google.android.apps.mytracks.io.SendToFusionTables;
|
||||
import com.google.android.apps.mytracks.io.TempFileCleaner;
|
||||
import com.google.android.apps.mytracks.io.TrackWriter;
|
||||
import com.google.android.apps.mytracks.io.TrackWriterFactory;
|
||||
import com.google.android.apps.mytracks.io.SendToMyMaps.OnSendCompletedListener;
|
||||
import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
|
||||
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||
@@ -93,7 +91,6 @@ import org.xml.sax.SAXException;
|
||||
*/
|
||||
public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
OnSharedPreferenceChangeListener, ProgressIndicator {
|
||||
|
||||
/**
|
||||
* Singleton instance
|
||||
*/
|
||||
@@ -124,10 +121,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
* Used by SendToGoogleResultDialog.
|
||||
*/
|
||||
|
||||
public boolean sendToMyMapsSuccess = false;
|
||||
public long sendToTrackId = -1;
|
||||
public boolean sendToFusionTablesSuccess = false;
|
||||
public boolean sendToDocsSuccess = false;
|
||||
public String sendToMyMapsMapId;
|
||||
public String sendToMyMapsMessage = "";
|
||||
public String sendToFusionTablesTableId;
|
||||
public String sendToFusionTablesMessage = "";
|
||||
public String sendToDocsMessage = "";
|
||||
|
||||
/**
|
||||
@@ -148,9 +146,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
*/
|
||||
private long selectedTrackId = -1;
|
||||
|
||||
/**
|
||||
* Does the user want to share the current track.
|
||||
*/
|
||||
/**
|
||||
* Does the user want to share the current track.
|
||||
*/
|
||||
private boolean shareRequested = false;
|
||||
|
||||
/**
|
||||
@@ -451,13 +449,13 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
case MyTracksConstants.GET_MAP: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
results.putExtra("trackid", selectedTrackId);
|
||||
if (results.hasExtra("mapid")) {
|
||||
sendToMyMapsMapId = results.getStringExtra("mapid");
|
||||
if (results.hasExtra("tableid")) {
|
||||
sendToFusionTablesTableId = results.getStringExtra("tableid");
|
||||
}
|
||||
setProgressMessage(
|
||||
R.string.progress_message_authenticating_mymaps);
|
||||
R.string.progress_message_authenticating_fusiontables);
|
||||
authenticate(results, MyTracksConstants.SEND_TO_GOOGLE,
|
||||
MyMapsConstants.MAPSHOP_SERVICE);
|
||||
SendToFusionTables.SERVICE_ID);
|
||||
} else {
|
||||
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
|
||||
}
|
||||
@@ -517,13 +515,14 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MyTracksConstants.AUTHENTICATE_TO_DOCS: {
|
||||
case MyTracksConstants.AUTHENTICATE_TO_DOCLIST: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
setProgressValue(0);
|
||||
setProgressMessage(
|
||||
R.string.progress_message_authenticating_docs);
|
||||
authenticate(results,
|
||||
MyTracksConstants.AUTHENTICATE_TO_TRIX, "writely");
|
||||
MyTracksConstants.AUTHENTICATE_TO_TRIX,
|
||||
SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
|
||||
} else {
|
||||
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
|
||||
}
|
||||
@@ -534,7 +533,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
setProgressValue(30);
|
||||
setProgressMessage(
|
||||
R.string.progress_message_authenticating_docs);
|
||||
authenticate(results, MyTracksConstants.SEND_TO_DOCS, "wise");
|
||||
authenticate(results, MyTracksConstants.SEND_TO_DOCS,
|
||||
SendToDocs.GDATA_SERVICE_NAME_TRIX);
|
||||
} else {
|
||||
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
|
||||
}
|
||||
@@ -546,8 +546,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
setProgressValue(50);
|
||||
setProgressMessage(R.string.progress_message_sending_docs);
|
||||
final long trackId = results.getLongExtra("trackid", selectedTrackId);
|
||||
final SendToDocs sender = new SendToDocs(this, authMap.get("wise"),
|
||||
authMap.get("writely"), trackId);
|
||||
final SendToDocs sender = new SendToDocs(this,
|
||||
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
|
||||
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST), trackId);
|
||||
sendToTrackId = trackId;
|
||||
Runnable onCompletion = new Runnable() {
|
||||
public void run() {
|
||||
setProgressValue(100);
|
||||
@@ -556,7 +558,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
public void run() {
|
||||
sendToDocsMessage = sender.getStatusMessage();
|
||||
sendToDocsSuccess = sender.wasSuccess();
|
||||
handleMapsFinish();
|
||||
handleFusionTablesFinish(trackId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -569,61 +571,55 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
break;
|
||||
}
|
||||
case MyTracksConstants.SEND_TO_GOOGLE_DIALOG: {
|
||||
shareRequested = false;
|
||||
shareRequested = false;
|
||||
dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE);
|
||||
break;
|
||||
}
|
||||
case MyTracksConstants.SEND_TO_GOOGLE: {
|
||||
if (results != null && resultCode == RESULT_OK) {
|
||||
final String mapId;
|
||||
final long trackId;
|
||||
if (results.hasExtra("mapid")) {
|
||||
mapId = results.getStringExtra("mapid");
|
||||
} else {
|
||||
mapId = "new";
|
||||
}
|
||||
if (results.hasExtra("trackid")) {
|
||||
trackId = results.getLongExtra("trackid", -1);
|
||||
} else {
|
||||
trackId = selectedTrackId;
|
||||
}
|
||||
|
||||
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
|
||||
@Override
|
||||
public void onSendCompleted(String mapId, boolean success,
|
||||
public void onSendCompleted(String tableId, boolean success,
|
||||
int statusMessage) {
|
||||
sendToMyMapsMessage = getString(statusMessage);
|
||||
sendToMyMapsSuccess = success;
|
||||
if (sendToMyMapsSuccess) {
|
||||
sendToMyMapsMapId = mapId;
|
||||
// Update the map id for this track:
|
||||
sendToFusionTablesMessage = getString(statusMessage);
|
||||
sendToFusionTablesSuccess = success;
|
||||
if (sendToFusionTablesSuccess) {
|
||||
sendToFusionTablesTableId = tableId;
|
||||
// Update the table id for this track:
|
||||
try {
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
track.setMapId(mapId);
|
||||
track.setTableId(tableId);
|
||||
providerUtils.updateTrack(track);
|
||||
} catch (RuntimeException e) {
|
||||
// If that fails whatever reasons we'll just log an error, but
|
||||
// continue.
|
||||
Log.w(MyTracksConstants.TAG, "Updating map id failed.", e);
|
||||
Log.w(MyTracksConstants.TAG, "Updating table id failed.", e);
|
||||
}
|
||||
}
|
||||
if (dialogManager.getSendToGoogleDialog().getSendToDocs()) {
|
||||
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCS,
|
||||
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST,
|
||||
RESULT_OK, new Intent());
|
||||
} else {
|
||||
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
handleMapsFinish();
|
||||
handleFusionTablesFinish(trackId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
final SendToMyMaps sender = new SendToMyMaps(this, mapId, auth,
|
||||
sendToTrackId = trackId;
|
||||
final SendToFusionTables sender = new SendToFusionTables(this, auth,
|
||||
trackId, this/*progressIndicator*/, onCompletion);
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread("SendToMyMaps");
|
||||
HandlerThread handlerThread = new HandlerThread("SendToFusionTables");
|
||||
handlerThread.start();
|
||||
Handler handler = new Handler(handlerThread.getLooper());
|
||||
handler.post(sender);
|
||||
@@ -654,8 +650,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
case MyTracksConstants.SHARE_LINK: {
|
||||
Track selectedTrack = providerUtils.getTrack(selectedTrackId);
|
||||
if (selectedTrack != null) {
|
||||
if (selectedTrack.getMapId().length() > 0) {
|
||||
shareLinkToMyMap(selectedTrack.getMapId());
|
||||
if (selectedTrack.getTableId().length() > 0) {
|
||||
shareLinkToFusionTable(selectedTrackId);
|
||||
} else {
|
||||
shareRequested = true;
|
||||
dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE);
|
||||
@@ -724,11 +720,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
* Resets status information for sending to MyMaps/Docs.
|
||||
*/
|
||||
public void resetSendToGoogleStatus() {
|
||||
sendToMyMapsMessage = "";
|
||||
sendToMyMapsSuccess = true;
|
||||
sendToFusionTablesMessage = "";
|
||||
sendToFusionTablesSuccess = true;
|
||||
sendToDocsMessage = "";
|
||||
sendToDocsSuccess = true;
|
||||
sendToMyMapsMapId = null;
|
||||
sendToFusionTablesTableId = null;
|
||||
}
|
||||
|
||||
private void importGpxFile(final String fileName) {
|
||||
@@ -796,10 +792,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares a link to a My Map via external app (email, gmail, ...)
|
||||
* Shares a link to a fusion table via external app (email, gmail, ...)
|
||||
* A chooser with apps that support text/plain will be shown to the user.
|
||||
*/
|
||||
public void shareLinkToMyMap(String mapId) {
|
||||
public void shareLinkToFusionTable(long trackId) {
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
|
||||
@@ -811,7 +807,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
getString(R.string.share_url_only_key), false);
|
||||
}
|
||||
|
||||
String url = MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid=" + mapId;
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
String url = SendToFusionTables.getMapVisualizationUrl(track);
|
||||
String msg = shareUrlOnly ? url : String.format(
|
||||
getResources().getText(R.string.share_map_body_format).toString(), url);
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
|
||||
@@ -930,7 +927,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
service);
|
||||
authMap.put(service, auth);
|
||||
}
|
||||
Log.d(MyTracksConstants.TAG, "Loggin in to " + service + "...");
|
||||
Log.d(MyTracksConstants.TAG, "Logging in to " + service + "...");
|
||||
if (AuthManagerFactory.useModernAuthManager()) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
@@ -1040,19 +1037,13 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
setProgressValue(0);
|
||||
clearProgressMessage();
|
||||
dialogManager.showDialogSafely(DIALOG_PROGRESS);
|
||||
if (sendToGoogleDialog.getSendToMyMaps()) {
|
||||
if (!sendToGoogleDialog.getCreateNewMap()) {
|
||||
Intent listIntent = new Intent(this, MyMapsList.class);
|
||||
startActivityForResult(listIntent, MyTracksConstants.GET_MAP);
|
||||
} else {
|
||||
setProgressValue(0);
|
||||
setProgressMessage(
|
||||
R.string.progress_message_authenticating_mymaps);
|
||||
authenticate(new Intent(), MyTracksConstants.SEND_TO_GOOGLE,
|
||||
MyMapsConstants.MAPSHOP_SERVICE);
|
||||
}
|
||||
if (sendToGoogleDialog.getSendToFusionTables()) {
|
||||
setProgressValue(0);
|
||||
setProgressMessage(R.string.progress_message_authenticating_fusiontables);
|
||||
authenticate(new Intent(), MyTracksConstants.SEND_TO_GOOGLE,
|
||||
SendToFusionTables.SERVICE_ID);
|
||||
} else {
|
||||
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCS, RESULT_OK,
|
||||
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST, RESULT_OK,
|
||||
new Intent());
|
||||
}
|
||||
}
|
||||
@@ -1167,25 +1158,24 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that uploading to maps is finished.
|
||||
* Notifies that uploading to fusion tables is finished.
|
||||
*/
|
||||
private void handleMapsFinish() {
|
||||
if (shareRequested && sendToMyMapsSuccess) {
|
||||
private void handleFusionTablesFinish(long trackId) {
|
||||
if (shareRequested && sendToFusionTablesSuccess) {
|
||||
// Just share
|
||||
Toast.makeText(this, getMapsResultMessage(), Toast.LENGTH_LONG).show();
|
||||
shareLinkToMyMap(sendToMyMapsMapId);
|
||||
Toast.makeText(this, getFusionTablesResultMessage(), Toast.LENGTH_LONG).show();
|
||||
shareLinkToFusionTable(trackId);
|
||||
} else {
|
||||
dialogManager.showDialogSafely(
|
||||
DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT);
|
||||
dialogManager.showDialogSafely(DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMapsResultMessage() {
|
||||
public String getFusionTablesResultMessage() {
|
||||
StringBuilder message = new StringBuilder();
|
||||
SendToGoogleDialog sendToGoogleDialog =
|
||||
dialogManager.getSendToGoogleDialog();
|
||||
if (sendToGoogleDialog.getSendToMyMaps()) {
|
||||
message.append(sendToMyMapsMessage);
|
||||
if (sendToGoogleDialog.getSendToFusionTables()) {
|
||||
message.append(sendToFusionTablesMessage);
|
||||
}
|
||||
if (sendToGoogleDialog.getSendToDocs()) {
|
||||
if (message.length() > 0) {
|
||||
@@ -1193,10 +1183,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
message.append(sendToDocsMessage);
|
||||
}
|
||||
if (sendToMyMapsSuccess && sendToDocsSuccess) {
|
||||
message.append(' ');
|
||||
message.append(getString(R.string.status_mymap_info));
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
@@ -1216,12 +1202,12 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
return dialogManager;
|
||||
}
|
||||
|
||||
public String getSendToMyMapsMapId() {
|
||||
return sendToMyMapsMapId;
|
||||
public String getSendToFusionTablesTableId() {
|
||||
return sendToFusionTablesTableId;
|
||||
}
|
||||
|
||||
public boolean getSendToGoogleSuccess() {
|
||||
return sendToMyMapsSuccess && sendToDocsSuccess;
|
||||
return sendToFusionTablesSuccess && sendToDocsSuccess;
|
||||
}
|
||||
|
||||
// @VisibleForTesting
|
||||
|
||||
@@ -50,7 +50,7 @@ public abstract class MyTracksConstants {
|
||||
public static final int FEATURE_DETAILS = 5;
|
||||
public static final int START_RECORDING = 6;
|
||||
public static final int STOP_RECORDING = 7;
|
||||
public static final int AUTHENTICATE_TO_DOCS = 8;
|
||||
public static final int AUTHENTICATE_TO_DOCLIST = 8;
|
||||
public static final int AUTHENTICATE_TO_TRIX = 9;
|
||||
public static final int SEND_TO_DOCS = 10;
|
||||
public static final int DELETE_TRACK = 11;
|
||||
@@ -213,6 +213,9 @@ public abstract class MyTracksConstants {
|
||||
}
|
||||
}
|
||||
|
||||
public static final String MAPSHOP_BASE_URL =
|
||||
"http://maps.google.com/maps/ms";
|
||||
|
||||
/**
|
||||
* This is an abstract utility class.
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mymaps.GeoRect;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||
@@ -24,6 +23,7 @@ import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.GeoRect;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapActivity;
|
||||
@@ -789,7 +789,7 @@ public class MyTracksMap extends MapActivity
|
||||
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
|
||||
R.string.tracklist_share_track);
|
||||
share.add(0, MyTracksConstants.MENU_SHARE_LINK, 0,
|
||||
R.string.tracklist_share_link);
|
||||
R.string.tracklist_share_linkfusion);
|
||||
share.add(0, MyTracksConstants.MENU_SHARE_GPX_FILE, 0,
|
||||
R.string.tracklist_share_gpx_file);
|
||||
share.add(0, MyTracksConstants.MENU_SHARE_KML_FILE, 0,
|
||||
|
||||
@@ -29,7 +29,6 @@ import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
/**
|
||||
* A dialog where the user can choose where to send the tracks to, i.e.
|
||||
@@ -39,10 +38,7 @@ import android.widget.RadioGroup;
|
||||
*/
|
||||
public class SendToGoogleDialog extends Dialog {
|
||||
|
||||
private RadioGroup groupMyMaps;
|
||||
private RadioButton createNewMapRadioButton;
|
||||
private RadioButton pickMapRadioButton;
|
||||
private CheckBox sendToMyMapsCheckBox;
|
||||
private CheckBox sendToFusionTablesCheckBox;
|
||||
private CheckBox sendToDocsCheckBox;
|
||||
private RadioButton sendStatsRadioButton;
|
||||
private RadioButton sendStatsAndPointsRadioButton;
|
||||
@@ -76,26 +72,19 @@ public class SendToGoogleDialog extends Dialog {
|
||||
OnCheckedChangeListener checkBoxListener = new OnCheckedChangeListener() {
|
||||
public void onCheckedChanged(CompoundButton button, boolean checked) {
|
||||
sendButton.setEnabled(
|
||||
sendToMyMapsCheckBox.isChecked() ||
|
||||
sendToFusionTablesCheckBox.isChecked() ||
|
||||
sendToDocsCheckBox.isChecked());
|
||||
groupMyMaps.setVisibility(
|
||||
sendToMyMapsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
|
||||
//groupDocs.setVisibility(
|
||||
// sendToDocsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
};
|
||||
|
||||
sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
|
||||
groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
|
||||
sendToMyMapsCheckBox =
|
||||
(CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
|
||||
sendToMyMapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
sendToFusionTablesCheckBox =
|
||||
(CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
|
||||
sendToFusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
|
||||
sendToDocsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
createNewMapRadioButton =
|
||||
(RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
|
||||
pickMapRadioButton =
|
||||
(RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
|
||||
sendStatsRadioButton =
|
||||
(RadioButton) findViewById(R.id.sendtogoogle_send_stats);
|
||||
sendStatsAndPointsRadioButton = (RadioButton) findViewById(
|
||||
@@ -104,18 +93,12 @@ public class SendToGoogleDialog extends Dialog {
|
||||
SharedPreferences prefs =
|
||||
getContext().getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
if (prefs != null) {
|
||||
sendToMyMapsCheckBox.setChecked(
|
||||
sendToFusionTablesCheckBox.setChecked(
|
||||
prefs.getBoolean(
|
||||
getContext().getString(R.string.send_to_my_maps_key), true));
|
||||
sendToDocsCheckBox.setChecked(
|
||||
prefs.getBoolean(
|
||||
getContext().getString(R.string.send_to_docs_key), true));
|
||||
pickMapRadioButton.setChecked(
|
||||
prefs.getBoolean(
|
||||
getContext().getString(R.string.pick_existing_map_key), false));
|
||||
createNewMapRadioButton.setChecked(
|
||||
!prefs.getBoolean(
|
||||
getContext().getString(R.string.pick_existing_map_key), false));
|
||||
sendStatsAndPointsRadioButton.setChecked(
|
||||
prefs.getBoolean(
|
||||
getContext().getString(R.string.send_stats_and_points_key),
|
||||
@@ -136,13 +119,10 @@ public class SendToGoogleDialog extends Dialog {
|
||||
if (editor != null) {
|
||||
editor.putBoolean(
|
||||
getContext().getString(R.string.send_to_my_maps_key),
|
||||
sendToMyMapsCheckBox.isChecked());
|
||||
sendToFusionTablesCheckBox.isChecked());
|
||||
editor.putBoolean(
|
||||
getContext().getString(R.string.send_to_docs_key),
|
||||
sendToDocsCheckBox.isChecked());
|
||||
editor.putBoolean(
|
||||
getContext().getString(R.string.pick_existing_map_key),
|
||||
pickMapRadioButton.isChecked());
|
||||
editor.putBoolean(
|
||||
getContext().getString(R.string.send_stats_and_points_key),
|
||||
sendStatsAndPointsRadioButton.isChecked());
|
||||
@@ -152,18 +132,14 @@ public class SendToGoogleDialog extends Dialog {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
public boolean getSendToMyMaps() {
|
||||
return sendToMyMapsCheckBox.isChecked();
|
||||
public boolean getSendToFusionTables() {
|
||||
return sendToFusionTablesCheckBox.isChecked();
|
||||
}
|
||||
|
||||
public boolean getSendToDocs() {
|
||||
return sendToDocsCheckBox.isChecked();
|
||||
}
|
||||
|
||||
public boolean getCreateNewMap() {
|
||||
return createNewMapRadioButton.isChecked();
|
||||
}
|
||||
|
||||
public boolean getSendStatsAndPoints() {
|
||||
return sendStatsAndPointsRadioButton.isChecked();
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ public class StatsActivity extends Activity
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuItem currentSegment = menu.add(0,
|
||||
MyTracksConstants.MENU_CURRENT_SEGMENT, 0, R.string.current_segment);
|
||||
currentSegment.setIcon(R.drawable.menu_by_distance);
|
||||
currentSegment.setIcon(R.drawable.ic_menu_lastsegment);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,16 +15,13 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
@@ -84,16 +81,7 @@ public class WelcomeActivity extends Activity {
|
||||
builder.setIcon(R.drawable.arrow_icon);
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
// Get the version from the manifest.
|
||||
try {
|
||||
PackageInfo pi =
|
||||
getPackageManager().getPackageInfo("com.google.android.maps.mytracks",
|
||||
PackageManager.GET_META_DATA);
|
||||
((TextView) dialog.findViewById(R.id.about_version_register)).
|
||||
setText(pi.versionName);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
|
||||
}
|
||||
((TextView) dialog.findViewById(R.id.about_version_register)).
|
||||
setText(MyTracksUtils.getMyTracksVersion(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,8 @@ public class MyTracksProvider extends ContentProvider {
|
||||
+ TracksColumns.ELEVATIONGAIN + " FLOAT, "
|
||||
+ TracksColumns.MINGRADE + " FLOAT, "
|
||||
+ TracksColumns.MAXGRADE + " FLOAT, "
|
||||
+ TracksColumns.MAPID + " STRING);");
|
||||
+ TracksColumns.MAPID + " STRING, "
|
||||
+ TracksColumns.TABLEID + " STRING);");
|
||||
db.execSQL("CREATE TABLE " + WAYPOINTS_TABLE + " ("
|
||||
+ WaypointsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ WaypointsColumns.NAME + " STRING, "
|
||||
@@ -146,13 +147,15 @@ public class MyTracksProvider extends ContentProvider {
|
||||
} else {
|
||||
// Incremental updates go here.
|
||||
// Each time you increase the DB version, add a corresponding if clause.
|
||||
|
||||
|
||||
// Sensor data.
|
||||
if (oldVersion <= 17) {
|
||||
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
|
||||
+ newVersion + ", adding sensor column.");
|
||||
+ newVersion + ", adding sensor and tableid columns.");
|
||||
db.execSQL("ALTER TABLE " + TRACKPOINTS_TABLE
|
||||
+ " ADD " + TrackPointsColumns.SENSOR + " BLOB");
|
||||
db.execSQL("ALTER TABLE " + TRACKS_TABLE
|
||||
+ " ADD " + TracksColumns.TABLEID + " STRING");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.location.Location;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
@@ -58,8 +59,8 @@ public class GpxTrackWriter implements TrackFormatWriter {
|
||||
@Override
|
||||
public void writeHeader() {
|
||||
if (pw != null) {
|
||||
pw.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ");
|
||||
pw.println("standalone=\"yes\"?>");
|
||||
pw.format("<?xml version=\"1.0\" encoding=\"%s\" standalone=\"yes\"?>\n",
|
||||
Charset.defaultCharset().name());
|
||||
pw.println("<?xml-stylesheet type=\"text/xsl\" href=\"details.xsl\"?>");
|
||||
pw.println("<gpx");
|
||||
pw.println(" version=\"1.0\"");
|
||||
|
||||
@@ -15,32 +15,21 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io;
|
||||
|
||||
import com.google.android.apps.mymaps.MyMapsConstants;
|
||||
import com.google.android.apps.mytracks.MyTracks;
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.io.docs.DocsHelper;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
|
||||
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.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ResourceUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.docs.DocumentsClient;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient.WorksheetEntry;
|
||||
import com.google.wireless.gdata.maps.xml.XmlMapsGDataParserFactory;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -48,16 +37,7 @@ import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* A helper class used to transmit tracks statistics to Google Docs/Trix.
|
||||
@@ -65,29 +45,15 @@ import java.text.NumberFormat;
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SendToDocs {
|
||||
private static final NumberFormat LARGE_UNIT_FORMAT =
|
||||
new DecimalFormat("#,###,###.00");
|
||||
private static final NumberFormat SMALL_UNIT_FORMAT =
|
||||
new DecimalFormat("###,###");
|
||||
private static final String DOCS_FEED_URL =
|
||||
"http://docs.google.com/feeds/documents/private/full";
|
||||
private static final String DOCS_MY_SPREADSHEETS_FEED_URL =
|
||||
"http://docs.google.com/feeds/documents/private/full?"
|
||||
+ "category=mine,spreadsheet";
|
||||
private static final String DOCS_SPREADSHEET_URL =
|
||||
"http://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
|
||||
private static final String DOCS_SPREADSHEET_URL_FORMAT =
|
||||
"http://spreadsheets.google.com/feeds/list/%s/%s/private/full";
|
||||
protected static final String DOCS_WORKSHEETS_URL_FORMAT =
|
||||
"http://spreadsheets.google.com/feeds/worksheets/%s/private/full";
|
||||
private static final String CONTENT_TYPE_PARAM = "Content-Type";
|
||||
private static final String ATOM_FEED_MIME_TYPE = "application/atom+xml";
|
||||
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
|
||||
"application/x-vnd.oasis.opendocument.spreadsheet";
|
||||
|
||||
/** The GData service name for Google Spreadsheets (aka Trix) */
|
||||
public static final String GDATA_SERVICE_NAME_TRIX = "wise";
|
||||
|
||||
/** The GData service name for the Google Docs Document List */
|
||||
public static final String GDATA_SERVICE_NAME_DOCLIST = "writely";
|
||||
|
||||
private final Activity activity;
|
||||
private final AuthManager wiseAuth;
|
||||
private final AuthManager writelyAuth;
|
||||
private final AuthManager trixAuth;
|
||||
private final AuthManager docListAuth;
|
||||
private final long trackId;
|
||||
private final boolean metricUnits;
|
||||
private final HandlerThread handlerThread;
|
||||
@@ -95,18 +61,15 @@ public class SendToDocs {
|
||||
|
||||
private boolean createdNewSpreadSheet = false;
|
||||
|
||||
private String spreadSheetId = null;
|
||||
private String workSheetId = null;
|
||||
|
||||
private boolean success = true;
|
||||
private String statusMessage = "";
|
||||
private Runnable onCompletion = null;
|
||||
|
||||
public SendToDocs(Activity activity, AuthManager wiseAuth,
|
||||
AuthManager writelyAuth, long trackId) {
|
||||
public SendToDocs(Activity activity, AuthManager trixAuth,
|
||||
AuthManager docListAuth, long trackId) {
|
||||
this.activity = activity;
|
||||
this.wiseAuth = wiseAuth;
|
||||
this.writelyAuth = writelyAuth;
|
||||
this.trixAuth = trixAuth;
|
||||
this.docListAuth = docListAuth;
|
||||
this.trackId = trackId;
|
||||
|
||||
SharedPreferences preferences = activity.getSharedPreferences(
|
||||
@@ -196,22 +159,24 @@ public class SendToDocs {
|
||||
* @param track the track
|
||||
*/
|
||||
private boolean uploadToDocs(Track track) {
|
||||
GDataWrapper wiseWrapper = new GDataWrapper();
|
||||
wiseWrapper.setAuthManager(wiseAuth);
|
||||
wiseWrapper.setRetryOnAuthFailure(true);
|
||||
GDataWrapper<GDataServiceClient> docListWrapper = new GDataWrapper<GDataServiceClient>();
|
||||
docListWrapper.setAuthManager(docListAuth);
|
||||
docListWrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
GDataWrapper<GDataServiceClient> trixWrapper = new GDataWrapper<GDataServiceClient>();
|
||||
trixWrapper.setAuthManager(trixAuth);
|
||||
trixWrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
GDataWrapper writelyWrapper = new GDataWrapper();
|
||||
writelyWrapper.setAuthManager(writelyAuth);
|
||||
writelyWrapper.setRetryOnAuthFailure(true);
|
||||
DocsHelper docsHelper = new DocsHelper();
|
||||
|
||||
GDataClient androidClient = null;
|
||||
try {
|
||||
androidClient = GDataClientFactory.getGDataClient(activity);
|
||||
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
|
||||
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
wiseWrapper.setClient(gdataClient);
|
||||
trixWrapper.setClient(gdataClient);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"GData connection prepared: " + this.writelyAuth);
|
||||
"GData connection prepared: " + this.docListAuth);
|
||||
String sheetTitle = "My Tracks";
|
||||
|
||||
if (track.getCategory() != null && !track.getCategory().equals("")) {
|
||||
@@ -220,15 +185,19 @@ public class SendToDocs {
|
||||
|
||||
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
|
||||
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
writelyWrapper.setClient(docsGdataClient);
|
||||
docListWrapper.setClient(docsGdataClient);
|
||||
|
||||
// First try to find the spreadsheet:
|
||||
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
||||
Log.i(MyTracksConstants.TAG, "Spreadsheet lookup failed.");
|
||||
String spreadsheetId = null;
|
||||
try {
|
||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(MyTracksConstants.TAG, "Spreadsheet lookup failed.", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spreadSheetId == null) {
|
||||
if (spreadsheetId == null) {
|
||||
MyTracks.getInstance().setProgressValue(65);
|
||||
// Waiting a few seconds and trying again. Maybe the server just had a
|
||||
// hickup (unfortunately that happens quite a lot...).
|
||||
@@ -237,22 +206,33 @@ public class SendToDocs {
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Sleep interrupted", e);
|
||||
}
|
||||
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
||||
Log.i(MyTracksConstants.TAG, "2nd spreadsheet lookup failed.");
|
||||
|
||||
try {
|
||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(MyTracksConstants.TAG, "2nd spreadsheet lookup failed.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We were unable to find an existing spreadsheet, so create a new one.
|
||||
MyTracks.getInstance().setProgressValue(70);
|
||||
if (spreadSheetId == null) {
|
||||
if (spreadsheetId == null) {
|
||||
Log.i(MyTracksConstants.TAG, "Creating new spreadsheet: " + sheetTitle);
|
||||
|
||||
if (!createSpreadSheet(writelyWrapper, sheetTitle)) {
|
||||
try {
|
||||
spreadsheetId = docsHelper.createSpreadsheet(activity, docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(MyTracksConstants.TAG, "Failed to create new spreadsheet "
|
||||
+ sheetTitle, e);
|
||||
return false;
|
||||
}
|
||||
MyTracks.getInstance().setProgressValue(80);
|
||||
createdNewSpreadSheet = true;
|
||||
|
||||
if (spreadSheetId == null) {
|
||||
if (spreadsheetId == null) {
|
||||
MyTracks.getInstance().setProgressValue(85);
|
||||
// The previous creation might have succeeded even though GData
|
||||
// reported an error. Seems to be a know bug,
|
||||
@@ -265,10 +245,16 @@ public class SendToDocs {
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Sleep interrupted", e);
|
||||
}
|
||||
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
||||
|
||||
try {
|
||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(MyTracksConstants.TAG, "Failed create-failed lookup", e);
|
||||
return false;
|
||||
}
|
||||
if (spreadSheetId == null) {
|
||||
|
||||
if (spreadsheetId == null) {
|
||||
MyTracks.getInstance().setProgressValue(87);
|
||||
// Re-try
|
||||
try {
|
||||
@@ -276,11 +262,16 @@ public class SendToDocs {
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Sleep interrupted", e);
|
||||
}
|
||||
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
||||
|
||||
try {
|
||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(MyTracksConstants.TAG, "Failed create-failed relookup", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (spreadSheetId == null) {
|
||||
if (spreadsheetId == null) {
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"Creating new spreadsheet really failed.");
|
||||
return false;
|
||||
@@ -288,14 +279,21 @@ public class SendToDocs {
|
||||
}
|
||||
}
|
||||
|
||||
if (!getWorkSheetId(wiseWrapper)) {
|
||||
Log.i(MyTracksConstants.TAG, "Looking up worksheet id failed.");
|
||||
String worksheetId = null;
|
||||
try {
|
||||
worksheetId = docsHelper.getWorksheetId(trixWrapper, spreadsheetId);
|
||||
if (worksheetId == null) {
|
||||
throw new IOException("Worksheet ID lookup returned empty");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.i(MyTracksConstants.TAG, "Looking up worksheet id failed.", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
MyTracks.getInstance().setProgressValue(90);
|
||||
|
||||
insertRowNet(track, spreadSheetId, workSheetId);
|
||||
docsHelper.addTrackRow(activity, trixAuth, spreadsheetId, worksheetId,
|
||||
track, metricUnits);
|
||||
Log.i(MyTracksConstants.TAG, "Done uploading to docs.");
|
||||
} catch (IOException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Unable to upload docs.", e);
|
||||
@@ -307,223 +305,4 @@ public class SendToDocs {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean getSpreadsheetId(GDataWrapper wrapper, final String title) {
|
||||
spreadSheetId = null;
|
||||
return wrapper.runQuery(new QueryFunction() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client)
|
||||
throws IOException, ParseException, HttpException {
|
||||
GDataParser listParser;
|
||||
listParser = client.getParserForFeed(Entry.class,
|
||||
DOCS_MY_SPREADSHEETS_FEED_URL, writelyAuth.getAuthToken());
|
||||
listParser.init();
|
||||
while (listParser.hasMoreData()) {
|
||||
Entry entry = listParser.readNextEntry(null);
|
||||
String entryTitle = entry.getTitle();
|
||||
Log.i(MyTracksConstants.TAG, "Found docs entry: " + entryTitle);
|
||||
if (entryTitle.equals(title)) {
|
||||
String entryId = entry.getId();
|
||||
int lastSlash = entryId.lastIndexOf('/');
|
||||
spreadSheetId = entryId.substring(lastSlash + 15);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean getWorkSheetId(GDataWrapper wrapper) {
|
||||
workSheetId = null;
|
||||
return wrapper.runQuery(new QueryFunction() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client)
|
||||
throws AuthenticationException, IOException, ParseException {
|
||||
String uri = String.format(DOCS_WORKSHEETS_URL_FORMAT, spreadSheetId);
|
||||
GDataParser sheetParser =
|
||||
((SpreadsheetsClient) client).getParserForWorksheetsFeed(uri,
|
||||
wiseAuth.getAuthToken());
|
||||
sheetParser.init();
|
||||
if (!sheetParser.hasMoreData()) {
|
||||
Log.i(MyTracksConstants.TAG, "Found no worksheets");
|
||||
return; // failure
|
||||
}
|
||||
|
||||
// just grab the first.
|
||||
WorksheetEntry worksheetEntry =
|
||||
(WorksheetEntry) sheetParser.readNextEntry(new WorksheetEntry());
|
||||
|
||||
int lastSlash = worksheetEntry.getId().lastIndexOf('/');
|
||||
workSheetId = worksheetEntry.getId().substring(lastSlash + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MyTracks spreadsheet with the given name.
|
||||
*/
|
||||
private boolean createSpreadSheet(GDataWrapper writelyWrapper,
|
||||
final String name) {
|
||||
spreadSheetId = null;
|
||||
|
||||
return writelyWrapper.runQuery(new QueryFunction() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client) throws IOException {
|
||||
// Construct data
|
||||
// Send data
|
||||
URL url = new URL(DOCS_FEED_URL);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE_PARAM,
|
||||
OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
|
||||
conn.addRequestProperty("Slug", name);
|
||||
conn.addRequestProperty("Authorization",
|
||||
"GoogleLogin auth=" + writelyAuth.getAuthToken());
|
||||
conn.setDoOutput(true);
|
||||
OutputStream os = conn.getOutputStream();
|
||||
ResourceUtils.readBinaryFileToOutputStream(
|
||||
activity, R.raw.mytracks_empty_spreadsheet, os);
|
||||
|
||||
// Get the response
|
||||
// TODO: The following is a horrible ugly hack.
|
||||
// Hopefully we can retire it when there is a proper gdata api.
|
||||
BufferedReader rd = null;
|
||||
String line;
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
try {
|
||||
rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
while ((line = rd.readLine()) != null) {
|
||||
resultBuilder.append(line);
|
||||
}
|
||||
os.close();
|
||||
rd.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
// The GData API sometimes throws an error, even though creation of
|
||||
// the document succeeded. In that case let's just return. The caller
|
||||
// then needs to check if the doc actually exists.
|
||||
return;
|
||||
} finally {
|
||||
os.close();
|
||||
if (rd != null) {
|
||||
rd.close();
|
||||
}
|
||||
}
|
||||
|
||||
String result = resultBuilder.toString();
|
||||
// Try to find the id.
|
||||
int idTagIndex = result.indexOf("<id>");
|
||||
if (idTagIndex == -1) {
|
||||
return;
|
||||
}
|
||||
int idTagCloseIndex = result.indexOf("</id>", idTagIndex);
|
||||
if (idTagCloseIndex == -1) {
|
||||
return;
|
||||
}
|
||||
int idStringStart = result.indexOf(DOCS_SPREADSHEET_URL, idTagIndex);
|
||||
if (idStringStart == -1) {
|
||||
return;
|
||||
}
|
||||
spreadSheetId = result.substring(
|
||||
idStringStart + DOCS_SPREADSHEET_URL.length(), idTagCloseIndex);
|
||||
Log.i(MyTracksConstants.TAG, "Created new spreadsheet: " + spreadSheetId);
|
||||
}});
|
||||
}
|
||||
|
||||
private void insertRowNet(Track track, String spreadsheetId,
|
||||
String worksheetId) throws IOException {
|
||||
String worksheetUri = String.format(DOCS_SPREADSHEET_URL_FORMAT,
|
||||
spreadsheetId, worksheetId);
|
||||
TripStatistics stats = track.getStatistics();
|
||||
|
||||
/* Prepare the Post-Text we are going to send. */
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<entry xmlns='http://www.w3.org/2005/Atom' ");
|
||||
sb.append("xmlns:gsx='http://schemas.google.com/spreadsheets/"
|
||||
+ "2006/extended'>");
|
||||
appendTag("name", track.getName(), sb);
|
||||
appendTag("description", track.getDescription(), sb);
|
||||
appendTag("date", String.format("%tc", stats.getStartTime()), sb);
|
||||
appendTag("totaltime", StringUtils.formatTimeAlwaysShowingHours(
|
||||
stats.getTotalTime()), sb);
|
||||
appendTag("movingtime", StringUtils.formatTimeAlwaysShowingHours(
|
||||
stats.getMovingTime()), sb);
|
||||
appendLargeUnitsTag("distance", stats.getTotalDistance() / 1000, sb);
|
||||
appendTag("distanceunit",
|
||||
metricUnits
|
||||
? activity.getString(R.string.kilometer)
|
||||
: activity.getString(R.string.mile),
|
||||
sb);
|
||||
appendLargeUnitsTag("averagespeed", stats.getAverageSpeed() * 3.6, sb);
|
||||
appendLargeUnitsTag("averagemovingspeed",
|
||||
stats.getAverageMovingSpeed() * 3.6, sb);
|
||||
appendLargeUnitsTag("maxspeed", stats.getMaxSpeed() * 3.6, sb);
|
||||
appendTag("speedunit",
|
||||
metricUnits
|
||||
? activity.getString(R.string.kilometer_per_hour)
|
||||
: activity.getString(R.string.mile_per_hour),
|
||||
sb);
|
||||
appendSmallUnitsTag("elevationgain", stats.getTotalElevationGain(), sb);
|
||||
appendSmallUnitsTag("minelevation", stats.getMinElevation(), sb);
|
||||
appendSmallUnitsTag("maxelevation", stats.getMaxElevation(), sb);
|
||||
appendTag("elevationunit",
|
||||
metricUnits
|
||||
? activity.getString(R.string.meter)
|
||||
: activity.getString(R.string.feet),
|
||||
sb);
|
||||
if (track.getMapId().length() > 0) {
|
||||
appendTag("map", MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid="
|
||||
+ track.getMapId(), sb);
|
||||
}
|
||||
sb.append("</entry>");
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"Inserting at: " + spreadsheetId + " => " + worksheetUri);
|
||||
|
||||
String postText = sb.toString();
|
||||
Log.i(MyTracksConstants.TAG, postText);
|
||||
|
||||
// Send data
|
||||
// No need for a wrapper because we know that the authorization was good
|
||||
// enough to get this far
|
||||
URL url = new URL(worksheetUri);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE_PARAM, ATOM_FEED_MIME_TYPE);
|
||||
conn.addRequestProperty("Authorization",
|
||||
"GoogleLogin auth=" + wiseAuth.getAuthToken());
|
||||
conn.setDoOutput(true);
|
||||
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
|
||||
wr.write(postText);
|
||||
wr.flush();
|
||||
|
||||
// Get the response
|
||||
BufferedReader rd =
|
||||
new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line;
|
||||
while ((line = rd.readLine()) != null) {
|
||||
// Process line...
|
||||
Log.i(MyTracksConstants.TAG, "r: " + line);
|
||||
}
|
||||
wr.close();
|
||||
rd.close();
|
||||
|
||||
Log.i(MyTracksConstants.TAG, "Post finished.");
|
||||
}
|
||||
|
||||
private void appendTag(String name, String value, StringBuilder sb) {
|
||||
sb.append("<gsx:");
|
||||
sb.append(name);
|
||||
sb.append('>');
|
||||
sb.append(StringUtils.stringAsCData(value));
|
||||
sb.append("</gsx:");
|
||||
sb.append(name);
|
||||
sb.append('>');
|
||||
}
|
||||
|
||||
private void appendLargeUnitsTag(String name, double d, StringBuilder sb) {
|
||||
double value = metricUnits ? d : (d * UnitConversions.KM_TO_MI);
|
||||
appendTag(name, LARGE_UNIT_FORMAT.format(value), sb);
|
||||
}
|
||||
|
||||
private void appendSmallUnitsTag(String name, double d, StringBuilder sb) {
|
||||
double value = metricUnits ? d : (d * UnitConversions.M_TO_FT);
|
||||
appendTag(name, SMALL_UNIT_FORMAT.format(value), sb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
* 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 com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
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.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
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.GoogleTransport;
|
||||
import com.google.api.client.http.GenericUrl;
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
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.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.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 {
|
||||
|
||||
/**
|
||||
* Listener invoked when sending to fusion tables completes.
|
||||
*/
|
||||
public interface OnSendCompletedListener {
|
||||
void onSendCompleted(String tableId, boolean success, int statusMessage);
|
||||
}
|
||||
|
||||
/** 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 =
|
||||
"http://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 =
|
||||
"http://www.google.com/fusiontables/api/query";
|
||||
|
||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
||||
|
||||
private static final String GDATA_VERSION = "2";
|
||||
|
||||
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 HttpTransport transport;
|
||||
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);
|
||||
transport = GoogleTransport.create();
|
||||
GoogleHeaders headers = (GoogleHeaders) transport.defaultHeaders;
|
||||
headers.setApplicationName("Google-MyTracks-" + MyTracksUtils.getMyTracksVersion(context));
|
||||
headers.gdataVersion = GDATA_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(MyTracksConstants.TAG, "Sending to Fusion tables: trackId = " + trackId);
|
||||
doUpload();
|
||||
}
|
||||
|
||||
public static String getMapVisualizationUrl(Track track) {
|
||||
// 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;
|
||||
return String.format(FUSIONTABLES_MAP, track.getTableId(), latE6 / 1.E6, lonE6 / 1.E6, z);
|
||||
}
|
||||
|
||||
private void doUpload() {
|
||||
((GoogleHeaders) transport.defaultHeaders).setGoogleLogin(auth.getAuthToken());
|
||||
int statusMessageId = R.string.error_sending_to_fusiontables;
|
||||
boolean success = true;
|
||||
try {
|
||||
progressIndicator.setProgressValue(1);
|
||||
progressIndicator.setProgressMessage(R.string.progress_message_reading_track);
|
||||
|
||||
// Get the track meta-data
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
String originalDescription = track.getDescription();
|
||||
|
||||
// Create a new table:
|
||||
progressIndicator.setProgressMessage(R.string.progress_message_creating_fusiontable);
|
||||
success = createNewTable(track);
|
||||
|
||||
if (success) {
|
||||
success = makeTableUnlisted(tableId);
|
||||
}
|
||||
|
||||
// Upload all of the segments of the track plus start/end markers
|
||||
if (success) {
|
||||
success = uploadAllTrackPoints(track, originalDescription);
|
||||
}
|
||||
|
||||
// Upload all the waypoints.
|
||||
if (success) {
|
||||
success = uploadWaypoints(track);
|
||||
statusMessageId = R.string.status_new_fusiontable_has_been_created;
|
||||
}
|
||||
Log.d(MyTracksConstants.TAG, "SendToFusionTables: Done: " + success);
|
||||
progressIndicator.setProgressValue(100);
|
||||
} finally {
|
||||
|
||||
final boolean finalSuccess = success;
|
||||
final int finalStatusMessageId = statusMessageId;
|
||||
context.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (onCompletion != null) {
|
||||
onCompletion.onSendCompleted(
|
||||
tableId, finalSuccess, finalStatusMessageId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new table.
|
||||
* If successful sets {@link #tableId}.
|
||||
*
|
||||
* @return true in case of success.
|
||||
*/
|
||||
private boolean createNewTable(Track track) {
|
||||
Log.d(MyTracksConstants.TAG, "Creating a new fusion table.");
|
||||
String query = "CREATE TABLE '" + track.getName() +
|
||||
"' (name:STRING,description:STRING,geometry:LOCATION,marker:STRING)";
|
||||
return runUpdate(query);
|
||||
}
|
||||
|
||||
private boolean makeTableUnlisted(String tableId) {
|
||||
Log.d(MyTracksConstants.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(values[i].replaceAll("'", "''"));
|
||||
builder.append('\'');
|
||||
}
|
||||
builder.append(')');
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new row representing a marker.
|
||||
*
|
||||
* @param name the marker name
|
||||
* @param description the marker description
|
||||
* @param the marker location
|
||||
* @return true in case of success.
|
||||
*/
|
||||
private boolean createNewPoint(String name, String description, Location location,
|
||||
String marker) {
|
||||
Log.d(MyTracksConstants.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(MyTracksConstants.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(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
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.moveToFirst()) {
|
||||
Log.w(MyTracksConstants.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(MyTracksConstants.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(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
|
||||
|
||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
||||
progressIndicator.setProgressMessage(R.string.progress_message_reading_track);
|
||||
Location lastLocation = null;
|
||||
do {
|
||||
if (totalLocationsRead % 100 == 0) {
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
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.start);
|
||||
createNewPoint(name, "", loc, MARKER_TYPE_START);
|
||||
}
|
||||
|
||||
// Add to the elevation profile.
|
||||
if (loc != null && MyTracksUtils.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.end);
|
||||
return createNewPoint(name, track.getDescription(), lastLocation, MARKER_TYPE_END);
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
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 Point tag for the given location.
|
||||
*
|
||||
* @param location The location.
|
||||
* @return the kml.
|
||||
*/
|
||||
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) {
|
||||
progressIndicator.setProgressMessage(R.string.progress_message_preparing_track);
|
||||
updateProgress();
|
||||
|
||||
int numLocations = locations.size();
|
||||
if (numLocations < 2) {
|
||||
Log.d(MyTracksConstants.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
|
||||
progressIndicator.setProgressMessage(R.string.progress_message_sending_fusiontables);
|
||||
for (Track splitTrack : splitTracks) {
|
||||
if (totalSegmentsUploaded > 1) {
|
||||
splitTrack.setName(splitTrack.getName() + " "
|
||||
+ String.format(
|
||||
context.getString(R.string.part), totalSegmentsUploaded));
|
||||
}
|
||||
totalSegmentsUploaded++;
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToFusionTables: Prepared feature for upload w/ "
|
||||
+ splitTrack.getLocations().size() + " points.");
|
||||
|
||||
// Transmit tracks via GData feed:
|
||||
// -------------------------------
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToFusionTables: Uploading to table " + tableId + " w/ auth " + auth);
|
||||
if (!uploadTrackPoints(splitTrack)) {
|
||||
Log.e(MyTracksConstants.TAG, "Uploading failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
locations.clear();
|
||||
totalLocationsUploaded += numLocations;
|
||||
updateProgress();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a buffer of locations for transmission to google fusion tables.
|
||||
*
|
||||
* @param track the original track with meta data
|
||||
* @param buffer a buffer of 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) {
|
||||
updateProgress();
|
||||
}
|
||||
if (loc.getLatitude() > 90) {
|
||||
startNewTrackSegment = true;
|
||||
}
|
||||
|
||||
if (startNewTrackSegment) {
|
||||
// Close up the last segment.
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
Log.d(MyTracksConstants.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:
|
||||
*/
|
||||
MyTracksUtils.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(MyTracksUtils.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(MyTracksConstants.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;
|
||||
c = providerUtils.getWaypointsCursor(
|
||||
track.getId(), 0,
|
||||
MyTracksConstants.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(MyTracksConstants.TAG, "SendToFusionTables: Creating waypoint.");
|
||||
success = createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(),
|
||||
MARKER_TYPE_WAYPOINT);
|
||||
if (!success) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
Log.w(MyTracksConstants.TAG, "SendToFusionTables: upload waypoints failed.");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current upload progress.
|
||||
*/
|
||||
private void updateProgress() {
|
||||
// The percent of the total that represents the completed part of this
|
||||
// segment.
|
||||
int totalPercentage =
|
||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
||||
/ (totalLocations * 3);
|
||||
totalPercentage = Math.min(99, totalPercentage);
|
||||
progressIndicator.setProgressValue(totalPercentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<HttpTransport> wrapper = new GDataWrapper<HttpTransport>();
|
||||
wrapper.setAuthManager(auth);
|
||||
wrapper.setRetryOnAuthFailure(true);
|
||||
wrapper.setClient(transport);
|
||||
Log.d(MyTracksConstants.TAG, "GData connection prepared: " + this.auth);
|
||||
wrapper.runQuery(new QueryFunction<HttpTransport>() {
|
||||
@Override
|
||||
public void query(HttpTransport client)
|
||||
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException,
|
||||
GDataWrapper.AuthenticationException {
|
||||
HttpRequest request = transport.buildPostRequest();
|
||||
request.headers.contentType = "application/x-www-form-urlencoded";
|
||||
GenericUrl url = new GenericUrl(FUSIONTABLES_BASE_FEED_URL);
|
||||
request.url = url;
|
||||
InputStreamContent isc = new InputStreamContent();
|
||||
String sql = "sql=" + URLEncoder.encode(query, "UTF-8");
|
||||
isc.inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
|
||||
request.content = isc;
|
||||
Log.d(MyTracksConstants.TAG, "Running update query " + url.toString() + ": " + sql);
|
||||
try {
|
||||
HttpResponse response = request.execute();
|
||||
boolean success = response.isSuccessStatusCode;
|
||||
if (success) {
|
||||
byte[] result = new byte[1024];
|
||||
response.getContent().read(result);
|
||||
String s = Strings.fromBytesUtf8(result);
|
||||
String[] lines = s.split(Strings.LINE_SEPARATOR);
|
||||
if (lines[0].equals("tableid")) {
|
||||
tableId = lines[1];
|
||||
Log.d(MyTracksConstants.TAG, "tableId = " + tableId);
|
||||
} else {
|
||||
Log.w(MyTracksConstants.TAG, "Unrecognized response: " + lines[0]);
|
||||
}
|
||||
} else {
|
||||
Log.d(MyTracksConstants.TAG, "Query failed: " + response.statusMessage + " (" +
|
||||
response.statusCode + ")");
|
||||
throw new GDataWrapper.HttpException(response.statusCode, response.statusMessage);
|
||||
}
|
||||
} catch (HttpResponseException e) {
|
||||
if (e.response.statusCode == 401) {
|
||||
throw new GDataWrapper.AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
return wrapper.getErrorType() == GDataWrapper.ERROR_NO_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -1,689 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 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 com.google.android.apps.mymaps.MyMapsFeature;
|
||||
import com.google.android.apps.mymaps.MyMapsGDataConverter;
|
||||
import com.google.android.apps.mymaps.MyMapsGDataWrapper;
|
||||
import com.google.android.apps.mymaps.MyMapsGDataWrapper.QueryFunction;
|
||||
import com.google.android.apps.mymaps.MyMapsMapMetadata;
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
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.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.maps.MapsClient;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
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.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* A helper class used to transmit tracks to Google MyMaps.
|
||||
* A new instance should be used for each upload.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class SendToMyMaps implements Runnable {
|
||||
|
||||
public static final String NEW_MAP_ID = "new";
|
||||
private static final String END_ICON_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
||||
private static final String START_ICON_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
||||
|
||||
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;
|
||||
private String mapId;
|
||||
|
||||
// GData service
|
||||
private MyMapsGDataWrapper wrapper;
|
||||
private MyMapsGDataConverter gdataConverter;
|
||||
|
||||
// Progress status
|
||||
private int totalLocationsRead;
|
||||
private int totalLocationsPrepared;
|
||||
private int totalLocationsUploaded;
|
||||
private int totalLocations;
|
||||
private int totalSegmentsUploaded;
|
||||
|
||||
public interface OnSendCompletedListener {
|
||||
void onSendCompleted(String mapId, boolean success, int statusMessage);
|
||||
}
|
||||
|
||||
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
||||
long trackId, ProgressIndicator progressIndicator,
|
||||
OnSendCompletedListener onCompletion) {
|
||||
this.context = context;
|
||||
this.mapId = mapId;
|
||||
this.auth = auth;
|
||||
this.trackId = trackId;
|
||||
this.progressIndicator = progressIndicator;
|
||||
this.onCompletion = onCompletion;
|
||||
this.stringUtils = new StringUtils(context);
|
||||
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(MyTracksConstants.TAG, "Sending to MyMaps: trackId = " + trackId);
|
||||
doUpload();
|
||||
}
|
||||
|
||||
private void doUpload() {
|
||||
int statusMessageId = R.string.error_sending_to_mymap;
|
||||
boolean success = true;
|
||||
try {
|
||||
gdataConverter = new MyMapsGDataConverter();
|
||||
|
||||
progressIndicator.setProgressValue(1);
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_reading_track);
|
||||
|
||||
// Get the track meta-data
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
String originalDescription = track.getDescription();
|
||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
||||
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
||||
wrapper = new MyMapsGDataWrapper(context);
|
||||
wrapper.setAuthManager(auth);
|
||||
wrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
// Create a new map if necessary:
|
||||
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
||||
if (isNewMap) {
|
||||
success = createNewMap(track, originalDescription);
|
||||
}
|
||||
|
||||
// Upload all of the segments of the track plus start/end markers
|
||||
if (success) {
|
||||
success = uploadAllTrackPoints(track, originalDescription);
|
||||
}
|
||||
|
||||
// Put waypoints.
|
||||
if (success) {
|
||||
success = uploadWaypoints(track);
|
||||
if (!success) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: upload waypoints failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
statusMessageId = isNewMap
|
||||
? R.string.status_new_mymap_has_been_created
|
||||
: R.string.status_tracks_have_been_uploaded;
|
||||
}
|
||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Done: " + success);
|
||||
progressIndicator.setProgressValue(100);
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
||||
} finally {
|
||||
if (wrapper != null) {
|
||||
wrapper.cleanUp();
|
||||
}
|
||||
|
||||
final boolean finalSuccess = success;
|
||||
final int finalStatusMessageId = statusMessageId;
|
||||
context.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (onCompletion != null) {
|
||||
onCompletion.onSendCompleted(
|
||||
mapId, finalSuccess, finalStatusMessageId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new map for the given track.
|
||||
*
|
||||
* @param track The track that will be uploaded to this map
|
||||
* @return True on success.
|
||||
*/
|
||||
private boolean createNewMap(final Track track, final String description) {
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_creating_map);
|
||||
return wrapper.runQuery(new QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client) throws IOException, Exception {
|
||||
Log.d(MyTracksConstants.TAG, "Creating a new map.");
|
||||
String mapFeed = MapsClient.getMapsFeed();
|
||||
Log.d(MyTracksConstants.TAG, "Map feed is " + mapFeed);
|
||||
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
||||
metaData.setTitle(track.getName());
|
||||
metaData.setDescription(description + " - "
|
||||
+ track.getCategory() + " - "
|
||||
+ context.getString(R.string.new_map_description));
|
||||
SharedPreferences preferences = context.getSharedPreferences(
|
||||
MyTracksSettings.SETTINGS_NAME, 0);
|
||||
boolean mapPublic = true;
|
||||
if (preferences != null) {
|
||||
mapPublic = preferences.getBoolean(
|
||||
context.getString(R.string.default_map_public_key), true);
|
||||
}
|
||||
metaData.setSearchable(mapPublic);
|
||||
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
||||
Log.d(MyTracksConstants.TAG, "Title: " + entry.getTitle());
|
||||
Entry map = client.createEntry(mapFeed, auth.getAuthToken(), entry);
|
||||
mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
|
||||
Log.d(MyTracksConstants.TAG, "New map id is: " + mapId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean uploadAllTrackPoints(
|
||||
final Track track, String originalDescription) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(
|
||||
MyTracksSettings.SETTINGS_NAME, 0);
|
||||
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.moveToFirst()) {
|
||||
Log.w(MyTracksConstants.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(MyTracksConstants.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(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
|
||||
|
||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_reading_track);
|
||||
Location lastLocation = null;
|
||||
do {
|
||||
if (totalLocationsRead % 100 == 0) {
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
Location loc = providerUtils.createLocation(locationsCursor);
|
||||
locations.add(loc);
|
||||
|
||||
if (totalLocationsRead == 0) {
|
||||
// Put a marker at the first point of the first valid segment:
|
||||
uploadMarker(track, loc, true);
|
||||
}
|
||||
|
||||
// Add to the elevation profile.
|
||||
if (loc != null && MyTracksUtils.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>");
|
||||
return uploadMarker(track, lastLocation, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
locationsCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean uploadMarker(final Track track,
|
||||
final Location location,
|
||||
final boolean isStart) {
|
||||
boolean okay = wrapper.runQuery(new QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client)
|
||||
throws AuthenticationException, IOException, Exception {
|
||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||
insertMarker(context, client, featureFeed, track, location, isStart);
|
||||
}
|
||||
});
|
||||
return okay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
||||
* 2nd failure.
|
||||
*/
|
||||
private void insertMarker(Context context, MapsClient client,
|
||||
String featureFeed, Track track, Location loc,
|
||||
boolean isStart) throws IOException, Exception {
|
||||
Entry entry = gdataConverter.getEntryForFeature(
|
||||
buildMyMapsPlacemarkFeature(context, track, loc, isStart));
|
||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Creating placemark "
|
||||
+ entry.getTitle());
|
||||
try {
|
||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||
// Retry once (often IOException is thrown on a timeout):
|
||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a placemark MyMapsFeature from a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @param isStart true if it's the start of the track, or false for end
|
||||
* @return a MyMapsFeature
|
||||
*/
|
||||
private static MyMapsFeature buildMyMapsPlacemarkFeature(Context context,
|
||||
Track track, Location loc, boolean isStart) {
|
||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||
myMapsFeature.generateAndroidId();
|
||||
myMapsFeature.setType(MyMapsFeature.MARKER);
|
||||
if (isStart) {
|
||||
myMapsFeature.setIconUrl(START_ICON_URL);
|
||||
} else {
|
||||
myMapsFeature.setIconUrl(END_ICON_URL);
|
||||
}
|
||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
||||
String name = track.getName() + " "
|
||||
+ (isStart ? context.getString(R.string.start)
|
||||
: context.getString(R.string.end));
|
||||
myMapsFeature.setTitle(name);
|
||||
myMapsFeature.setDescription(isStart ? "" : track.getDescription());
|
||||
return myMapsFeature;
|
||||
}
|
||||
|
||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_preparing_track);
|
||||
updateProgress();
|
||||
|
||||
int numLocations = locations.size();
|
||||
if (numLocations < 2) {
|
||||
Log.d(MyTracksConstants.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
|
||||
progressIndicator.setProgressMessage(
|
||||
R.string.progress_message_sending_mymaps);
|
||||
for (Track splitTrack : splitTracks) {
|
||||
if (totalSegmentsUploaded > 1) {
|
||||
splitTrack.setName(splitTrack.getName() + " "
|
||||
+ String.format(
|
||||
context.getString(R.string.part), totalSegmentsUploaded));
|
||||
}
|
||||
totalSegmentsUploaded++;
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: Prepared feature for upload w/ "
|
||||
+ splitTrack.getLocations().size() + " points.");
|
||||
|
||||
// Transmit tracks via GData feed:
|
||||
// -------------------------------
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
||||
if (!uploadTrackPoints(splitTrack)) {
|
||||
Log.e(MyTracksConstants.TAG, "Uploading failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
locations.clear();
|
||||
totalLocationsUploaded += numLocations;
|
||||
updateProgress();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a buffer of locations for transmission to google maps.
|
||||
*
|
||||
* @param track the original track with meta data
|
||||
* @param buffer a buffer of 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) {
|
||||
updateProgress();
|
||||
}
|
||||
if (loc.getLatitude() > 90) {
|
||||
startNewTrackSegment = true;
|
||||
}
|
||||
|
||||
if (startNewTrackSegment) {
|
||||
// Close up the last segment.
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"MyTracksSendToMyMaps: 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 maps.
|
||||
* 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. Mapshop doesn't like too many
|
||||
* points:
|
||||
*/
|
||||
MyTracksUtils.decimate(segment, 2.0);
|
||||
|
||||
/* It the track still has > 500 points, split it in pieces: */
|
||||
if (segment.getLocations().size() > 500) {
|
||||
splitTracks.addAll(MyTracksUtils.split(segment, 500));
|
||||
} else if (segment.getLocations().size() >= 2) {
|
||||
splitTracks.add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a given list of tracks to Google MyMaps using the maps GData feed.
|
||||
*/
|
||||
private boolean uploadTrackPoints(final Track track) {
|
||||
return wrapper.runQuery(new QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client)
|
||||
throws AuthenticationException, IOException, Exception {
|
||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||
Log.d(MyTracksConstants.TAG, "Feature feed url: " + featureFeed);
|
||||
uploadTrackPoints(track, client, featureFeed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean uploadTrackPoints(Track splitTrack,
|
||||
MapsClient client,
|
||||
String featureFeed)
|
||||
throws IOException, Exception {
|
||||
Entry entry = null;
|
||||
int numLocations = splitTrack.getLocations().size();
|
||||
if (numLocations < 2) {
|
||||
// Need at least two points for a polyline:
|
||||
Log.w(MyTracksConstants.TAG, "Not uploading too few points");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Put the line:
|
||||
entry = gdataConverter.getEntryForFeature(
|
||||
buildMyMapsLineFeature(splitTrack));
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: Creating line " + entry.getTitle());
|
||||
try {
|
||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||
// Retry once (often IOException is thrown on a timeout):
|
||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a MyMapsFeature from a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @return a MyMapsFeature
|
||||
*/
|
||||
private static MyMapsFeature buildMyMapsLineFeature(Track track) {
|
||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||
myMapsFeature.generateAndroidId();
|
||||
myMapsFeature.setType(MyMapsFeature.LINE);
|
||||
if (track.getName().length() < 1) {
|
||||
// Features must have a name (otherwise GData upload may fail):
|
||||
myMapsFeature.setTitle("-");
|
||||
} else {
|
||||
myMapsFeature.setTitle(track.getName());
|
||||
}
|
||||
myMapsFeature.setColor(0x80FF0000);
|
||||
for (Location loc : track.getLocations()) {
|
||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
||||
}
|
||||
return myMapsFeature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads all of the waypoints associated with this track to map.
|
||||
*
|
||||
* @param track The track to upload waypoints for.
|
||||
*
|
||||
* @return True on success.
|
||||
*/
|
||||
private boolean uploadWaypoints(final Track track) {
|
||||
return wrapper.runQuery(new QueryFunction() {
|
||||
public void query(MapsClient client) {
|
||||
// TODO: Stream through he 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.
|
||||
Cursor c = null;
|
||||
c = providerUtils.getWaypointsCursor(
|
||||
track.getId(), 0,
|
||||
MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||
if (c != null) {
|
||||
try {
|
||||
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);
|
||||
Entry entry = gdataConverter.getEntryForFeature(
|
||||
buildMyMapsPlacemarkFeature(wpt));
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: Creating waypoint.");
|
||||
try {
|
||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Retrying.");
|
||||
|
||||
// Retry once (often IOException is thrown on a timeout):
|
||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SendToMyMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
Log.w(MyTracksConstants.TAG, "ParseException caught.", e);
|
||||
} catch (HttpException e) {
|
||||
Log.w(MyTracksConstants.TAG, "HttpException caught.", e);
|
||||
} catch (IOException e) {
|
||||
Log.w(MyTracksConstants.TAG, "IOException caught.", e);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a MyMapsFeature from a track.
|
||||
*
|
||||
* @param wpt the waypoint
|
||||
* @return a MyMapsFeature
|
||||
*/
|
||||
private static MyMapsFeature buildMyMapsPlacemarkFeature(Waypoint wpt) {
|
||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||
myMapsFeature.generateAndroidId();
|
||||
myMapsFeature.setType(MyMapsFeature.MARKER);
|
||||
myMapsFeature.setIconUrl(wpt.getIcon());
|
||||
myMapsFeature.setDescription(wpt.getDescription());
|
||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(wpt.getLocation()));
|
||||
if (wpt.getName().length() < 1) {
|
||||
// Features must have a name (otherwise GData upload may fail):
|
||||
myMapsFeature.setTitle("-");
|
||||
} else {
|
||||
myMapsFeature.setTitle(wpt.getName());
|
||||
}
|
||||
myMapsFeature.setDescription(wpt.getDescription().replaceAll("\n", "<br>"));
|
||||
return myMapsFeature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current upload progress.
|
||||
*/
|
||||
private void updateProgress() {
|
||||
// The percent of the total that represents the completed part of this
|
||||
// segment.
|
||||
int totalPercentage =
|
||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
||||
/ (totalLocations * 3);
|
||||
totalPercentage = Math.min(99, totalPercentage);
|
||||
progressIndicator.setProgressValue(totalPercentage);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import android.os.Build;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
@@ -77,7 +78,8 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
if (pw == null) {
|
||||
return;
|
||||
}
|
||||
pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>");
|
||||
pw.format("<?xml version=\"1.0\" encoding=\"%s\" standalone=\"no\" ?>\n",
|
||||
Charset.defaultCharset().name());
|
||||
pw.print("<TrainingCenterDatabase ");
|
||||
pw.print("xmlns=\"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2\" ");
|
||||
pw.print("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
|
||||
|
||||
@@ -37,13 +37,15 @@ public class BackupColumns {
|
||||
TracksColumns.MOVINGTIME, TracksColumns.AVGSPEED, TracksColumns.AVGMOVINGSPEED,
|
||||
TracksColumns.MAXSPEED, TracksColumns.MINELEVATION, TracksColumns.MAXELEVATION,
|
||||
TracksColumns.ELEVATIONGAIN, TracksColumns.MINGRADE, TracksColumns.MAXGRADE,
|
||||
TracksColumns.MINLAT, TracksColumns.MAXLAT, TracksColumns.MINLON, TracksColumns.MAXLON };
|
||||
TracksColumns.MINLAT, TracksColumns.MAXLAT, TracksColumns.MINLON, TracksColumns.MAXLON,
|
||||
TracksColumns.MAPID, TracksColumns.TABLEID};
|
||||
public static final byte[] TRACKS_BACKUP_COLUMN_TYPES = {
|
||||
LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
|
||||
LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID,
|
||||
FLOAT_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
|
||||
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
|
||||
FLOAT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID };
|
||||
FLOAT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, STRING_TYPE_ID,
|
||||
STRING_TYPE_ID};
|
||||
|
||||
public static final String[] WAYPOINTS_BACKUP_COLUMNS = {
|
||||
WaypointsColumns._ID, WaypointsColumns.TRACKID, WaypointsColumns.NAME,
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* 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.docs;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
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.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ResourceUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient.WorksheetEntry;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* This class contains helper methods for interacting with Google Docs and
|
||||
* Google Spreadsheets.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public class DocsHelper {
|
||||
private static final String DOCS_FEED_URL =
|
||||
"http://docs.google.com/feeds/documents/private/full";
|
||||
private static final String DOCS_SPREADSHEET_URL =
|
||||
"http://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
|
||||
private static final String DOCS_WORKSHEETS_URL_FORMAT =
|
||||
"http://spreadsheets.google.com/feeds/worksheets/%s/private/full";
|
||||
private static final String DOCS_MY_SPREADSHEETS_FEED_URL =
|
||||
"http://docs.google.com/feeds/documents/private/full?category=mine,spreadsheet";
|
||||
private static final String DOCS_SPREADSHEET_URL_FORMAT =
|
||||
"http://spreadsheets.google.com/feeds/list/%s/%s/private/full";
|
||||
|
||||
private static final String CONTENT_TYPE_PARAM = "Content-Type";
|
||||
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
|
||||
"application/x-vnd.oasis.opendocument.spreadsheet";
|
||||
private static final String ATOM_FEED_MIME_TYPE = "application/atom+xml";
|
||||
|
||||
/**
|
||||
* Creates a new MyTracks spreadsheet with the given name.
|
||||
*
|
||||
* @param context The context associated with this request.
|
||||
* @param docListWrapper The GData handle for the Document List service.
|
||||
* @param name The name for the newly-created spreadsheet.
|
||||
* @return The spreadsheet ID, if one is created. {@code null} will be
|
||||
* returned if a GData error didn't occur, but no spreadsheet ID was
|
||||
* returned.
|
||||
*/
|
||||
public String createSpreadsheet(final Context context,
|
||||
final GDataWrapper<GDataServiceClient> docListWrapper, final String name) throws IOException {
|
||||
final AtomicReference<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
boolean result = docListWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client) throws IOException,
|
||||
GDataWrapper.AuthenticationException {
|
||||
// Construct and send request
|
||||
URL url = new URL(DOCS_FEED_URL);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE_PARAM,
|
||||
OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
|
||||
conn.addRequestProperty("Slug", name);
|
||||
conn.addRequestProperty("Authorization",
|
||||
"GoogleLogin auth=" +
|
||||
docListWrapper.getAuthManager().getAuthToken());
|
||||
conn.setDoOutput(true);
|
||||
OutputStream os = conn.getOutputStream();
|
||||
ResourceUtils.readBinaryFileToOutputStream(
|
||||
context, R.raw.mytracks_empty_spreadsheet, os);
|
||||
|
||||
// Get the response
|
||||
// TODO: The following is a horrible ugly hack.
|
||||
// Hopefully we can retire it when there is a proper gdata api.
|
||||
BufferedReader rd = null;
|
||||
String line;
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
try {
|
||||
rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
while ((line = rd.readLine()) != null) {
|
||||
resultBuilder.append(line);
|
||||
}
|
||||
os.close();
|
||||
rd.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
// The GData API sometimes throws an error, even though creation of
|
||||
// the document succeeded. In that case let's just return. The caller
|
||||
// then needs to check if the doc actually exists.
|
||||
return;
|
||||
} finally {
|
||||
os.close();
|
||||
if (rd != null) {
|
||||
rd.close();
|
||||
}
|
||||
}
|
||||
|
||||
String result = resultBuilder.toString();
|
||||
// Try to find the id.
|
||||
int idTagIndex = result.indexOf("<id>");
|
||||
if (idTagIndex == -1) {
|
||||
return;
|
||||
}
|
||||
int idTagCloseIndex = result.indexOf("</id>", idTagIndex);
|
||||
if (idTagCloseIndex == -1) {
|
||||
return;
|
||||
}
|
||||
int idStringStart = result.indexOf(DOCS_SPREADSHEET_URL, idTagIndex);
|
||||
if (idStringStart == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
String id = result.substring(
|
||||
idStringStart + DOCS_SPREADSHEET_URL.length(), idTagCloseIndex);
|
||||
Log.i(MyTracksConstants.TAG, "Created new spreadsheet: " + id);
|
||||
idSaver.set(id);
|
||||
}});
|
||||
|
||||
if (!result) {
|
||||
throw newIOException(docListWrapper,
|
||||
"Failed to create new spreadsheet.");
|
||||
}
|
||||
|
||||
return idSaver.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of a spreadsheet with the given name.
|
||||
*
|
||||
* @param docListWrapper The GData handle for the Document List service.
|
||||
* @param title The name of the spreadsheet whose ID is to be retrieved.
|
||||
* @return The spreadsheet ID, if it can be retrieved. {@code null} will
|
||||
* be returned if no spreadsheet exists by the given name.
|
||||
* @throws IOException If an error occurs during the GData request.
|
||||
*/
|
||||
public String requestSpreadsheetId(final GDataWrapper<GDataServiceClient> docListWrapper,
|
||||
final String title) throws IOException {
|
||||
final AtomicReference<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
boolean result = docListWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client)
|
||||
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException,
|
||||
GDataWrapper.AuthenticationException {
|
||||
GDataParser listParser;
|
||||
try {
|
||||
listParser = client.getParserForFeed(Entry.class,
|
||||
DOCS_MY_SPREADSHEETS_FEED_URL,
|
||||
docListWrapper.getAuthManager().getAuthToken());
|
||||
listParser.init();
|
||||
|
||||
while (listParser.hasMoreData()) {
|
||||
Entry entry = listParser.readNextEntry(null);
|
||||
String entryTitle = entry.getTitle();
|
||||
Log.i(MyTracksConstants.TAG, "Found docs entry: " + entryTitle);
|
||||
if (entryTitle.equals(title)) {
|
||||
String entryId = entry.getId();
|
||||
int lastSlash = entryId.lastIndexOf('/');
|
||||
idSaver.set(entryId.substring(lastSlash + 15));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
throw new GDataWrapper.ParseException(e);
|
||||
} catch (HttpException e) {
|
||||
throw new GDataWrapper.HttpException(e.getStatusCode(), e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
throw newIOException(docListWrapper,
|
||||
"Failed to retrieve spreadsheet list.");
|
||||
}
|
||||
|
||||
return idSaver.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of the first worksheet in the named spreadsheet.
|
||||
*
|
||||
* @param trixWrapper The GData handle for the spreadsheet service.
|
||||
* @param spreadsheetId The GData ID for the given spreadsheet.
|
||||
* @return The worksheet ID, if it can be retrieved. {@code null} will be
|
||||
* returned if the GData request returns without error, but without an
|
||||
* ID.
|
||||
* @throws IOException If an error occurs during the GData request.
|
||||
*/
|
||||
public String getWorksheetId(final GDataWrapper<GDataServiceClient> trixWrapper,
|
||||
final String spreadsheetId) throws IOException {
|
||||
final AtomicReference<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
boolean result = trixWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client)
|
||||
throws GDataWrapper.AuthenticationException, IOException, GDataWrapper.ParseException, GDataWrapper.HttpException {
|
||||
String uri = String.format(DOCS_WORKSHEETS_URL_FORMAT, spreadsheetId);
|
||||
GDataParser sheetParser;
|
||||
try {
|
||||
sheetParser = ((SpreadsheetsClient) client).getParserForWorksheetsFeed(uri,
|
||||
trixWrapper.getAuthManager().getAuthToken());
|
||||
sheetParser.init();
|
||||
if (!sheetParser.hasMoreData()) {
|
||||
Log.i(MyTracksConstants.TAG, "Found no worksheets");
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the first worksheet.
|
||||
WorksheetEntry worksheetEntry =
|
||||
(WorksheetEntry) sheetParser.readNextEntry(new WorksheetEntry());
|
||||
|
||||
int lastSlash = worksheetEntry.getId().lastIndexOf('/');
|
||||
idSaver.set(worksheetEntry.getId().substring(lastSlash + 1));
|
||||
} catch (ParseException e) {
|
||||
throw new GDataWrapper.ParseException(e);
|
||||
} catch (AuthenticationException e) {
|
||||
throw new GDataWrapper.AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
throw newIOException(trixWrapper, "Failed to retrieve worksheet ID.");
|
||||
}
|
||||
|
||||
return idSaver.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a row to a worksheet containing the stats for a given track.
|
||||
*
|
||||
* @param context The context associated with this request.
|
||||
* @param trixAuth The GData authorization for the spreadsheet service.
|
||||
* @param spreadsheetId The spreadsheet to be modified.
|
||||
* @param worksheetId The worksheet to be modified.
|
||||
* @param track The track whose stats are to be written.
|
||||
* @param metricUnits True if metric units are to be used. If false,
|
||||
* imperial units will be used.
|
||||
* @throws IOException If an error occurs while updating the worksheet.
|
||||
*/
|
||||
public void addTrackRow(Context context, AuthManager trixAuth,
|
||||
String spreadsheetId, String worksheetId, Track track,
|
||||
boolean metricUnits) throws IOException {
|
||||
|
||||
String worksheetUri = String.format(DOCS_SPREADSHEET_URL_FORMAT,
|
||||
spreadsheetId, worksheetId);
|
||||
TripStatistics stats = track.getStatistics();
|
||||
|
||||
String distanceUnit = context.getString(metricUnits ?
|
||||
R.string.kilometer : R.string.mile);
|
||||
String speedUnit = context.getString(metricUnits ?
|
||||
R.string.kilometer_per_hour : R.string.mile_per_hour);
|
||||
String elevationUnit = context.getString(metricUnits ?
|
||||
R.string.meter : R.string.feet);
|
||||
|
||||
// Prepare the Post-Text we are going to send.
|
||||
DocsTagBuilder tagBuilder = new DocsTagBuilder(metricUnits)
|
||||
.append("name", track.getName())
|
||||
.append("description", track.getDescription())
|
||||
.append("date", String.format("%tc", stats.getStartTime()))
|
||||
.append("totaltime", StringUtils.formatTimeAlwaysShowingHours(
|
||||
stats.getTotalTime()))
|
||||
.append("movingtime", StringUtils.formatTimeAlwaysShowingHours(
|
||||
stats.getMovingTime()))
|
||||
.appendLargeUnits("distance", stats.getTotalDistance() / 1000)
|
||||
.append("distanceunit", distanceUnit)
|
||||
.appendLargeUnits("averagespeed", stats.getAverageSpeed() * 3.6)
|
||||
.appendLargeUnits("averagemovingspeed",
|
||||
stats.getAverageMovingSpeed() * 3.6)
|
||||
.appendLargeUnits("maxspeed", stats.getMaxSpeed() * 3.6)
|
||||
.append("speedunit", speedUnit)
|
||||
.appendSmallUnits("elevationgain", stats.getTotalElevationGain())
|
||||
.appendSmallUnits("minelevation", stats.getMinElevation())
|
||||
.appendSmallUnits("maxelevation", stats.getMaxElevation())
|
||||
.append("elevationunit", elevationUnit);
|
||||
|
||||
if (track.getMapId().length() > 0) {
|
||||
tagBuilder.append("map", String.format("%s?msa=0&msid=%s",
|
||||
MyTracksConstants.MAPSHOP_BASE_URL, track.getMapId()));
|
||||
}
|
||||
|
||||
String postText = new StringBuilder()
|
||||
.append("<entry xmlns='http://www.w3.org/2005/Atom' "
|
||||
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/"
|
||||
+ "2006/extended'>")
|
||||
.append(tagBuilder.build())
|
||||
.append("</entry>")
|
||||
.toString();
|
||||
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"Inserting at: " + spreadsheetId + " => " + worksheetUri);
|
||||
|
||||
Log.i(MyTracksConstants.TAG, postText);
|
||||
|
||||
writeRowData(trixAuth, worksheetUri, postText);
|
||||
|
||||
Log.i(MyTracksConstants.TAG, "Post finished.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes spreadsheet row data to the indicated worksheet.
|
||||
*
|
||||
* @param trixAuth The GData authorization for the spreadsheet service.
|
||||
* @param worksheetUri The URI of the worksheet to be altered.
|
||||
* @param postText The XML tags describing the change to be made.
|
||||
* @throws IOException Thrown if an error occurs during the write.
|
||||
*/
|
||||
protected void writeRowData(AuthManager trixAuth, String worksheetUri,
|
||||
String postText) throws IOException {
|
||||
// No need for a wrapper because we know that the authorization was good
|
||||
// enough to get this far.
|
||||
URL url = new URL(worksheetUri);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE_PARAM, ATOM_FEED_MIME_TYPE);
|
||||
conn.addRequestProperty("Authorization",
|
||||
"GoogleLogin auth=" + trixAuth.getAuthToken());
|
||||
conn.setDoOutput(true);
|
||||
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
|
||||
wr.write(postText);
|
||||
wr.flush();
|
||||
|
||||
// Get the response.
|
||||
// TODO: Should we parse the response, rather than simply throwing it away?
|
||||
BufferedReader rd =
|
||||
new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line;
|
||||
while ((line = rd.readLine()) != null) {
|
||||
// Process line.
|
||||
Log.i(MyTracksConstants.TAG, "r: " + line);
|
||||
}
|
||||
wr.close();
|
||||
rd.close();
|
||||
}
|
||||
|
||||
private static IOException newIOException(GDataWrapper wrapper,
|
||||
String message) {
|
||||
return new IOException(String.format("%s: %d: %s", message,
|
||||
wrapper.getErrorType(), wrapper.getErrorMessage()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.docs;
|
||||
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* <p>This class builds a string of XML tags used to talk to Docs using GData.
|
||||
*
|
||||
* <p>Sample Usage:
|
||||
*
|
||||
* <code>
|
||||
* String tags = new DocsTagBuilder(false)
|
||||
* .append("tagName", "tagValue")
|
||||
* .appendLargeUnits("bigTagName", 1.0)
|
||||
* .build();
|
||||
* </code>
|
||||
*
|
||||
* <p>results in:
|
||||
*
|
||||
* <code>
|
||||
* <gsx:tagName><![CDATA[tagValue]]></gsx:tagName>
|
||||
* <gsx:bigTagName><![CDATA[1.00]]></gsx:bigTagName>
|
||||
* </code>
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
class DocsTagBuilder {
|
||||
// TODO(simmonmt): These formats aren't I18N-compatible. Not everyone uses
|
||||
// commas for thousands and dots for the decimal point.
|
||||
private static final NumberFormat LARGE_UNIT_FORMAT =
|
||||
new DecimalFormat("#,###,###.00");
|
||||
private static final NumberFormat SMALL_UNIT_FORMAT =
|
||||
new DecimalFormat("###,###");
|
||||
|
||||
protected final boolean metricUnits;
|
||||
protected final StringBuilder stringBuilder;
|
||||
|
||||
/**
|
||||
* @param metricUnits True if metric units are to be used. If false,
|
||||
* imperial units will be used.
|
||||
*/
|
||||
DocsTagBuilder(boolean metricUnits) {
|
||||
this.metricUnits = metricUnits;
|
||||
stringBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
/** Appends a tag containing a string value */
|
||||
DocsTagBuilder append(String name, String value) {
|
||||
appendTag(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a tag containing a numeric value. The value will be formatted
|
||||
* according to the large distance of the specified measurement system (i.e.
|
||||
* kilometers or miles).
|
||||
*
|
||||
* @param name The tag name.
|
||||
* @param d The value to be formatted, in kilometers.
|
||||
*/
|
||||
DocsTagBuilder appendLargeUnits(String name, double d) {
|
||||
double value = metricUnits ? d : (d * UnitConversions.KM_TO_MI);
|
||||
appendTag(name, LARGE_UNIT_FORMAT.format(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a tag containing a numeric value. The value will be formatted
|
||||
* according to the small distance of the specified measurement system (i.e.
|
||||
* meters or feet).
|
||||
*
|
||||
* @param name The tag name.
|
||||
* @param d The value to be formatted, in meters.
|
||||
*/
|
||||
DocsTagBuilder appendSmallUnits(String name, double d) {
|
||||
double value = metricUnits ? d : (d * UnitConversions.M_TO_FT);
|
||||
appendTag(name, SMALL_UNIT_FORMAT.format(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a string containing all tags which have been added. */
|
||||
String build() {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private void appendTag(String name, String value) {
|
||||
stringBuilder.append("<gsx:")
|
||||
.append(name)
|
||||
.append(">")
|
||||
.append(StringUtils.stringAsCData(value))
|
||||
.append("</gsx:")
|
||||
.append(name)
|
||||
.append(">");
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,6 @@ package com.google.android.apps.mytracks.io.gdata;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata2.ConflictDetectedException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
@@ -39,9 +34,60 @@ import java.util.concurrent.TimeoutException;
|
||||
* handling. After a query is run, the wrapper can be queried about the error
|
||||
* that occurred.
|
||||
*
|
||||
* @param C the GData service client
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class GDataWrapper {
|
||||
public class GDataWrapper<C> {
|
||||
|
||||
public static class AuthenticationException extends Exception {
|
||||
private Exception exception;
|
||||
private static final long serialVersionUID = 1L;
|
||||
public AuthenticationException(Exception caught) {
|
||||
this.exception = caught;
|
||||
}
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
};
|
||||
|
||||
public static class ParseException extends Exception {
|
||||
private Exception exception;
|
||||
private static final long serialVersionUID = 1L;
|
||||
public ParseException(Exception caught) {
|
||||
this.exception = caught;
|
||||
}
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
};
|
||||
|
||||
public static class ConflictDetectedException extends Exception {
|
||||
private Exception exception;
|
||||
private static final long serialVersionUID = 1L;
|
||||
public ConflictDetectedException(Exception caught) {
|
||||
this.exception = caught;
|
||||
}
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
};
|
||||
|
||||
public static class HttpException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int statusCode;
|
||||
private String statusMessage;
|
||||
public HttpException(int statusCode, String statusMessage) {
|
||||
super();
|
||||
this.statusCode = statusCode;
|
||||
this.statusMessage = statusMessage;
|
||||
}
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
public String getStatusMessage() {
|
||||
return statusMessage;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A QueryFunction is passed in when executing a query. The query function of
|
||||
@@ -49,8 +95,8 @@ public class GDataWrapper {
|
||||
* should execute whatever operations it desires on the client without concern
|
||||
* for whether the client will throw an error.
|
||||
*/
|
||||
public interface QueryFunction {
|
||||
public abstract void query(GDataServiceClient client)
|
||||
public interface QueryFunction<C> {
|
||||
public abstract void query(C client)
|
||||
throws AuthenticationException, IOException, ParseException,
|
||||
ConflictDetectedException, HttpException;
|
||||
}
|
||||
@@ -92,7 +138,7 @@ public class GDataWrapper {
|
||||
|
||||
private String errorMessage;
|
||||
private int errorType;
|
||||
private GDataServiceClient gdataServiceClient;
|
||||
private C gdataServiceClient;
|
||||
private AuthManager auth;
|
||||
private boolean retryOnAuthFailure;
|
||||
|
||||
@@ -103,7 +149,7 @@ public class GDataWrapper {
|
||||
retryOnAuthFailure = false;
|
||||
}
|
||||
|
||||
public void setClient(GDataServiceClient gdataServiceClient) {
|
||||
public void setClient(C gdataServiceClient) {
|
||||
this.gdataServiceClient = gdataServiceClient;
|
||||
}
|
||||
|
||||
@@ -112,7 +158,7 @@ public class GDataWrapper {
|
||||
return runCommon(function, null);
|
||||
}
|
||||
|
||||
public boolean runQuery(final QueryFunction query) {
|
||||
public boolean runQuery(final QueryFunction<C> query) {
|
||||
return runCommon(null, query);
|
||||
}
|
||||
|
||||
@@ -120,7 +166,7 @@ public class GDataWrapper {
|
||||
* Runs an arbitrary piece of code.
|
||||
*/
|
||||
private boolean runCommon(final AuthenticatedFunction function,
|
||||
final QueryFunction query) {
|
||||
final QueryFunction<C> query) {
|
||||
for (int i = 0; i <= AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES; i++) {
|
||||
runOne(function, query);
|
||||
if (errorType == ERROR_NO_ERROR) {
|
||||
@@ -144,7 +190,7 @@ public class GDataWrapper {
|
||||
* errorMessage will contain the result/status of the function/query.
|
||||
*/
|
||||
private void runOne(final AuthenticatedFunction function,
|
||||
final QueryFunction query) {
|
||||
final QueryFunction<C> query) {
|
||||
try {
|
||||
if (function != null) {
|
||||
function.run(this.auth.getAuthToken());
|
||||
@@ -160,7 +206,7 @@ public class GDataWrapper {
|
||||
errorMessage = null;
|
||||
|
||||
} catch (AuthenticationException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||
Log.e(MyTracksConstants.TAG, "AuthenticationException", e);
|
||||
errorType = ERROR_AUTH;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (HttpException e) {
|
||||
@@ -243,6 +289,10 @@ public class GDataWrapper {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public AuthManager getAuthManager() {
|
||||
return auth;
|
||||
}
|
||||
|
||||
public void setRetryOnAuthFailure(boolean retry) {
|
||||
retryOnAuthFailure = retry;
|
||||
}
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mymaps;
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
@@ -61,4 +61,4 @@ public class GeoRect {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.Signature;
|
||||
import android.location.Location;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -267,12 +267,28 @@ public class MyTracksUtils {
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Unable to get signatures", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a utility class w/ only static memebers.
|
||||
* Get the My Tracks version from the manifest.
|
||||
*
|
||||
* @return the version, or an empty string in case of failure.
|
||||
*/
|
||||
public static String getMyTracksVersion(Context context) {
|
||||
try {
|
||||
PackageInfo pi = context.getPackageManager().getPackageInfo(
|
||||
"com.google.android.maps.mytracks",
|
||||
PackageManager.GET_META_DATA);
|
||||
return pi.versionName;
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a utility class w/ only static members.
|
||||
*/
|
||||
protected MyTracksUtils() {
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ public class StringUtils implements DescriptionGenerator {
|
||||
+ "<img border=\"0\" src=\"%s\"/>",
|
||||
|
||||
// Line 1
|
||||
context.getString(R.string.my_map_link),
|
||||
context.getString(R.string.fusiontable_link),
|
||||
|
||||
// Line 2
|
||||
context.getString(R.string.total_distance_label),
|
||||
|
||||
Reference in New Issue
Block a user