mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 09:33:06 +02:00
Merging changes from main repository
This commit is contained in:
@@ -41,8 +41,7 @@ limitations under the License.
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<View
|
||||
android:layout_height="2dip" />
|
||||
<View android:layout_height="2dip" />
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
@@ -86,8 +85,7 @@ limitations under the License.
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<View
|
||||
android:layout_height="2dip" />
|
||||
<View android:layout_height="2dip" />
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
@@ -106,8 +104,7 @@ limitations under the License.
|
||||
android:textSize="20sp" />
|
||||
</TableRow>
|
||||
|
||||
<View
|
||||
android:layout_height="2dip" />
|
||||
<View android:layout_height="2dip" />
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
@@ -126,7 +123,25 @@ limitations under the License.
|
||||
android:textSize="18sp" />
|
||||
</TableRow>
|
||||
|
||||
<View
|
||||
android:layout_height="4dip" />
|
||||
<View android:layout_height="2dip" />
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:paddingLeft="10dip"
|
||||
android:paddingRight="10dip"
|
||||
android:layout_column="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/about_privacy"
|
||||
android:textSize="20sp" />
|
||||
<TextView
|
||||
android:autoLink="web"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/about_privacy_weburl"
|
||||
android:textSize="18sp" />
|
||||
</TableRow>
|
||||
|
||||
<View android:layout_height="4dip" />
|
||||
|
||||
</TableLayout>
|
||||
|
||||
@@ -21,6 +21,7 @@ limitations under the License.
|
||||
<!-- About Dialog -->
|
||||
<string name="about_googlegroups">mytracks@googlegroups.com</string>
|
||||
<string name="about_weburl">mytracks.appspot.com</string>
|
||||
<string name="about_privacy_weburl">m.google.com/privacy</string>
|
||||
<string name="about_copyright_message">Copyright 2009 Google Inc.</string>
|
||||
<string name="about_bart">Bartlomiej Niechwiej</string>
|
||||
<string name="about_dylan">Dylan Casey</string>
|
||||
|
||||
@@ -104,6 +104,7 @@ limitations under the License.
|
||||
<string name="error">Error</string>
|
||||
<string name="success">Success</string>
|
||||
<string name="waypoint">Waypoint</string>
|
||||
<string name="license">License</string>
|
||||
|
||||
<!-- Placemark labels for start and end of track -->
|
||||
<string name="start">(Start)</string>
|
||||
@@ -378,8 +379,8 @@ limitations under the License.
|
||||
<string name="help">Help</string>
|
||||
<string name="about_team">Team:</string>
|
||||
<string name="about_version">Version:</string>
|
||||
<string name="about_email">Support</string>
|
||||
<string name="about_support">Support:</string>
|
||||
<string name="about_privacy">Privacy:</string>
|
||||
|
||||
<!-- Send to Google -->
|
||||
<string name="sendtogoogle_title">Send to...</string>
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,20 @@
|
||||
*/
|
||||
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 android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -48,16 +36,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 +44,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 +60,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 +158,24 @@ public class SendToDocs {
|
||||
* @param track the track
|
||||
*/
|
||||
private boolean uploadToDocs(Track track) {
|
||||
GDataWrapper wiseWrapper = new GDataWrapper();
|
||||
wiseWrapper.setAuthManager(wiseAuth);
|
||||
wiseWrapper.setRetryOnAuthFailure(true);
|
||||
GDataWrapper docListWrapper = new GDataWrapper();
|
||||
docListWrapper.setAuthManager(docListAuth);
|
||||
docListWrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
GDataWrapper trixWrapper = new GDataWrapper();
|
||||
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 +184,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 +205,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 +244,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 +261,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 +278,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);
|
||||
@@ -308,222 +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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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\" ");
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* 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.mymaps.MyMapsConstants;
|
||||
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 docListWrapper, final String name) throws IOException {
|
||||
final AtomicReference<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
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("<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 docListWrapper,
|
||||
final String title) throws IOException {
|
||||
final AtomicReference<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
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<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
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("<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(">");
|
||||
}
|
||||
}
|
||||
@@ -243,6 +243,10 @@ public class GDataWrapper {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public AuthManager getAuthManager() {
|
||||
return auth;
|
||||
}
|
||||
|
||||
public void setRetryOnAuthFailure(boolean retry) {
|
||||
retryOnAuthFailure = retry;
|
||||
}
|
||||
|
||||
@@ -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<Void>() {
|
||||
@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 <T> IAnswer<T> stubCursorToArray(
|
||||
final MatrixCursor cursor, final T[] values) {
|
||||
return new IAnswer<T>() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
+159
@@ -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 =
|
||||
"<entry xmlns='http://www.w3.org/2005/Atom' "
|
||||
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/2006/extended'>"
|
||||
+ "<gsx:name><![CDATA[trackName]]></gsx:name>"
|
||||
+ "<gsx:description><![CDATA[trackDescription]]></gsx:description>"
|
||||
// We format the date here because we can't guarantee/force the timezone
|
||||
+ "<gsx:date><![CDATA[" + String.format("%tc", TIME) + "]]></gsx:date>"
|
||||
+ "<gsx:totaltime><![CDATA[0:00:05]]></gsx:totaltime>"
|
||||
+ "<gsx:movingtime><![CDATA[0:00:04]]></gsx:movingtime>"
|
||||
+ "<gsx:distance><![CDATA[12.43]]></gsx:distance>"
|
||||
+ "<gsx:distanceunit><![CDATA[mile]]></gsx:distanceunit>"
|
||||
+ "<gsx:averagespeed><![CDATA[8,947.75]]></gsx:averagespeed>"
|
||||
+ "<gsx:averagemovingspeed><![CDATA[11,184.68]]>"
|
||||
+ "</gsx:averagemovingspeed>"
|
||||
+ "<gsx:maxspeed><![CDATA[3,355.40]]></gsx:maxspeed>"
|
||||
+ "<gsx:speedunit><![CDATA[mph]]></gsx:speedunit>"
|
||||
+ "<gsx:elevationgain><![CDATA[19,685]]></gsx:elevationgain>"
|
||||
+ "<gsx:minelevation><![CDATA[-1,640]]></gsx:minelevation>"
|
||||
+ "<gsx:maxelevation><![CDATA[1,804]]></gsx:maxelevation>"
|
||||
+ "<gsx:elevationunit><![CDATA[feet]]></gsx:elevationunit>"
|
||||
+ "<gsx:map>"
|
||||
+ "<![CDATA[http://maps.google.com/maps/ms?msa=0&msid=trackMapId]]>"
|
||||
+ "</gsx:map>"
|
||||
+ "</entry>";
|
||||
|
||||
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(
|
||||
"<gsx:distanceunit><![CDATA[km]]></gsx:distanceunit>"));
|
||||
assertTrue(docsHelper.writtenData.contains(
|
||||
"<gsx:speedunit><![CDATA[kph]]></gsx:speedunit>"));
|
||||
assertTrue(docsHelper.writtenData.contains(
|
||||
"<gsx:elevationunit><![CDATA[meter]]></gsx:elevationunit>"));
|
||||
|
||||
assertTrue(docsHelper.writtenData.contains(
|
||||
"<gsx:distance><![CDATA[20.00]]></gsx:distance>"));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -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("<gsx:tag><![CDATA[value]]></gsx:tag>",
|
||||
new DocsTagBuilder(false).append("tag", "value").build());
|
||||
}
|
||||
|
||||
public void testMultiAppend() {
|
||||
String actual = new DocsTagBuilder(false)
|
||||
.append("tag1", "value1")
|
||||
.append("tag2", "value2")
|
||||
.build();
|
||||
|
||||
assertEquals("<gsx:tag1><![CDATA[value1]]></gsx:tag1>"
|
||||
+ "<gsx:tag2><![CDATA[value2]]></gsx:tag2>", actual);
|
||||
}
|
||||
|
||||
public void testAppendLargeUnits_imperial() {
|
||||
assertEquals("<gsx:tag><![CDATA[62.14]]></gsx:tag>",
|
||||
new DocsTagBuilder(false).appendLargeUnits("tag", 100.0).build());
|
||||
}
|
||||
|
||||
public void testAppendLargeUnits_metric() {
|
||||
assertEquals("<gsx:tag><![CDATA[100.00]]></gsx:tag>",
|
||||
new DocsTagBuilder(true).appendLargeUnits("tag", 100.0).build());
|
||||
}
|
||||
|
||||
public void testAppendSmallUnits_imperial() {
|
||||
assertEquals("<gsx:tag><![CDATA[328]]></gsx:tag>",
|
||||
new DocsTagBuilder(false).appendSmallUnits("tag", 100.0).build());
|
||||
}
|
||||
|
||||
public void testAppendSmallUnits_metric() {
|
||||
assertEquals("<gsx:tag><![CDATA[100]]></gsx:tag>",
|
||||
new DocsTagBuilder(true).appendSmallUnits("tag", 100.0).build());
|
||||
}
|
||||
}
|
||||
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user