forked from upstream-mirrors/OpenTracks
Update gdata api for Google spreadsheet.
This commit is contained in:
@@ -13,59 +13,74 @@
|
||||
* 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.Constants;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
|
||||
import com.google.android.apps.mytracks.io.gdata.docs.DocumentsClient;
|
||||
import com.google.android.apps.mytracks.io.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.android.apps.mytracks.io.gdata.docs.XmlDocsGDataParserFactory;
|
||||
import com.google.android.apps.mytracks.io.fusiontables.SendFusionTablesUtils;
|
||||
import com.google.android.apps.mytracks.io.maps.SendMapsUtils;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.AbstractSendAsyncTask;
|
||||
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
|
||||
import com.google.android.apps.mytracks.io.sync.SyncUtils;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.gms.auth.GoogleAuthException;
|
||||
import com.google.android.gms.auth.UserRecoverableAuthException;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
import com.google.api.client.auth.oauth2.BearerToken;
|
||||
import com.google.api.client.auth.oauth2.Credential;
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
|
||||
import com.google.api.client.http.ByteArrayContent;
|
||||
import com.google.api.services.drive.Drive;
|
||||
import com.google.api.services.drive.model.File;
|
||||
import com.google.api.services.drive.model.FileList;
|
||||
import com.google.gdata.client.spreadsheet.SpreadsheetService;
|
||||
import com.google.gdata.data.spreadsheet.ListEntry;
|
||||
import com.google.gdata.data.spreadsheet.WorksheetEntry;
|
||||
import com.google.gdata.data.spreadsheet.WorksheetFeed;
|
||||
import com.google.gdata.util.ServiceException;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* AsyncTask to send a track to Google Docs.
|
||||
*
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class SendDocsAsyncTask extends AbstractSendAsyncTask {
|
||||
private static final int PROGRESS_GET_SPREADSHEET_ID = 0;
|
||||
private static final int PROGRESS_CREATE_SPREADSHEET = 25;
|
||||
private static final int PROGRESS_GET_WORKSHEET_ID = 50;
|
||||
private static final int PROGRESS_ADD_TRACK_INFO = 75;
|
||||
private static final int PROGRESS_COMPLETE = 100;
|
||||
|
||||
private static final String TAG = SendDocsAsyncTask.class.getSimpleName();
|
||||
private static final String
|
||||
GOOGLE_SPREADSHEET_MIME_TYPE = "application/vnd.google-apps.spreadsheet";
|
||||
private static final String
|
||||
OPENDOCUMENT_SPREADSHEET_MIME_TYPE = "application/x-vnd.oasis.opendocument.spreadsheet";
|
||||
private static final String GET_SPREADSHEET_QUERY =
|
||||
"'root' in parents and title = '%s' and mimeType = '" + GOOGLE_SPREADSHEET_MIME_TYPE
|
||||
+ "' and trashed = false";
|
||||
private static final String
|
||||
GET_WORKSHEETS_URI = "https://spreadsheets.google.com/feeds/worksheets/%s/private/full";
|
||||
|
||||
private static final int PROGRESS_GET_SPREADSHEET_ID = 0;
|
||||
private static final int PROGRESS_GET_WORKSHEET_URL = 35;
|
||||
private static final int PROGRESS_ADD_TRACK_INFO = 70;
|
||||
private static final int PROGRESS_COMPLETE = 100;
|
||||
|
||||
private final long trackId;
|
||||
private final Account account;
|
||||
private final Context context;
|
||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||
private final GDataClient gDataClient;
|
||||
private final DocumentsClient documentsClient;
|
||||
private final SpreadsheetsClient spreadsheetsClient;
|
||||
|
||||
// The following variables are for per upload states
|
||||
private String documentsAuthToken;
|
||||
private String spreadsheetsAuthToken;
|
||||
private String spreadsheetId;
|
||||
private String worksheetId;
|
||||
|
||||
public SendDocsAsyncTask(SendDocsActivity activity, long trackId, Account account) {
|
||||
super(activity);
|
||||
@@ -74,242 +89,209 @@ public class SendDocsAsyncTask extends AbstractSendAsyncTask {
|
||||
|
||||
context = activity.getApplicationContext();
|
||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
gDataClient = GDataClientFactory.getGDataClient(context);
|
||||
documentsClient = new DocumentsClient(
|
||||
gDataClient, new XmlDocsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
spreadsheetsClient = new SpreadsheetsClient(
|
||||
gDataClient, new XmlDocsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeConnection() {
|
||||
if (gDataClient != null) {
|
||||
gDataClient.close();
|
||||
}
|
||||
}
|
||||
protected void closeConnection() {}
|
||||
|
||||
@Override
|
||||
protected void saveResult() {
|
||||
// No action for Google Docs
|
||||
}
|
||||
protected void saveResult() {}
|
||||
|
||||
@Override
|
||||
protected boolean performTask() {
|
||||
// Reset the per upload states
|
||||
documentsAuthToken = null;
|
||||
spreadsheetsAuthToken = null;
|
||||
spreadsheetId = null;
|
||||
worksheetId = null;
|
||||
|
||||
try {
|
||||
documentsAuthToken = AccountManager.get(context).blockingGetAuthToken(
|
||||
account, documentsClient.getServiceName(), false);
|
||||
spreadsheetsAuthToken = AccountManager.get(context).blockingGetAuthToken(
|
||||
account, spreadsheetsClient.getServiceName(), false);
|
||||
} catch (OperationCanceledException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryTask();
|
||||
} catch (AuthenticatorException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryTask();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryTask();
|
||||
}
|
||||
SpreadsheetService spreadsheetService = new SpreadsheetService(
|
||||
"MyTracks-" + SystemUtils.getMyTracksVersion(context));
|
||||
Credential credential = new Credential(BearerToken.authorizationHeaderAccessMethod());
|
||||
|
||||
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.d(TAG, "No track for " + trackId);
|
||||
return false;
|
||||
}
|
||||
credential.setAccessToken(
|
||||
SendToGoogleUtils.getToken(context, account.name, SendToGoogleUtils.SPREADSHEET_SCOPE));
|
||||
spreadsheetService.setOAuth2Credentials(credential);
|
||||
|
||||
String title = context.getString(R.string.my_tracks_app_name);
|
||||
if (track.getCategory() != null && !track.getCategory().equals("")) {
|
||||
title += "-" + track.getCategory();
|
||||
}
|
||||
|
||||
// Get the spreadsheet ID
|
||||
publishProgress(PROGRESS_GET_SPREADSHEET_ID);
|
||||
if (!fetchSpreadSheetId(title, false)) {
|
||||
return retryTask();
|
||||
}
|
||||
|
||||
// Create a new spreadsheet if necessary
|
||||
publishProgress(PROGRESS_CREATE_SPREADSHEET);
|
||||
if (spreadsheetId == null) {
|
||||
if (!createSpreadSheet(title)) {
|
||||
Log.d(TAG, "Unable to create a new spreadsheet");
|
||||
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.d(TAG, "No track for " + trackId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The previous creation might have succeeded even though GData
|
||||
// reported an error. Seems to be a know bug.
|
||||
// See http://code.google.com/p/gdata-issues/issues/detail?id=929
|
||||
// Try to find the created spreadsheet.
|
||||
if (spreadsheetId == null) {
|
||||
if (!fetchSpreadSheetId(title, true)) {
|
||||
Log.d(TAG, "Unable to check if the new spreadsheet is created");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spreadsheetId == null) {
|
||||
Log.d(TAG, "Unable to create a new spreadsheet");
|
||||
return false;
|
||||
}
|
||||
String title = context.getString(R.string.my_tracks_app_name);
|
||||
if (track.getCategory() != null && !track.getCategory().equals("")) {
|
||||
title += "-" + track.getCategory();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the worksheet ID
|
||||
publishProgress(PROGRESS_GET_WORKSHEET_ID);
|
||||
if (!fetchWorksheetId()) {
|
||||
publishProgress(PROGRESS_GET_SPREADSHEET_ID);
|
||||
String spreadsheetId = getSpreadSheetId(title);
|
||||
if (spreadsheetId == null) {
|
||||
Log.d(TAG, "Unable to get the spreadsheet ID for " + title);
|
||||
return false;
|
||||
}
|
||||
|
||||
publishProgress(PROGRESS_GET_WORKSHEET_URL);
|
||||
URL worksheetUrl = getWorksheetUrl(spreadsheetService, spreadsheetId);
|
||||
if (worksheetUrl == null) {
|
||||
Log.d(TAG, "Unable to get the worksheet url for " + spreadsheetId);
|
||||
return false;
|
||||
}
|
||||
|
||||
publishProgress(PROGRESS_ADD_TRACK_INFO);
|
||||
if (!addTrackInfo(spreadsheetService, worksheetUrl, track)) {
|
||||
Log.d(TAG, "Unable to add track info");
|
||||
return false;
|
||||
}
|
||||
|
||||
publishProgress(PROGRESS_COMPLETE);
|
||||
return true;
|
||||
} catch (UserRecoverableAuthException e) {
|
||||
SendToGoogleUtils.sendNotification(
|
||||
context, account.name, e.getIntent(), SendToGoogleUtils.SPREADSHEET_NOTIFICATION_ID);
|
||||
return false;
|
||||
} catch (GoogleAuthException e) {
|
||||
Log.e(TAG, "GoogleaAuthException", e);
|
||||
return retryTask();
|
||||
} catch (UserRecoverableAuthIOException e) {
|
||||
SendToGoogleUtils.sendNotification(
|
||||
context, account.name, e.getIntent(), SendToGoogleUtils.SPREADSHEET_NOTIFICATION_ID);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "IOException", e);
|
||||
return retryTask();
|
||||
} catch (ServiceException e) {
|
||||
Log.e(TAG, "ServiceException", e);
|
||||
return retryTask();
|
||||
}
|
||||
if (worksheetId == null) {
|
||||
Log.d(TAG, "Unable to get a worksheet ID");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the track info
|
||||
publishProgress(PROGRESS_ADD_TRACK_INFO);
|
||||
if (!addTrackInfo(track)) {
|
||||
Log.d(TAG, "Unable to add track info");
|
||||
return false;
|
||||
}
|
||||
|
||||
publishProgress(PROGRESS_COMPLETE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidateToken() {
|
||||
AccountManager.get(context).invalidateAuthToken(Constants.ACCOUNT_TYPE, documentsAuthToken);
|
||||
AccountManager.get(context).invalidateAuthToken(Constants.ACCOUNT_TYPE, spreadsheetsAuthToken);
|
||||
protected void invalidateToken() {}
|
||||
|
||||
/**
|
||||
* Gets the spreadsheet id.
|
||||
*
|
||||
* @param fileName the file name
|
||||
*/
|
||||
private String getSpreadSheetId(String fileName) throws IOException, GoogleAuthException {
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
GoogleAccountCredential driveCredential = SendToGoogleUtils.getGoogleAccountCredential(
|
||||
context, account.name, SendToGoogleUtils.DRIVE_SCOPE);
|
||||
if (driveCredential == null) {
|
||||
return null;
|
||||
}
|
||||
Drive drive = SyncUtils.getDriveService(driveCredential);
|
||||
com.google.api.services.drive.Drive.Files.List list = drive.files()
|
||||
.list().setQ(String.format(Locale.US, GET_SPREADSHEET_QUERY, fileName));
|
||||
FileList result = list.execute();
|
||||
for (File file : result.getItems()) {
|
||||
if (file.getSharedWithMeDate() == null) {
|
||||
return file.getId();
|
||||
}
|
||||
}
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = context.getResources().openRawResource(R.raw.mytracks_empty_spreadsheet);
|
||||
byte[] b = new byte[inputStream.available()];
|
||||
inputStream.read(b);
|
||||
ByteArrayContent fileContent = new ByteArrayContent(OPENDOCUMENT_SPREADSHEET_MIME_TYPE, b);
|
||||
|
||||
File file = new File();
|
||||
file.setTitle(fileName);
|
||||
file.setMimeType(OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
|
||||
return drive.files().insert(file, fileContent).setConvert(true).execute().getId();
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the spreadsheet id. Sets the instance variable
|
||||
* {@link SendDocsAsyncTask#spreadsheetId}.
|
||||
*
|
||||
* @param title the spreadsheet title
|
||||
* @param waitFirst wait before checking
|
||||
* @return true if completes.
|
||||
* Gets the worksheet url.
|
||||
*
|
||||
* @param spreadsheetService the spreadsheet service
|
||||
* @param spreadsheetId the spreadsheet id
|
||||
*/
|
||||
private boolean fetchSpreadSheetId(String title, boolean waitFirst) {
|
||||
private URL getWorksheetUrl(SpreadsheetService spreadsheetService, String spreadsheetId)
|
||||
throws IOException, ServiceException {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
URL url = new URL(String.format(Locale.US, GET_WORKSHEETS_URI, spreadsheetId));
|
||||
WorksheetFeed feed = spreadsheetService.getFeed(url, WorksheetFeed.class);
|
||||
List<WorksheetEntry> worksheets = feed.getEntries();
|
||||
|
||||
if (waitFirst) {
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(TAG, "Unable to wait", e);
|
||||
return false;
|
||||
}
|
||||
if (worksheets.size() > 0) {
|
||||
return worksheets.get(0).getListFeedUrl();
|
||||
}
|
||||
|
||||
try {
|
||||
spreadsheetId = SendDocsUtils.getSpreadsheetId(title, documentsClient, documentsAuthToken);
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
} catch (HttpException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spreadsheetId == null) {
|
||||
// Waiting a few seconds and trying again. Maybe the server just had a
|
||||
// hickup (unfortunately that happens quite a lot...).
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(TAG, "Unable to wait", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
spreadsheetId = SendDocsUtils.getSpreadsheetId(title, documentsClient, documentsAuthToken);
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
} catch (HttpException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a spreadsheet. If successful, sets the instance variable
|
||||
* {@link SendDocsAsyncTask#spreadsheetId}.
|
||||
*
|
||||
* @param spreadsheetTitle the spreadsheet title
|
||||
* @return true if completes.
|
||||
*/
|
||||
private boolean createSpreadSheet(String spreadsheetTitle) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
spreadsheetId = SendDocsUtils.createSpreadsheet(
|
||||
spreadsheetTitle, documentsAuthToken, context);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to create spreadsheet", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the worksheet ID. Sets the instance variable
|
||||
* {@link SendDocsAsyncTask#worksheetId}.
|
||||
*
|
||||
* @return true if completes.
|
||||
*/
|
||||
private boolean fetchWorksheetId() {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
worksheetId = SendDocsUtils.getWorksheetId(
|
||||
spreadsheetId, spreadsheetsClient, spreadsheetsAuthToken);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to fetch worksheet ID", e);
|
||||
return false;
|
||||
} catch (AuthenticationException e) {
|
||||
Log.d(TAG, "Unable to fetch worksheet ID", e);
|
||||
return false;
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "Unable to fetch worksheet ID", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds track info to a worksheet.
|
||||
*
|
||||
* @param spreadsheetService the spreadsheet service
|
||||
* @param worksheetUrl the worksheet url
|
||||
* @param track the track
|
||||
* @return true if completes.
|
||||
*/
|
||||
private boolean addTrackInfo(Track track) {
|
||||
private boolean addTrackInfo(SpreadsheetService spreadsheetService, URL worksheetUrl, Track track)
|
||||
throws IOException, ServiceException {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
SendDocsUtils.addTrackInfo(track, spreadsheetId, worksheetId, spreadsheetsAuthToken, context);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to add track info", e);
|
||||
return false;
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
boolean metricUnits = PreferencesUtils.getBoolean(
|
||||
context, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
|
||||
String distanceUnit = context.getString(
|
||||
metricUnits ? R.string.unit_kilometer : R.string.unit_mile);
|
||||
String speedUnit = context.getString(
|
||||
metricUnits ? R.string.unit_kilometer_per_hour : R.string.unit_mile_per_hour);
|
||||
String elevationUnit = context.getString(
|
||||
metricUnits ? R.string.unit_meter : R.string.unit_feet);
|
||||
ListEntry row = new ListEntry();
|
||||
|
||||
row.getCustomElements().setValueLocal("name", track.getName());
|
||||
row.getCustomElements().setValueLocal("description", track.getDescription());
|
||||
row.getCustomElements()
|
||||
.setValueLocal("date", StringUtils.formatDateTime(context, tripStatistics.getStartTime()));
|
||||
row.getCustomElements().setValueLocal(
|
||||
"totaltime", StringUtils.formatElapsedTimeWithHour(tripStatistics.getTotalTime()));
|
||||
row.getCustomElements().setValueLocal(
|
||||
"movingtime", StringUtils.formatElapsedTimeWithHour(tripStatistics.getMovingTime()));
|
||||
row.getCustomElements().setValueLocal(
|
||||
"distance", SendDocsUtils.getDistance(tripStatistics.getTotalDistance(), metricUnits));
|
||||
row.getCustomElements().setValueLocal("distanceunit", distanceUnit);
|
||||
row.getCustomElements().setValueLocal(
|
||||
"averagespeed", SendDocsUtils.getSpeed(tripStatistics.getAverageSpeed(), metricUnits));
|
||||
row.getCustomElements().setValueLocal("averagemovingspeed",
|
||||
SendDocsUtils.getSpeed(tripStatistics.getAverageMovingSpeed(), metricUnits));
|
||||
row.getCustomElements().setValueLocal(
|
||||
"maxspeed", SendDocsUtils.getSpeed(tripStatistics.getMaxSpeed(), metricUnits));
|
||||
row.getCustomElements().setValueLocal("speedunit", speedUnit);
|
||||
row.getCustomElements().setValueLocal("elevationgain",
|
||||
SendDocsUtils.getElevation(tripStatistics.getTotalElevationGain(), metricUnits));
|
||||
row.getCustomElements().setValueLocal(
|
||||
"minelevation", SendDocsUtils.getElevation(tripStatistics.getMinElevation(), metricUnits));
|
||||
row.getCustomElements().setValueLocal(
|
||||
"maxelevation", SendDocsUtils.getElevation(tripStatistics.getMaxElevation(), metricUnits));
|
||||
row.getCustomElements().setValueLocal("elevationunit", elevationUnit);
|
||||
|
||||
String map = SendMapsUtils.getMapUrl(track);
|
||||
if (map == null) {
|
||||
map = context.getString(R.string.value_unknown);
|
||||
}
|
||||
return true;
|
||||
row.getCustomElements().setValueLocal("map", map);
|
||||
|
||||
String fusionTable = SendFusionTablesUtils.getMapUrl(track);
|
||||
if (fusionTable == null) {
|
||||
fusionTable = context.getString(R.string.value_unknown);
|
||||
}
|
||||
row.getCustomElements().setValueLocal("fusiontable", fusionTable);
|
||||
|
||||
ListEntry result = spreadsheetService.insert(worksheetUrl, row);
|
||||
return result != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,76 +13,20 @@
|
||||
* 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.fusiontables.SendFusionTablesUtils;
|
||||
import com.google.android.apps.mytracks.io.gdata.docs.DocumentsClient;
|
||||
import com.google.android.apps.mytracks.io.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.android.apps.mytracks.io.gdata.docs.SpreadsheetsClient.WorksheetEntry;
|
||||
import com.google.android.apps.mytracks.io.maps.SendMapsUtils;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
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.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
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.net.URLEncoder;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utilities for sending a track to Google Docs.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Matthew Simmons
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class SendDocsUtils {
|
||||
@VisibleForTesting
|
||||
public static final String GET_SPREADSHEET_BY_TITLE_URI =
|
||||
"https://docs.google.com/feeds/documents/private/full?"
|
||||
+ "category=mine,spreadsheet&title=%s&title-exact=true";
|
||||
private static final String CREATE_SPREADSHEET_URI =
|
||||
"https://docs.google.com/feeds/documents/private/full";
|
||||
private static final String GET_WORKSHEETS_URI =
|
||||
"https://spreadsheets.google.com/feeds/worksheets/%s/private/full";
|
||||
@VisibleForTesting
|
||||
public static final String GET_WORKSHEET_URI =
|
||||
"https://spreadsheets.google.com/feeds/list/%s/%s/private/full";
|
||||
@VisibleForTesting
|
||||
public static final String SPREADSHEET_ID_PREFIX =
|
||||
"https://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
|
||||
@VisibleForTesting
|
||||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
@VisibleForTesting
|
||||
public static final String ATOM_FEED_MIME_TYPE = "application/atom+xml";
|
||||
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
|
||||
"application/x-vnd.oasis.opendocument.spreadsheet";
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
@VisibleForTesting
|
||||
public static final String AUTHORIZATION_PREFIX = "GoogleLogin auth=";
|
||||
|
||||
private static final String SLUG = "Slug";
|
||||
|
||||
// Google Docs can only parse numbers in the English locale.
|
||||
private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance(Locale.ENGLISH);
|
||||
@@ -94,273 +38,15 @@ public class SendDocsUtils {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(2);
|
||||
}
|
||||
|
||||
private static final String TAG = SendDocsUtils.class.getSimpleName();
|
||||
|
||||
private SendDocsUtils() {}
|
||||
|
||||
/**
|
||||
* Gets the spreadsheet id of a spreadsheet. Returns null if the spreadsheet
|
||||
* doesn't exist.
|
||||
*
|
||||
* @param title the title of the spreadsheet
|
||||
* @param documentsClient the documents client
|
||||
* @param authToken the auth token
|
||||
* @return spreadsheet id or null if it doesn't exist.
|
||||
*/
|
||||
public static String getSpreadsheetId(
|
||||
String title, DocumentsClient documentsClient, String authToken)
|
||||
throws IOException, ParseException, HttpException {
|
||||
GDataParser gDataParser = null;
|
||||
try {
|
||||
String uri = String.format(
|
||||
Locale.US, GET_SPREADSHEET_BY_TITLE_URI, URLEncoder.encode(title, "UTF-8"));
|
||||
gDataParser = documentsClient.getParserForFeed(Entry.class, uri, authToken);
|
||||
gDataParser.init();
|
||||
|
||||
while (gDataParser.hasMoreData()) {
|
||||
Entry entry = gDataParser.readNextEntry(null);
|
||||
String entryTitle = entry.getTitle();
|
||||
if (entryTitle.equals(title)) {
|
||||
return getEntryId(entry);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (gDataParser != null) {
|
||||
gDataParser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id from an entry. Returns null if not available.
|
||||
*
|
||||
* @param entry the entry
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getEntryId(Entry entry) {
|
||||
String entryId = entry.getId();
|
||||
if (entryId.startsWith(SPREADSHEET_ID_PREFIX)) {
|
||||
return entryId.substring(SPREADSHEET_ID_PREFIX.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new spreadsheet with the given title. Returns the spreadsheet ID
|
||||
* if successful. Returns null otherwise. Note that it is possible that a new
|
||||
* spreadsheet is created, but the returned ID is null.
|
||||
*
|
||||
* @param title the title
|
||||
* @param authToken the auth token
|
||||
* @param context the context
|
||||
*/
|
||||
public static String createSpreadsheet(String title, String authToken, Context context)
|
||||
throws IOException {
|
||||
URL url = new URL(CREATE_SPREADSHEET_URI);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE, OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
|
||||
conn.addRequestProperty(SLUG, title);
|
||||
conn.addRequestProperty(AUTHORIZATION, AUTHORIZATION_PREFIX + authToken);
|
||||
conn.setDoOutput(true);
|
||||
OutputStream outputStream = conn.getOutputStream();
|
||||
ResourceUtils.readBinaryFileToOutputStream(
|
||||
context, R.raw.mytracks_empty_spreadsheet, outputStream);
|
||||
|
||||
// Get the response
|
||||
BufferedReader bufferedReader = null;
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
try {
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
resultBuilder.append(line);
|
||||
}
|
||||
} 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.
|
||||
Log.d(TAG, "Unable to read result after creating a spreadsheet", e);
|
||||
return null;
|
||||
} finally {
|
||||
outputStream.close();
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
}
|
||||
return getNewSpreadsheetId(resultBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the spreadsheet id from a create spreadsheet result.
|
||||
*
|
||||
* @param result the create spreadsheet result
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getNewSpreadsheetId(String result) {
|
||||
int idTagIndex = result.indexOf("<id>");
|
||||
if (idTagIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int idTagCloseIndex = result.indexOf("</id>", idTagIndex);
|
||||
if (idTagCloseIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int idStringStart = result.indexOf(SPREADSHEET_ID_PREFIX, idTagIndex);
|
||||
if (idStringStart == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.substring(idStringStart + SPREADSHEET_ID_PREFIX.length(), idTagCloseIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first worksheet ID of a spreadsheet. Returns null if not
|
||||
* available.
|
||||
*
|
||||
* @param spreadsheetId the spreadsheet ID
|
||||
* @param spreadsheetClient the spreadsheet client
|
||||
* @param authToken the auth token
|
||||
*/
|
||||
public static String getWorksheetId(
|
||||
String spreadsheetId, SpreadsheetsClient spreadsheetClient, String authToken)
|
||||
throws IOException, AuthenticationException, ParseException {
|
||||
GDataParser gDataParser = null;
|
||||
try {
|
||||
String uri = String.format(Locale.US, GET_WORKSHEETS_URI, spreadsheetId);
|
||||
gDataParser = spreadsheetClient.getParserForWorksheetsFeed(uri, authToken);
|
||||
gDataParser.init();
|
||||
if (!gDataParser.hasMoreData()) {
|
||||
Log.d(TAG, "No worksheet");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the first worksheet
|
||||
WorksheetEntry worksheetEntry =
|
||||
(WorksheetEntry) gDataParser.readNextEntry(new WorksheetEntry());
|
||||
return getWorksheetEntryId(worksheetEntry);
|
||||
} finally {
|
||||
if (gDataParser != null) {
|
||||
gDataParser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the worksheet id from a worksheet entry. Returns null if not available.
|
||||
*
|
||||
* @param entry the worksheet entry
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getWorksheetEntryId(WorksheetEntry entry) {
|
||||
String id = entry.getId();
|
||||
int lastSlash = id.lastIndexOf('/');
|
||||
if (lastSlash == -1) {
|
||||
Log.d(TAG, "No id");
|
||||
return null;
|
||||
}
|
||||
return id.substring(lastSlash + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a track's info as a row in a worksheet.
|
||||
*
|
||||
* @param track the track
|
||||
* @param spreadsheetId the spreadsheet ID
|
||||
* @param worksheetId the worksheet ID
|
||||
* @param authToken the auth token
|
||||
* @param context the context
|
||||
*/
|
||||
public static void addTrackInfo(
|
||||
Track track, String spreadsheetId, String worksheetId, String authToken, Context context)
|
||||
throws IOException {
|
||||
String worksheetUri = String.format(Locale.US, GET_WORKSHEET_URI, spreadsheetId, worksheetId);
|
||||
boolean metricUnits = PreferencesUtils.getBoolean(
|
||||
context, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
|
||||
addRow(worksheetUri, getRowContent(track, metricUnits, context), authToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the row content containing the track's info.
|
||||
*
|
||||
* @param track the track
|
||||
* @param metricUnits true to use metric
|
||||
* @param context the context
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getRowContent(Track track, boolean metricUnits, Context context) {
|
||||
TripStatistics stats = track.getTripStatistics();
|
||||
|
||||
String distanceUnit = context.getString(
|
||||
metricUnits ? R.string.unit_kilometer : R.string.unit_mile);
|
||||
String speedUnit = context.getString(
|
||||
metricUnits ? R.string.unit_kilometer_per_hour : R.string.unit_mile_per_hour);
|
||||
String elevationUnit = context.getString(
|
||||
metricUnits ? R.string.unit_meter : R.string.unit_feet);
|
||||
|
||||
StringBuilder builder = new StringBuilder().append("<entry xmlns='http://www.w3.org/2005/Atom' "
|
||||
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/2006/extended'>");
|
||||
appendTag(builder, "name", track.getName());
|
||||
appendTag(builder, "description", track.getDescription());
|
||||
appendTag(builder, "date", StringUtils.formatDateTime(context, stats.getStartTime()));
|
||||
appendTag(builder, "totaltime", StringUtils.formatElapsedTimeWithHour(stats.getTotalTime()));
|
||||
appendTag(builder, "movingtime", StringUtils.formatElapsedTimeWithHour(stats.getMovingTime()));
|
||||
appendTag(builder, "distance", getDistance(stats.getTotalDistance(), metricUnits));
|
||||
appendTag(builder, "distanceunit", distanceUnit);
|
||||
appendTag(builder, "averagespeed", getSpeed(stats.getAverageSpeed(), metricUnits));
|
||||
appendTag(builder, "averagemovingspeed", getSpeed(stats.getAverageMovingSpeed(), metricUnits));
|
||||
appendTag(builder, "maxspeed", getSpeed(stats.getMaxSpeed(), metricUnits));
|
||||
appendTag(builder, "speedunit", speedUnit);
|
||||
appendTag(builder, "elevationgain", getElevation(stats.getTotalElevationGain(), metricUnits));
|
||||
appendTag(builder, "minelevation", getElevation(stats.getMinElevation(), metricUnits));
|
||||
appendTag(builder, "maxelevation", getElevation(stats.getMaxElevation(), metricUnits));
|
||||
appendTag(builder, "elevationunit", elevationUnit);
|
||||
|
||||
String map = SendMapsUtils.getMapUrl(track);
|
||||
if (map == null) {
|
||||
map = context.getString(R.string.value_unknown);
|
||||
}
|
||||
appendTag(builder, "map", map);
|
||||
|
||||
String fusionTable = SendFusionTablesUtils.getMapUrl(track);
|
||||
if (fusionTable == null) {
|
||||
fusionTable = context.getString(R.string.value_unknown);
|
||||
}
|
||||
appendTag(builder, "fusiontable", fusionTable);
|
||||
|
||||
builder.append("</entry>");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a name-value pair as a gsx tag to a string builder.
|
||||
*
|
||||
* @param stringBuilder the string builder
|
||||
* @param name the name
|
||||
* @param value the value
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static void appendTag(StringBuilder stringBuilder, String name, String value) {
|
||||
stringBuilder
|
||||
.append("<gsx:")
|
||||
.append(name)
|
||||
.append(">")
|
||||
.append(StringUtils.formatCData(value))
|
||||
.append("</gsx:")
|
||||
.append(name)
|
||||
.append(">");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the distance. Performs unit conversion and formatting.
|
||||
*
|
||||
*
|
||||
* @param distanceInMeter the distance in meters
|
||||
* @param metricUnits true to use metric
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final String getDistance(double distanceInMeter, boolean metricUnits) {
|
||||
public static final String getDistance(double distanceInMeter, boolean metricUnits) {
|
||||
double distanceInKilometer = distanceInMeter * UnitConversions.M_TO_KM;
|
||||
double distance = metricUnits ? distanceInKilometer
|
||||
: distanceInKilometer * UnitConversions.KM_TO_MI;
|
||||
@@ -369,12 +55,11 @@ public class SendDocsUtils {
|
||||
|
||||
/**
|
||||
* Gets the speed. Performs unit conversion and formatting.
|
||||
*
|
||||
*
|
||||
* @param speedInMeterPerSecond the speed in meters per second
|
||||
* @param metricUnits true to use metric
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final String getSpeed(double speedInMeterPerSecond, boolean metricUnits) {
|
||||
public static final String getSpeed(double speedInMeterPerSecond, boolean metricUnits) {
|
||||
double speedInKilometerPerHour = speedInMeterPerSecond * UnitConversions.MS_TO_KMH;
|
||||
double speed = metricUnits ? speedInKilometerPerHour
|
||||
: speedInKilometerPerHour * UnitConversions.KM_TO_MI;
|
||||
@@ -383,40 +68,12 @@ public class SendDocsUtils {
|
||||
|
||||
/**
|
||||
* Gets the elevation. Performs unit conversion and formatting.
|
||||
*
|
||||
*
|
||||
* @param elevationInMeter the elevation value in meters
|
||||
* @param metricUnits true to use metric
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final String getElevation(double elevationInMeter, boolean metricUnits) {
|
||||
public static final String getElevation(double elevationInMeter, boolean metricUnits) {
|
||||
double elevation = metricUnits ? elevationInMeter : elevationInMeter * UnitConversions.M_TO_FT;
|
||||
return INTEGER_FORMAT.format(elevation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a row to a Google Spreadsheet worksheet.
|
||||
*
|
||||
* @param worksheetUri the worksheet URI
|
||||
* @param rowContent the row content
|
||||
* @param authToken the auth token
|
||||
*/
|
||||
private static final void addRow(String worksheetUri, String rowContent, String authToken)
|
||||
throws IOException {
|
||||
URL url = new URL(worksheetUri);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE, ATOM_FEED_MIME_TYPE);
|
||||
conn.addRequestProperty(AUTHORIZATION, AUTHORIZATION_PREFIX + authToken);
|
||||
conn.setDoOutput(true);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
|
||||
writer.write(rowContent);
|
||||
writer.flush();
|
||||
|
||||
// Get the response
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
while ((reader.readLine()) != null) {
|
||||
// Just read till the end
|
||||
}
|
||||
writer.close();
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.gdata.docs;
|
||||
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.GDataParserFactory;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
|
||||
/**
|
||||
* GDataServiceClient for accessing Google Documents. This is not a full
|
||||
* implementation.
|
||||
*/
|
||||
public class DocumentsClient extends GDataServiceClient {
|
||||
/** The name of the service, dictated to be 'wise' by the protocol. */
|
||||
public static final String SERVICE = "writely";
|
||||
|
||||
/**
|
||||
* Creates a new DocumentsClient.
|
||||
*
|
||||
* @param client The GDataClient that should be used to authenticate requests,
|
||||
* retrieve feeds, etc
|
||||
* @param parserFactory The GDataParserFactory that should be used to obtain
|
||||
* GDataParsers used by this client
|
||||
*/
|
||||
public DocumentsClient(GDataClient client, GDataParserFactory parserFactory) {
|
||||
super(client, parserFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return SERVICE;
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/*
|
||||
* Copyright 2007 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.gdata.docs;
|
||||
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.GDataParserFactory;
|
||||
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.data.StringUtils;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata.serializer.GDataSerializer;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* GDataServiceClient for accessing Google Spreadsheets. This client can access
|
||||
* and parse all of the Spreadsheets feed types: Spreadsheets feed, Worksheets
|
||||
* feed, List feed, and Cells feed. Read operations are supported on all feed
|
||||
* types, but only the List and Cells feeds support write operations. (This is a
|
||||
* limitation of the protocol, not this API. Such write access may be added to
|
||||
* the protocol in the future, requiring changes to this implementation.)
|
||||
*
|
||||
* Only 'private' visibility and 'full' projections are currently supported.
|
||||
*/
|
||||
public class SpreadsheetsClient extends GDataServiceClient {
|
||||
/** The name of the service, dictated to be 'wise' by the protocol. */
|
||||
public static final String SERVICE = "wise";
|
||||
|
||||
/** Standard base feed url for spreadsheets. */
|
||||
public static final String SPREADSHEETS_BASE_FEED_URL =
|
||||
"http://spreadsheets.google.com/feeds/spreadsheets/private/full";
|
||||
|
||||
/**
|
||||
* Represents an entry in a GData Spreadsheets meta-feed.
|
||||
*/
|
||||
public static class SpreadsheetEntry extends Entry { }
|
||||
|
||||
/**
|
||||
* Represents an entry in a GData Worksheets meta-feed.
|
||||
*/
|
||||
public static class WorksheetEntry extends Entry { }
|
||||
|
||||
/**
|
||||
* Creates a new SpreadsheetsClient. Uses the standard base URL for
|
||||
* spreadsheets feeds.
|
||||
*
|
||||
* @param client The GDataClient that should be used to authenticate requests,
|
||||
* retrieve feeds, etc
|
||||
*/
|
||||
public SpreadsheetsClient(GDataClient client,
|
||||
GDataParserFactory spreadsheetFactory) {
|
||||
super(client, spreadsheetFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return SERVICE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a parser for the specified feed type.
|
||||
*
|
||||
* @param feedEntryClass the Class of entry type that will be parsed, which
|
||||
* lets this method figure out which parser to create
|
||||
* @param feedUri the URI of the feed to be fetched and parsed
|
||||
* @param authToken the current authToken to use for the request
|
||||
* @return a parser for the indicated feed
|
||||
* @throws AuthenticationException if the authToken is not valid
|
||||
* @throws ParseException if the response from the server could not be parsed
|
||||
*/
|
||||
private GDataParser getParserForTypedFeed(
|
||||
Class<? extends Entry> feedEntryClass, String feedUri, String authToken)
|
||||
throws AuthenticationException, ParseException, IOException {
|
||||
GDataClient gDataClient = getGDataClient();
|
||||
GDataParserFactory gDataParserFactory = getGDataParserFactory();
|
||||
|
||||
try {
|
||||
InputStream is = gDataClient.getFeedAsStream(feedUri, authToken);
|
||||
return gDataParserFactory.createParser(feedEntryClass, is);
|
||||
} catch (HttpException e) {
|
||||
convertHttpExceptionForReads("Could not fetch parser feed.", e);
|
||||
return null; // never reached
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HTTP exception that happened while reading into the equivalent
|
||||
* local exception.
|
||||
*/
|
||||
public void convertHttpExceptionForReads(String message, HttpException cause)
|
||||
throws AuthenticationException, IOException {
|
||||
switch (cause.getStatusCode()) {
|
||||
case HttpException.SC_FORBIDDEN:
|
||||
case HttpException.SC_UNAUTHORIZED:
|
||||
throw new AuthenticationException(message, cause);
|
||||
case HttpException.SC_GONE:
|
||||
default:
|
||||
throw new IOException(message + ": " + cause.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry createEntry(String feedUri, String authToken, Entry entry)
|
||||
throws ParseException, IOException {
|
||||
|
||||
GDataParserFactory factory = getGDataParserFactory();
|
||||
GDataSerializer serializer = factory.createSerializer(entry);
|
||||
|
||||
InputStream is;
|
||||
try {
|
||||
is = getGDataClient().createEntry(feedUri, authToken, serializer);
|
||||
} catch (HttpException e) {
|
||||
convertHttpExceptionForWrites(entry.getClass(),
|
||||
"Could not update entry.", e);
|
||||
return null; // never reached.
|
||||
}
|
||||
|
||||
GDataParser parser = factory.createParser(entry.getClass(), is);
|
||||
try {
|
||||
return parser.parseStandaloneEntry();
|
||||
} finally {
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a GDataParser for the indicated feed. The parser can be used to
|
||||
* access the contents of URI. WARNING: because we cannot reliably infer the
|
||||
* feed type from the URI alone, this method assumes the default feed type!
|
||||
* This is probably NOT what you want. Please use the getParserFor[Type]Feed
|
||||
* methods.
|
||||
*
|
||||
* @param feedEntryClass
|
||||
* @param feedUri the URI of the feed to be fetched and parsed
|
||||
* @param authToken the current authToken to use for the request
|
||||
* @return a parser for the indicated feed
|
||||
* @throws ParseException if the response from the server could not be parsed
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public GDataParser getParserForFeed(
|
||||
Class feedEntryClass, String feedUri, String authToken)
|
||||
throws ParseException, IOException {
|
||||
try {
|
||||
return getParserForTypedFeed(SpreadsheetEntry.class, feedUri, authToken);
|
||||
} catch (AuthenticationException e) {
|
||||
throw new IOException("Authentication Failure: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a parser for a Worksheets meta-feed.
|
||||
*
|
||||
* @param feedUri the URI of the feed to be fetched and parsed
|
||||
* @param authToken the current authToken to use for the request
|
||||
* @return a parser for the indicated feed
|
||||
* @throws AuthenticationException if the authToken is not valid
|
||||
* @throws ParseException if the response from the server could not be parsed
|
||||
*/
|
||||
public GDataParser getParserForWorksheetsFeed(
|
||||
String feedUri, String authToken)
|
||||
throws AuthenticationException, ParseException, IOException {
|
||||
return getParserForTypedFeed(WorksheetEntry.class, feedUri, authToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entry. The URI to be updated is taken from <code>entry</code>.
|
||||
* Note that only entries in List and Cells feeds can be updated, so
|
||||
* <code>entry</code> must be of the corresponding type; other types will
|
||||
* result in an exception.
|
||||
*
|
||||
* @param entry the entry to be updated; must include its URI
|
||||
* @param authToken the current authToken to be used for the operation
|
||||
* @return An Entry containing the re-parsed version of the entry returned by
|
||||
* the server in response to the update
|
||||
* @throws ParseException if the server returned an error, if the server's
|
||||
* response was unparseable (unlikely), or if <code>entry</code> is of
|
||||
* a read-only type
|
||||
* @throws IOException on network error
|
||||
*/
|
||||
@Override
|
||||
public Entry updateEntry(Entry entry, String authToken)
|
||||
throws ParseException, IOException {
|
||||
GDataParserFactory factory = getGDataParserFactory();
|
||||
GDataSerializer serializer = factory.createSerializer(entry);
|
||||
|
||||
String editUri = entry.getEditUri();
|
||||
if (StringUtils.isEmpty(editUri)) {
|
||||
throw new ParseException("No edit URI -- cannot update.");
|
||||
}
|
||||
|
||||
InputStream is;
|
||||
try {
|
||||
is = getGDataClient().updateEntry(editUri, authToken, serializer);
|
||||
} catch (HttpException e) {
|
||||
convertHttpExceptionForWrites(entry.getClass(),
|
||||
"Could not update entry.", e);
|
||||
return null; // never reached
|
||||
}
|
||||
|
||||
GDataParser parser = factory.createParser(entry.getClass(), is);
|
||||
try {
|
||||
return parser.parseStandaloneEntry();
|
||||
} finally {
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HTTP exception which happened while writing to the equivalent
|
||||
* local exception.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void convertHttpExceptionForWrites(
|
||||
Class entryClass, String message, HttpException cause)
|
||||
throws ParseException, IOException {
|
||||
switch (cause.getStatusCode()) {
|
||||
case HttpException.SC_CONFLICT:
|
||||
if (entryClass != null) {
|
||||
InputStream is = cause.getResponseStream();
|
||||
if (is != null) {
|
||||
parseEntry(entryClass, cause.getResponseStream());
|
||||
}
|
||||
}
|
||||
throw new IOException(message);
|
||||
case HttpException.SC_BAD_REQUEST:
|
||||
throw new ParseException(message + ": " + cause);
|
||||
case HttpException.SC_FORBIDDEN:
|
||||
case HttpException.SC_UNAUTHORIZED:
|
||||
throw new IOException(message);
|
||||
default:
|
||||
throw new IOException(message + ": " + cause.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses one entry from the input stream.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Entry parseEntry(Class entryClass, InputStream is)
|
||||
throws ParseException, IOException {
|
||||
GDataParser parser = null;
|
||||
try {
|
||||
parser = getGDataParserFactory().createParser(entryClass, is);
|
||||
return parser.parseStandaloneEntry();
|
||||
} finally {
|
||||
if (parser != null) {
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.gdata.docs;
|
||||
|
||||
import com.google.wireless.gdata.client.GDataParserFactory;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
|
||||
import com.google.wireless.gdata.parser.xml.XmlParserFactory;
|
||||
import com.google.wireless.gdata.serializer.GDataSerializer;
|
||||
import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* Factory of Xml parsers for gdata maps data.
|
||||
*/
|
||||
public class XmlDocsGDataParserFactory implements GDataParserFactory {
|
||||
private XmlParserFactory xmlFactory;
|
||||
|
||||
public XmlDocsGDataParserFactory(XmlParserFactory xmlFactory) {
|
||||
this.xmlFactory = xmlFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GDataParser createParser(InputStream is) throws ParseException {
|
||||
try {
|
||||
return new XmlGDataParser(is, xmlFactory.createParser());
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public GDataParser createParser(Class cls, InputStream is)
|
||||
throws ParseException {
|
||||
try {
|
||||
return createParserForClass(is);
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private GDataParser createParserForClass(InputStream is)
|
||||
throws ParseException, XmlPullParserException {
|
||||
return new XmlGDataParser(is, xmlFactory.createParser());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GDataSerializer createSerializer(Entry en) {
|
||||
return new XmlEntryGDataSerializer(xmlFactory, en);
|
||||
}
|
||||
}
|
||||
+33
-16
@@ -20,8 +20,6 @@ import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.io.docs.SendDocsActivity;
|
||||
import com.google.android.apps.mytracks.io.drive.SendDriveActivity;
|
||||
import com.google.android.apps.mytracks.io.fusiontables.SendFusionTablesActivity;
|
||||
import com.google.android.apps.mytracks.io.gdata.docs.DocumentsClient;
|
||||
import com.google.android.apps.mytracks.io.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.android.apps.mytracks.io.gdata.maps.MapsConstants;
|
||||
import com.google.android.apps.mytracks.io.maps.ChooseMapActivity;
|
||||
import com.google.android.apps.mytracks.io.maps.SendMapsActivity;
|
||||
@@ -109,6 +107,14 @@ public class AccountChooserActivity extends Activity {
|
||||
fusionTablesCallback.onFailure();
|
||||
}
|
||||
break;
|
||||
case SendToGoogleUtils.SPREADSHEET_PERMISSION_REQUEST_CODE:
|
||||
SendToGoogleUtils.cancelNotification(this, SendToGoogleUtils.SPREADSHEET_NOTIFICATION_ID);
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
spreadsheetsCallback.onSuccess();
|
||||
} else {
|
||||
spreadsheetsCallback.onFailure();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
@@ -172,7 +178,17 @@ public class AccountChooserActivity extends Activity {
|
||||
* Checks the Drive permission.
|
||||
*/
|
||||
private void checkDrivePermission() {
|
||||
if (sendRequest.isSendDrive() || sendRequest.isSendFusionTables()) {
|
||||
boolean needDrivePermission = sendRequest.isSendDrive();
|
||||
if (!needDrivePermission) {
|
||||
boolean defaultTablePublic = PreferencesUtils.getBoolean(
|
||||
this, R.string.default_table_public_key, PreferencesUtils.DEFAULT_TABLE_PUBLIC_DEFAULT);
|
||||
needDrivePermission = defaultTablePublic && sendRequest.isSendFusionTables();
|
||||
}
|
||||
if (!needDrivePermission) {
|
||||
needDrivePermission = sendRequest.isSendDocs();
|
||||
}
|
||||
|
||||
if (needDrivePermission) {
|
||||
SendToGoogleUtils.checkPermissionByActivity(this, sendRequest.getAccount().name,
|
||||
SendToGoogleUtils.DRIVE_SCOPE, SendToGoogleUtils.DRIVE_PERMISSION_REQUEST_CODE,
|
||||
driveCallback);
|
||||
@@ -193,6 +209,19 @@ public class AccountChooserActivity extends Activity {
|
||||
fusionTablesCallback.onSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the Spreadsheet permission.
|
||||
*/
|
||||
private void checkSpreadsheetPermission() {
|
||||
if (sendRequest.isSendDocs()) {
|
||||
SendToGoogleUtils.checkPermissionByActivity(this, sendRequest.getAccount().name,
|
||||
SendToGoogleUtils.SPREADSHEET_SCOPE,
|
||||
SendToGoogleUtils.SPREADSHEET_PERMISSION_REQUEST_CODE, spreadsheetsCallback);
|
||||
} else {
|
||||
spreadsheetsCallback.onSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
private PermissionCallback spreadsheetsCallback = new PermissionCallback() {
|
||||
@Override
|
||||
@@ -206,22 +235,10 @@ public class AccountChooserActivity extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
private PermissionCallback docsCallback = new PermissionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
getPermission(SpreadsheetsClient.SERVICE, sendRequest.isSendDocs(), spreadsheetsCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
handleNoAccountPermission();
|
||||
}
|
||||
};
|
||||
|
||||
private PermissionCallback fusionTablesCallback = new PermissionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
getPermission(DocumentsClient.SERVICE, sendRequest.isSendDocs(), docsCallback);
|
||||
checkSpreadsheetPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -60,6 +60,11 @@ public class SendToGoogleUtils {
|
||||
public static final String FUSION_TABLES_SCOPE = FusiontablesScopes.FUSIONTABLES;
|
||||
public static final int FUSION_TABLES_NOTIFICATION_ID = 2;
|
||||
|
||||
// Request code to obtain Spreadsheet permission
|
||||
public static final int SPREADSHEET_PERMISSION_REQUEST_CODE = 3;
|
||||
public static final String SPREADSHEET_SCOPE = "https://spreadsheets.google.com/feeds";
|
||||
public static final int SPREADSHEET_NOTIFICATION_ID = 3;
|
||||
|
||||
private SendToGoogleUtils() {}
|
||||
|
||||
/**
|
||||
@@ -155,6 +160,20 @@ public class SendToGoogleUtils {
|
||||
return credential;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the OAuth2 token.
|
||||
*
|
||||
* @param context the context
|
||||
* @param accountName the account name
|
||||
* @param scope the scope
|
||||
*/
|
||||
public static String getToken(Context context, String accountName, String scope)
|
||||
throws IOException, GoogleAuthException {
|
||||
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context, scope);
|
||||
credential.setSelectedAccountName(accountName);
|
||||
return credential.getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks permission by an activity. Will start an activity to request
|
||||
* permission using the request code.
|
||||
|
||||
@@ -159,7 +159,7 @@ public class SyncUtils {
|
||||
.list().setQ(String.format(Locale.US, MY_TRACKS_FOLDER_QUERY, folderName));
|
||||
FileList result = list.execute();
|
||||
for (File file : result.getItems()) {
|
||||
if (file.getSharedWithMeDate() == null && file.getTitle().equals(folderName)) {
|
||||
if (file.getSharedWithMeDate() == null) {
|
||||
return file.getId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Utility functions for android resources.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class ResourceUtils {
|
||||
public static CharSequence readFile(Context activity, int id) {
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
in = new BufferedReader(new InputStreamReader(
|
||||
activity.getResources().openRawResource(id)));
|
||||
String line;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
while ((line = in.readLine()) != null) {
|
||||
buffer.append(line).append('\n');
|
||||
}
|
||||
return buffer;
|
||||
} catch (IOException e) {
|
||||
return "";
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void readBinaryFileToOutputStream(
|
||||
Context activity, int id, OutputStream os) {
|
||||
BufferedInputStream in = null;
|
||||
BufferedOutputStream out = null;
|
||||
try {
|
||||
in = new BufferedInputStream(
|
||||
activity.getResources().openRawResource(id));
|
||||
out = new BufferedOutputStream(os);
|
||||
int b;
|
||||
while ((b = in.read()) != -1) {
|
||||
out.write(b);
|
||||
}
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceUtils() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user