() {
+ @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("")
+ .append(tagBuilder.build())
+ .append("")
+ .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()));
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilder.java b/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilder.java
new file mode 100644
index 000000000..50ab27057
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilder.java
@@ -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;
+
+/**
+ * This class builds a string of XML tags used to talk to Docs using GData.
+ *
+ *
Sample Usage:
+ *
+ *
+ * String tags = new DocsTagBuilder(false)
+ * .append("tagName", "tagValue")
+ * .appendLargeUnits("bigTagName", 1.0)
+ * .build();
+ *
+ *
+ *
results in:
+ *
+ *
+ *
+ *
+ *
+ *
+ * @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("")
+ .append(StringUtils.stringAsCData(value))
+ .append("");
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/gdata/GDataWrapper.java b/MyTracks/src/com/google/android/apps/mytracks/io/gdata/GDataWrapper.java
index 97d7cc99e..751be06bb 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/gdata/GDataWrapper.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/gdata/GDataWrapper.java
@@ -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 {
+
+ 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 {
+ 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 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 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 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;
}
diff --git a/MyTracks/src/com/google/android/apps/mymaps/GeoRect.java b/MyTracks/src/com/google/android/apps/mytracks/util/GeoRect.java
similarity index 97%
rename from MyTracks/src/com/google/android/apps/mymaps/GeoRect.java
rename to MyTracks/src/com/google/android/apps/mytracks/util/GeoRect.java
index f854b55bf..6605f1453 100644
--- a/MyTracks/src/com/google/android/apps/mymaps/GeoRect.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/GeoRect.java
@@ -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;
}
-}
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java
index 52f5dee72..4d6da6786 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java
@@ -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() {
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java
index 76004303f..b6b8baace 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java
@@ -316,7 +316,7 @@ public class StringUtils implements DescriptionGenerator {
+ "
",
// Line 1
- context.getString(R.string.my_map_link),
+ context.getString(R.string.fusiontable_link),
// Line 2
context.getString(R.string.total_distance_label),
diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
index 4e90d7227..4d4ec7d8f 100644
--- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
@@ -101,6 +101,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
values.put(TracksColumns.NAME, track.getName());
values.put(TracksColumns.DESCRIPTION, track.getDescription());
values.put(TracksColumns.MAPID, track.getMapId());
+ values.put(TracksColumns.TABLEID, track.getTableId());
values.put(TracksColumns.CATEGORY, track.getCategory());
values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints());
values.put(TracksColumns.STARTID, track.getStartId());
@@ -269,6 +270,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
int idxDescription =
cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
int idxMapId = cursor.getColumnIndexOrThrow(TracksColumns.MAPID);
+ int idxTableId = cursor.getColumnIndexOrThrow(TracksColumns.TABLEID);
int idxCategory = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
int idxStartId = cursor.getColumnIndexOrThrow(TracksColumns.STARTID);
int idxStartTime = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
@@ -308,6 +310,9 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
if (!cursor.isNull(idxMapId)) {
track.setMapId(cursor.getString(idxMapId));
}
+ if (!cursor.isNull(idxTableId)) {
+ track.setTableId(cursor.getString(idxTableId));
+ }
if (!cursor.isNull(idxCategory)) {
track.setCategory(cursor.getString(idxCategory));
}
diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java
index fe4c19453..cf963fff7 100644
--- a/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java
@@ -56,6 +56,7 @@ public class Track implements Parcelable {
track.locations.add(loc);
}
+ track.tableId = source.readString();
return track;
}
@@ -81,6 +82,7 @@ public class Track implements Parcelable {
private String name = "";
private String description = "";
private String mapId = "";
+ private String tableId = "";
private long startId = -1;
private long stopId = -1;
private String category = "";
@@ -104,6 +106,8 @@ public class Track implements Parcelable {
for (int i = 0; i < numberOfPoints; ++i) {
dest.writeParcelable(locations.get(i), 0);
}
+
+ dest.writeString(tableId);
}
// Getters and setters:
@@ -161,6 +165,14 @@ public class Track implements Parcelable {
this.mapId = mapId;
}
+ public String getTableId() {
+ return tableId;
+ }
+
+ public void setTableId(String tableId) {
+ this.tableId = tableId;
+ }
+
public String getCategory() {
return category;
}
diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/TracksColumns.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/TracksColumns.java
index 6fb122b89..d3e0fae4d 100644
--- a/MyTracksLib/src/com/google/android/apps/mytracks/content/TracksColumns.java
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/TracksColumns.java
@@ -59,4 +59,5 @@ public interface TracksColumns extends BaseColumns {
public static final String MINLON = "minlon";
public static final String MAXLON = "maxlon";
public static final String MAPID = "mapid";
+ public static final String TABLEID = "tableid";
}
diff --git a/MyTracksTest/.classpath b/MyTracksTest/.classpath
index 7ec6a9bf6..9ede41bc3 100644
--- a/MyTracksTest/.classpath
+++ b/MyTracksTest/.classpath
@@ -1,6 +1,5 @@
-
@@ -10,5 +9,7 @@
+
+
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelperTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelperTest.java
new file mode 100644
index 000000000..b1402c06f
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelperTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.io.gdata.GDataWrapper;
+
+import android.content.Context;
+import android.test.mock.MockContext;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+/**
+ * Tests for {@link DocsHelper}, with the exception of
+ * {@link DocsHelper#addTrackRow}, which is in
+ * {@link DocsHelper_AddTrackRowTest}.
+ *
+ * @author Matthew Simmons
+ */
+public class DocsHelperTest extends TestCase {
+ private Context mockContext = new MockContext();
+
+ // TODO(simmonmt): Use AndroidMock to mock this class. We do it the hard
+ // way because use of AndroidMock to mock GDataWrapper triggers a compile
+ // error in an unrelated file. Specifically, it causes a NoClassDefFound
+ // exception for com.google.wireless.gdata2.client.AuthenticationException,
+ // (wrongly) attributed to the first source file in the project. Using the
+ // @UsesMocks(GDataWrapper.class) annotation is enough -- you don't have to
+ // touch AndroidMock at all to get this failure.
+ // The bug is filed with Android Mock as
+ // http://code.google.com/p/android-mock/issues/detail?id=3
+ private class MockGDataWrapper extends GDataWrapper {
+ private final boolean returnValue;
+
+ MockGDataWrapper(boolean returnValue) {
+ this.returnValue = returnValue;
+ }
+
+ @Override
+ public boolean runQuery(QueryFunction queryFunction) {
+ return returnValue;
+ }
+ }
+
+ public void testCreateSpreadsheet_noException() throws Exception {
+ // Our mock GDataWrapper isn't able to affect the return value from
+ // DocsHelper#createSpreadsheet. As such, we're only able to simulate the
+ // case where there weren't any GData errors, but not spreadsheet ID was
+ // actually returned. createSpreadsheet is defined to return null in that
+ // situation.
+ assertNull(new DocsHelper().createSpreadsheet(
+ mockContext, new MockGDataWrapper(true), "sheetName"));
+ }
+
+ public void testCreateSpreadsheet_exception() throws Exception {
+ try {
+ new DocsHelper().createSpreadsheet(
+ mockContext, new MockGDataWrapper(false), "sheetName");
+ fail();
+ } catch (IOException expected) {}
+ }
+
+ public void testRequestSpreadsheetId_noException() throws Exception {
+ assertNull(new DocsHelper().requestSpreadsheetId(
+ new MockGDataWrapper(true), "sheetName"));
+ }
+
+ public void testRequestSpreadsheetId_exception() throws Exception {
+ try {
+ new DocsHelper().requestSpreadsheetId(new MockGDataWrapper(false),
+ "sheetName");
+ fail();
+ } catch (IOException expected) {}
+ }
+
+ public void testGetWorksheetId_noException() throws Exception {
+ assertNull(new DocsHelper().getWorksheetId(new MockGDataWrapper(true),
+ "sheetId"));
+ }
+
+ public void testGetWorksheetId_exception() throws Exception {
+ try {
+ new DocsHelper().getWorksheetId(new MockGDataWrapper(false), "sheetId");
+ fail();
+ } catch (IOException expected) {}
+ }
+}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelper_AddTrackRowTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelper_AddTrackRowTest.java
new file mode 100644
index 000000000..e357605b4
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelper_AddTrackRowTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.content.Track;
+import com.google.android.apps.mytracks.io.AuthManager;
+import com.google.android.apps.mytracks.stats.TripStatistics;
+import com.google.android.testing.mocking.AndroidMock;
+import com.google.android.testing.mocking.UsesMocks;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.mock.MockContext;
+import android.test.mock.MockResources;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+/**
+ * Tests for {@link DocsHelper#addTrackRow}
+ *
+ * @author Matthew Simmons
+ */
+public class DocsHelper_AddTrackRowTest extends TestCase {
+ private static final long TIME = 1288721514000L;
+
+ private static class StringWritingDocsHelper extends DocsHelper {
+ String writtenSheetUri = null;
+ String writtenData = null;
+
+ @Override
+ protected void writeRowData(AuthManager trixAuth, String worksheetUri,
+ String postText) {
+ writtenSheetUri = worksheetUri;
+ writtenData = postText;
+ }
+ }
+
+ public void testAddTrackRow_imperial() throws Exception {
+ StringWritingDocsHelper docsHelper = new StringWritingDocsHelper();
+ addTrackRow(docsHelper, false);
+
+ String expectedData =
+ ""
+ + ""
+ + ""
+ // We format the date here because we can't guarantee/force the timezone
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + "";
+
+ assertEquals(
+ "http://spreadsheets.google.com/feeds/list/ssid/wsid/private/full",
+ docsHelper.writtenSheetUri);
+ assertEquals(expectedData, docsHelper.writtenData);
+ }
+
+ public void testAddTrackRow_metric() throws Exception {
+ StringWritingDocsHelper docsHelper = new StringWritingDocsHelper();
+ addTrackRow(docsHelper, true);
+
+ // The imperial test verifies that the tags come out in the proper order,
+ // and with the proper names. We need only verify that the labels are
+ // correct, and that at least one of the unit-dependent value tags is
+ // correct.
+ assertTrue(docsHelper.writtenData.contains(
+ ""));
+ assertTrue(docsHelper.writtenData.contains(
+ ""));
+ assertTrue(docsHelper.writtenData.contains(
+ ""));
+
+ assertTrue(docsHelper.writtenData.contains(
+ ""));
+ }
+
+ @UsesMocks({AuthManager.class, MockContext.class, MockResources.class,
+ Track.class})
+ /** Adds a row to the spreadsheet, using the provided helper. */
+ private void addTrackRow(DocsHelper docsHelper, boolean useMetric)
+ throws IOException {
+ Resources mockResources = AndroidMock.createMock(MockResources.class);
+
+ if (useMetric) {
+ AndroidMock.expect(mockResources.getString(R.string.kilometer))
+ .andReturn("km");
+ AndroidMock.expect(mockResources.getString(R.string.kilometer_per_hour))
+ .andReturn("kph");
+ AndroidMock.expect(mockResources.getString(R.string.meter))
+ .andReturn("meter");
+ } else {
+ AndroidMock.expect(mockResources.getString(R.string.mile))
+ .andReturn("mile");
+ AndroidMock.expect(mockResources.getString(R.string.mile_per_hour))
+ .andReturn("mph");
+ AndroidMock.expect(mockResources.getString(R.string.feet))
+ .andReturn("feet");
+ }
+ AndroidMock.replay(mockResources);
+
+ Context mockContext = AndroidMock.createMock(MockContext.class);
+ AndroidMock.expect(mockContext.getResources())
+ .andReturn(mockResources).anyTimes();
+ AndroidMock.replay(mockContext);
+
+ AuthManager mockAuthManager = AndroidMock.createMock(AuthManager.class);
+ AndroidMock.replay(mockAuthManager);
+
+ TripStatistics stats = new TripStatistics();
+ stats.setStartTime(TIME);
+ stats.setTotalTime(5000);
+ stats.setMovingTime(4000);
+ stats.setTotalDistance(20000);
+ stats.setMaxSpeed(1500);
+ stats.setTotalElevationGain(6000);
+ stats.setMinElevation(-500);
+ stats.setMaxElevation(550);
+
+ Track track = new Track();
+ track.setName("trackName");
+ track.setDescription("trackDescription");
+ track.setMapId("trackMapId");
+ track.setStatistics(stats);
+
+ docsHelper.addTrackRow(mockContext, mockAuthManager, "ssid", "wsid",
+ track, useMetric);
+ }
+}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilderTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilderTest.java
new file mode 100644
index 000000000..0145f6a9e
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilderTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 junit.framework.TestCase;
+
+/**
+ * This class tests {@link DocsTagBuilder}
+ *
+ * @author Matthew Simmons
+ */
+public class DocsTagBuilderTest extends TestCase {
+ public void testAppend() {
+ assertEquals("",
+ new DocsTagBuilder(false).append("tag", "value").build());
+ }
+
+ public void testMultiAppend() {
+ String actual = new DocsTagBuilder(false)
+ .append("tag1", "value1")
+ .append("tag2", "value2")
+ .build();
+
+ assertEquals(""
+ + "", actual);
+ }
+
+ public void testAppendLargeUnits_imperial() {
+ assertEquals("",
+ new DocsTagBuilder(false).appendLargeUnits("tag", 100.0).build());
+ }
+
+ public void testAppendLargeUnits_metric() {
+ assertEquals("",
+ new DocsTagBuilder(true).appendLargeUnits("tag", 100.0).build());
+ }
+
+ public void testAppendSmallUnits_imperial() {
+ assertEquals("",
+ new DocsTagBuilder(false).appendSmallUnits("tag", 100.0).build());
+ }
+
+ public void testAppendSmallUnits_metric() {
+ assertEquals("",
+ new DocsTagBuilder(true).appendSmallUnits("tag", 100.0).build());
+ }
+}