diff --git a/MyTracks/res/layout/about.xml b/MyTracks/res/layout/about.xml index 753d860e8..0870154b7 100644 --- a/MyTracks/res/layout/about.xml +++ b/MyTracks/res/layout/about.xml @@ -41,8 +41,7 @@ limitations under the License. - + - + - + - + + + + + + + + diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml index 32095d881..861738bf7 100644 --- a/MyTracks/res/values/do_not_translate.xml +++ b/MyTracks/res/values/do_not_translate.xml @@ -21,6 +21,7 @@ limitations under the License. mytracks@googlegroups.com mytracks.appspot.com + m.google.com/privacy Copyright 2009 Google Inc. Bartlomiej Niechwiej Dylan Casey diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index 10a35a347..d519d6a0e 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -104,6 +104,7 @@ limitations under the License. Error Success Waypoint + License (Start) @@ -378,8 +379,8 @@ limitations under the License. Help Team: Version: - Support Support: + Privacy: Send to... diff --git a/MyTracks/src/com/google/android/apps/mytracks/Eula.java b/MyTracks/src/com/google/android/apps/mytracks/Eula.java index 4471d93fc..aed5a40a0 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/Eula.java +++ b/MyTracks/src/com/google/android/apps/mytracks/Eula.java @@ -21,38 +21,37 @@ import com.google.android.maps.mytracks.R; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; /** - * Displays an EULA ("End User License Agreement") that the user has to accept - * before using the application. Your application should call - * {@link Eula#showEula(android.app.Activity)} in the onCreate() method of the - * first activity. If the user accepts the EULA, it will never be shown again. - * If the user refuses, {@link android.app.Activity#finish()} is invoked on your - * activity. + * This class handles display of EULAs ("End User License Agreements") to the + * user. */ class Eula { private static final String PREFERENCE_EULA_ACCEPTED = "eula.accepted"; private static final String PREFERENCES_EULA = "eula"; + private Eula() {} + /** * Displays the EULA if necessary. This method should be called from the - * onCreate() method of your main Activity. + * onCreate() method of your main Activity. If the user accepts, the EULA + * will never be displayed again. If the user refuses, the activity will + * finish (exit). * * @param activity The Activity to finish if the user rejects the EULA */ - static void showEula(final Activity activity) { + static void showEulaRequireAcceptance(final Activity activity) { final SharedPreferences preferences = activity.getSharedPreferences(PREFERENCES_EULA, Activity.MODE_PRIVATE); if (preferences.getBoolean(PREFERENCE_EULA_ACCEPTED, false)) { return; } - final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(R.string.eula_title); - builder.setCancelable(true); + final AlertDialog.Builder builder = initDialog(activity); builder.setPositiveButton(R.string.accept, new DialogInterface.OnClickListener() { @Override @@ -73,10 +72,28 @@ class Eula { refuse(activity); } }); - builder.setMessage(ResourceUtils.readFile(activity, R.raw.eula)); builder.show(); } + /** + * Display the EULA to the user in an informational context. They won't be + * given the choice of accepting or declining the EULA -- we're simply + * displaying it for them to read. + */ + static void showEula(Context context) { + AlertDialog.Builder builder = initDialog(context); + builder.setPositiveButton(R.string.ok, null); + builder.show(); + } + + private static AlertDialog.Builder initDialog(Context context) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setCancelable(true); + builder.setTitle(R.string.eula_title); + builder.setMessage(ResourceUtils.readFile(context, R.raw.eula)); + return builder; + } + private static void accept(Activity activity, SharedPreferences preferences) { ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges( preferences.edit().putBoolean(PREFERENCE_EULA_ACCEPTED, true)); @@ -87,7 +104,4 @@ class Eula { private static void refuse(Activity activity) { activity.finish(); } - - private Eula() { - } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java index 3a276f86e..1eb86831a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java @@ -46,6 +46,7 @@ public class ExportAllTracks { public static final int GPX_OPTION_INDEX = 0; public static final int KML_OPTION_INDEX = 1; public static final int CSV_OPTION_INDEX = 2; + public static final int TCX_OPTION_INDEX = 3; private final Activity activity; private WakeLock wakeLock; @@ -67,6 +68,11 @@ public class ExportAllTracks { case CSV_OPTION_INDEX: format = TrackFileFormat.CSV; break; + case TCX_OPTION_INDEX: + format = TrackFileFormat.TCX; + break; + default: + Log.w(MyTracksConstants.TAG, "Unknown export format: " + which); } } }; diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index c4ed31c11..670072310 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -29,11 +29,11 @@ import com.google.android.apps.mytracks.content.WaypointCreationRequest; 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.TempFileCleaner; import com.google.android.apps.mytracks.io.TrackWriter; import com.google.android.apps.mytracks.io.TrackWriterFactory; +import com.google.android.apps.mytracks.io.SendToDocs; import com.google.android.apps.mytracks.io.SendToMyMaps.OnSendCompletedListener; import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.services.ITrackRecordingService; @@ -93,7 +93,6 @@ import org.xml.sax.SAXException; */ public class MyTracks extends TabActivity implements OnTouchListener, OnSharedPreferenceChangeListener, ProgressIndicator { - /** * Singleton instance */ @@ -315,7 +314,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, } // This will show the eula until the user accepts or quits the app. - Eula.showEula(this); + Eula.showEulaRequireAcceptance(this); // Check if we got invoked via the VIEW intent: Intent intent = getIntent(); @@ -517,13 +516,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 +534,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 +547,9 @@ 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); Runnable onCompletion = new Runnable() { public void run() { setProgressValue(100); @@ -608,7 +610,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, } } if (dialogManager.getSendToGoogleDialog().getSendToDocs()) { - onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCS, + onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST, RESULT_OK, new Intent()); } else { dialogManager.dismissDialogSafely(DIALOG_PROGRESS); @@ -930,7 +932,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 @@ -1052,7 +1054,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, MyMapsConstants.MAPSHOP_SERVICE); } } else { - onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCS, RESULT_OK, + onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST, RESULT_OK, new Intent()); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java index 80726e5fc..bcef4ff99 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java @@ -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; diff --git a/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java b/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java index 42edd66f6..cfbab6d73 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java @@ -19,6 +19,7 @@ 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; @@ -74,6 +75,12 @@ public class WelcomeActivity extends Activity { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setView(view); builder.setPositiveButton(R.string.ok, null); + builder.setNeutralButton(R.string.license, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Eula.showEula(WelcomeActivity.this); + } + }); builder.setIcon(R.drawable.arrow_icon); AlertDialog dialog = builder.create(); dialog.show(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java index 6054dc9a5..6c7978b11 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java @@ -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(""); + pw.format("\n", + Charset.defaultCharset().name()); pw.println(""); pw.println(""); - if (idTagIndex == -1) { - return; - } - int idTagCloseIndex = result.indexOf("", 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(""); - 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(""); - 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("'); - sb.append(StringUtils.stringAsCData(value)); - 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); - } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java index a09e2fd60..dfd3513ad 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java @@ -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(""); + pw.format("\n", + Charset.defaultCharset().name()); pw.print(" idSaver = new AtomicReference(); + + boolean result = docListWrapper.runQuery(new QueryFunction() { + @Override + public void query(GDataServiceClient client) throws IOException { + // 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(""); + if (idTagIndex == -1) { + return; + } + int idTagCloseIndex = result.indexOf("", 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 docListWrapper, + final String title) throws IOException { + final AtomicReference idSaver = new AtomicReference(); + + boolean result = docListWrapper.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, + 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; + } + } + } + }); + + 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 trixWrapper, + final String spreadsheetId) throws IOException { + final AtomicReference idSaver = new AtomicReference(); + + boolean result = trixWrapper.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, + 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)); + } + }); + + 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", + MyMapsConstants.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..d45ec9002 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 @@ -243,6 +243,10 @@ public class GDataWrapper { this.auth = auth; } + public AuthManager getAuthManager() { + return auth; + } + public void setRetryOnAuthFailure(boolean retry) { retryOnAuthFailure = retry; } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java index 12542199b..73721214e 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java @@ -2,31 +2,27 @@ package com.google.android.apps.mytracks.io; -import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.isA; -import static org.easymock.EasyMock.leq; -import static org.easymock.EasyMock.same; -import com.google.android.apps.mytracks.MyTracksConstants; +import com.google.android.apps.mytracks.content.MyTracksProvider; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; -import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; +import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext; import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory; import android.content.Context; -import android.database.MatrixCursor; import android.location.Location; -import android.provider.BaseColumns; import android.test.AndroidTestCase; +import android.test.RenamingDelegatingContext; +import android.test.mock.MockContentResolver; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.OutputStream; import org.easymock.EasyMock; -import org.easymock.IAnswer; import org.easymock.IArgumentMatcher; import org.easymock.IMocksControl; @@ -138,12 +134,19 @@ public class TrackWriterTest extends AndroidTestCase { protected void setUp() throws Exception { super.setUp(); + MockContentResolver mockContentResolver = new MockContentResolver(); + RenamingDelegatingContext targetContext = new RenamingDelegatingContext( + getContext(), getContext(), "test."); + Context context = new MockContext(mockContentResolver, targetContext); + MyTracksProvider provider = new MyTracksProvider(); + provider.attachInfo(context, null); + mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider); + setContext(context); + providerUtils = MyTracksProviderUtils.Factory.get(context); + oldProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(providerUtils); + mocksControl = EasyMock.createStrictControl(); formatWriter = mocksControl.createMock(TrackFormatWriter.class); - providerUtils = mocksControl.createMock(MyTracksProviderUtils.class); - oldProviderUtilsFactory = - TestingProviderUtilsFactory.installWithInstance(providerUtils); - expect(formatWriter.getExtension()).andStubReturn(EXTENSION); track = new Track(); @@ -225,13 +228,6 @@ public class TrackWriterTest extends AndroidTestCase { public void testWriteDocument_emptyTrack() { writer = new TrackWriter(getContext(), providerUtils, track, formatWriter); - // Don't let it write any waypoints - expect(providerUtils.getWaypointsCursor( - TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS)) - .andStubReturn(null); - expect(providerUtils.getLocationsCursor( - eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(null); - // Set expected mock behavior formatWriter.writeHeader(); formatWriter.writeFooter(); @@ -253,7 +249,7 @@ public class TrackWriterTest extends AndroidTestCase { new Location("fake2"), new Location("fake3"), new Location("fake4"), - new Location("fake5"), + new Location("fake5") }; Waypoint[] wps = { new Waypoint(), new Waypoint(), new Waypoint() }; @@ -262,37 +258,14 @@ public class TrackWriterTest extends AndroidTestCase { // Make location 3 invalid locs[2].setLatitude(100); - - // Set up cursors - // We use fake columns since the cursor is only read by the provider utils - final MatrixCursor locCursor = - new MatrixCursor(new String[] { BaseColumns._ID }, 6); - for (int i = 1; i <= 6; i++) { - locCursor.newRow().add(i); + + assertEquals(locs.length, providerUtils.bulkInsertTrackPoints(locs, locs.length, TRACK_ID)); + for (int i = 0; i < wps.length; ++i) { + Waypoint wpt = wps[i]; + wpt.setTrackId(TRACK_ID); + assertNotNull(providerUtils.insertWaypoint(wpt)); + wpt.setId(i + 1); } - expect(providerUtils.getLocationsCursor( - eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(locCursor); - providerUtils.fillLocation(same(locCursor), isA(Location.class)); - EasyMock.expectLastCall().andStubAnswer(new IAnswer() { - @Override - public Void answer() throws Throwable { - Location loc = (Location) EasyMock.getCurrentArguments()[1]; - loc.set(locs[locCursor.getPosition()]); - return null; - } - }); - - MatrixCursor wpCursor = - new MatrixCursor(new String[] { BaseColumns._ID }, 3); - wpCursor.newRow().add(1); - wpCursor.newRow().add(2); - wpCursor.newRow().add(3); - expect(providerUtils.getWaypointsCursor( - eq(TRACK_ID), leq(0L), - eq(MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))) - .andStubReturn(wpCursor); - expect(providerUtils.createWaypoint(wpCursor)) - .andStubAnswer(stubCursorToArray(wpCursor, wps)); // Begin the track formatWriter.writeHeader(); @@ -317,8 +290,8 @@ public class TrackWriterTest extends AndroidTestCase { formatWriter.writeEndTrack(locEq(locs[5])); // Expect reading/writing of the waypoints (except the first) - formatWriter.writeWaypoint(wps[1]); - formatWriter.writeWaypoint(wps[2]); + formatWriter.writeWaypoint(wptEq(wps[1])); + formatWriter.writeWaypoint(wptEq(wps[2])); formatWriter.writeFooter(); formatWriter.close(); @@ -330,6 +303,26 @@ public class TrackWriterTest extends AndroidTestCase { mocksControl.verify(); } + private static Waypoint wptEq(final Waypoint wpt) { + EasyMock.reportMatcher(new IArgumentMatcher() { + @Override + public boolean matches(Object wptObj2) { + if (wptObj2 == null || wpt == null) return wpt == wptObj2; + Waypoint wpt2 = (Waypoint) wptObj2; + + return wpt.getId() == wpt2.getId(); + } + + @Override + public void appendTo(StringBuffer buffer) { + buffer.append("wptEq("); + buffer.append(wpt); + buffer.append(")"); + } + }); + return null; + } + private static Location locEq(final Location loc) { EasyMock.reportMatcher(new IArgumentMatcher() { @Override @@ -359,23 +352,14 @@ public class TrackWriterTest extends AndroidTestCase { }); return null; } - - private IAnswer stubCursorToArray( - final MatrixCursor cursor, final T[] values) { - return new IAnswer() { - @Override - public T answer() throws Throwable { - return values[cursor.getPosition()]; - } - }; - } - + private void fillLocations(Location... locs) { assertTrue(locs.length < 90); for (int i = 0; i < locs.length; i++) { Location location = locs[i]; location.setLatitude(i + 1); location.setLongitude(i + 1); + location.setTime(i + 1000); } } } 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()); + } +} diff --git a/SignalStrengthSampler/src/com/google/android/apps/mytracks/signalstrength/SignalStrengthService.java b/SignalStrengthSampler/src/com/google/android/apps/mytracks/signalstrength/SignalStrengthService.java index 210fefe02..8a9cf4cd3 100644 --- a/SignalStrengthSampler/src/com/google/android/apps/mytracks/signalstrength/SignalStrengthService.java +++ b/SignalStrengthSampler/src/com/google/android/apps/mytracks/signalstrength/SignalStrengthService.java @@ -19,7 +19,6 @@ import static com.google.android.apps.mytracks.signalstrength.SignalStrengthCons import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.MyTracksProviderUtilsImpl; -import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.WaypointCreationRequest; import com.google.android.apps.mytracks.services.ITrackRecordingService; import com.google.android.apps.mytracks.signalstrength.SignalStrengthListener.SignalStrengthCallback; @@ -56,7 +55,6 @@ public class SignalStrengthService extends Service private ITrackRecordingService mytracksService; private long lastSamplingTime; private long samplingPeriod; - private MyTracksProviderUtils utils; @Override public void onCreate() { @@ -67,7 +65,6 @@ public class SignalStrengthService extends Service getString(R.string.mytracks_service_class)); preferences = PreferenceManager.getDefaultSharedPreferences(this); signalListenerFactory = new SignalStrengthListenerFactory(); - utils = new MyTracksProviderUtilsImpl(this.getContentResolver()); } @Override