Add retry for SyncAdapter.

This commit is contained in:
Jimmy Shih
2013-02-05 17:50:35 -08:00
parent 49d055ce36
commit 08bcfde5c7
6 changed files with 324 additions and 244 deletions
@@ -84,8 +84,8 @@ public class SendDriveAsyncTask extends AbstractSendAsyncTask {
String driveId = track.getDriveId();
if (driveId != null && !driveId.equals("")) {
File driveFile = drive.files().get(driveId).execute();
if (SyncUtils.isValid(driveFile, folderId)
&& SyncUtils.updateDriveFile(context, myTracksProviderUtils, drive, driveFile, track)) {
if (SyncUtils.isValid(driveFile, folderId) && SyncUtils.updateDriveFile(
drive, driveFile, context, myTracksProviderUtils, track, false)) {
addPermission(drive, driveId);
return true;
}
@@ -95,7 +95,8 @@ public class SendDriveAsyncTask extends AbstractSendAsyncTask {
myTracksProviderUtils.updateTrack(track);
}
String id = SyncUtils.addDriveFile(context, myTracksProviderUtils, drive, folderId, track);
String id = SyncUtils.insertDriveFile(
drive, folderId, context, myTracksProviderUtils, track, false);
if (id == null) {
return false;
}
@@ -45,6 +45,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -174,7 +175,8 @@ abstract class AbstractImporter extends DefaultHandler {
* @param inputStream the input stream.
* @return an array of imported track ids.
*/
public long[] importFile(InputStream inputStream) throws Exception {
public long[] importFile(InputStream inputStream)
throws SAXException, IOException, ParserConfigurationException {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParser saxParser = saxParserFactory.newSAXParser();
@@ -186,9 +188,11 @@ abstract class AbstractImporter extends DefaultHandler {
long end = System.currentTimeMillis();
Log.d(Constants.TAG, "Total import time: " + (end - start) + "ms");
} catch (SAXException e) {
handleException(e);
handleException();
throw e;
} catch (IOException e) {
handleException(e);
handleException();
throw e;
}
long[] result = new long[trackIds.size()];
for (int i = 0; i < result.length; i++) {
@@ -578,13 +582,10 @@ abstract class AbstractImporter extends DefaultHandler {
/**
* Handles an exception.
*
* @param e the exception
*/
private void handleException(Exception e) throws Exception {
private void handleException() {
for (long trackId : trackIds) {
myTracksProviderUtils.deleteTrack(trackId);
}
throw e;
}
}
@@ -20,8 +20,11 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.file.KmlImporter;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.maps.mytracks.R;
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.GenericUrl;
import com.google.api.client.http.HttpResponse;
import com.google.api.services.drive.Drive;
@@ -40,7 +43,6 @@ import android.content.Context;
import android.content.SyncResult;
import android.database.Cursor;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
@@ -51,6 +53,10 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* SyncAdapter to sync tracks with Google Drive.
*
@@ -94,17 +100,18 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
return;
}
GoogleAccountCredential credential = SyncUtils.getCredential(context, account.name);
if (credential == null) {
return;
}
if (drive == null || !driveAccountName.equals(account.name)) {
drive = SyncUtils.getDriveService(credential);
driveAccountName = account.name;
}
try {
GoogleAccountCredential credential = SyncUtils.getGoogleAccountCredential(
context, account.name);
if (credential == null) {
return;
}
if (drive == null || !driveAccountName.equals(account.name)) {
drive = SyncUtils.getDriveService(credential);
driveAccountName = account.name;
}
String folderId = SyncUtils.getMyTracksFolder(context, drive);
if (folderId == null) {
return;
@@ -117,8 +124,14 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
performIncrementalSync(folderId, largestChangeId);
}
insertNewTracks(folderId);
} catch (Exception e) {
Log.e(TAG, "Exception", e);
} catch (UserRecoverableAuthException e) {
SyncUtils.sendNotification(context, account.name, e.getIntent());
} catch (GoogleAuthException e) {
Log.e(TAG, "GoogleAuthException", e);
} catch (UserRecoverableAuthIOException e) {
SyncUtils.sendNotification(context, account.name, e.getIntent());
} catch (IOException e) {
Log.e(TAG, "IOException", e);
}
}
@@ -127,24 +140,45 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
*
* @param folderId the folder id
*/
private void performInitialSync(String folderId) throws Exception {
private void performInitialSync(String folderId) throws IOException {
// Get the largest change id first to avoid race conditions
About about = drive.about().get().setFields(ABOUT_GET_FIELDS).execute();
long largestChangeId = about.getLargestChangeId();
// Get all the KML files in the "My Drive:/My Tracks" folder
Files.List request = drive.files().list()
.setQ(String.format(Locale.US, SyncUtils.MY_TRACKS_FOLDER_FILES_QUERY, folderId));
Map<String, File> idToFileMap = getFiles(request);
insertNewDriveFiles(idToFileMap.values());
Files.List myTracksFolderRequest = drive.files()
.list().setQ(String.format(Locale.US, SyncUtils.MY_TRACKS_FOLDER_FILES_QUERY, folderId));
Map<String, File> myTracksFolderMap = getFiles(myTracksFolderRequest);
// Get all the KML files in the "Shared with me:/" folder
request = drive.files().list().setQ(SyncUtils.SHARED_WITH_ME_FILES_QUERY);
idToFileMap = getFiles(request);
insertNewDriveFiles(idToFileMap.values());
Files.List sharedWithMeRequest = drive.files()
.list().setQ(SyncUtils.SHARED_WITH_ME_FILES_QUERY);
Map<String, File> sharedWithMeMap = getFiles(sharedWithMeRequest);
PreferencesUtils.setLong(context, R.string.drive_largest_change_id_key, largestChangeId);
try {
insertNewDriveFiles(myTracksFolderMap.values());
insertNewDriveFiles(sharedWithMeMap.values());
PreferencesUtils.setLong(context, R.string.drive_largest_change_id_key, largestChangeId);
} catch (IOException e) {
// Remove all imported tracks
Cursor cursor = null;
try {
cursor = myTracksProviderUtils.getTrackCursor(SyncUtils.DRIVE_ID_TRACKS_QUERY, null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
Track track = myTracksProviderUtils.createTrack(cursor);
myTracksProviderUtils.deleteTrack(track.getId());
} while (cursor.moveToNext());
}
} finally {
if (cursor != null) {
cursor.close();
}
}
throw e;
}
}
/**
@@ -153,26 +187,13 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
* @param folderId the folder id
* @param largestChangeId the largest change id
*/
private void performIncrementalSync(String folderId, long largestChangeId) throws Exception {
private void performIncrementalSync(String folderId, long largestChangeId) throws IOException {
// Move to trash in Drive all deleted tracks
String driveDeletedList = PreferencesUtils.getString(
context, R.string.drive_deleted_list_key, PreferencesUtils.DRIVE_DELETED_LIST_DEFAULT);
String deletedIds[] = TextUtils.split(driveDeletedList, ";");
for (String id : deletedIds) {
try {
File driveFile = drive.files().get(id).execute();
if (SyncUtils.isInFolder(driveFile, folderId)) {
if (!driveFile.getLabels().getTrashed()) {
drive.files().trash(id).execute();
}
// if trashed, ignore
} else if (SyncUtils.isSharedWithMe(driveFile)) {
drive.files().delete(id).execute();
}
} catch (IOException e) {
// safe to ignore
}
for (String driveId : deletedIds) {
deleteDriveFile(driveId, folderId, true);
}
PreferencesUtils.setString(
context, R.string.drive_deleted_list_key, PreferencesUtils.DRIVE_DELETED_LIST_DEFAULT);
@@ -197,7 +218,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
Log.d(TAG, "Delete local track " + track.getName());
myTracksProviderUtils.deleteTrack(track.getId());
} else {
mergeFiles(track, driveFile);
merge(track, driveFile);
}
changes.remove(driveId);
} else {
@@ -206,7 +227,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
// Handle the case the track has changed
File driveFile = drive.files().get(driveId).execute();
if (SyncUtils.isValid(driveFile, folderId)) {
mergeFiles(track, driveFile);
merge(track, driveFile);
} else {
/*
* Track has a drive id, but the drive id is no longer valid.
@@ -225,10 +246,6 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
// Handle new drive files
insertNewDriveFiles(changes.values());
PreferencesUtils.setLong(context, R.string.drive_largest_change_id_key, largestChangeId);
} catch (IOException e) {
Log.e(TAG, "IOException", e);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -253,8 +270,8 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
if (track.getId() == recordingTrackId) {
continue;
}
// Note, will retry on the next sync if unable to add drive file
SyncUtils.addDriveFile(context, myTracksProviderUtils, drive, folderId, track);
// If not successful, the next sync will retry again
SyncUtils.insertDriveFile(drive, folderId, context, myTracksProviderUtils, track, true);
} while (cursor.moveToNext());
}
} finally {
@@ -268,24 +285,36 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
* Inserts new Google Drive files.
*
* @param driveFiles a collection of drive files to insert
* @throws IOException
*/
private void insertNewDriveFiles(Collection<File> driveFiles) throws Exception {
private void insertNewDriveFiles(Collection<File> driveFiles) throws IOException {
for (File driveFile : driveFiles) {
if (driveFile != null) {
InputStream inputStream = downloadDriveFile(driveFile);
// TODO: should retry if inputStream is null
InputStream inputStream = downloadDriveFile(driveFile, true);
if (inputStream != null) {
KmlImporter kmlImporter = new KmlImporter(context, -1L);
long[] tracksIds = kmlImporter.importFile(inputStream);
if (tracksIds.length == 1) {
Track track = myTracksProviderUtils.getTrack(tracksIds[0]);
track.setDriveId(driveFile.getId());
track.setModifiedTime(driveFile.getModifiedDate().getValue());
track.setSharedWithMe(driveFile.getSharedWithMeDate() != null);
myTracksProviderUtils.updateTrack(track);
Log.d(TAG, "Add from Google Drive " + track.getName());
try {
long[] trackIds = kmlImporter.importFile(inputStream);
if (trackIds.length == 1) {
Track track = myTracksProviderUtils.getTrack(trackIds[0]);
track.setDriveId(driveFile.getId());
track.setModifiedTime(driveFile.getModifiedDate().getValue());
track.setSharedWithMe(driveFile.getSharedWithMeDate() != null);
myTracksProviderUtils.updateTrack(track);
Log.d(TAG, "Add from Google Drive " + track.getName());
} else {
// Clean up if imported more than one track
for (int i = 0; i < trackIds.length; i++) {
myTracksProviderUtils.deleteTrack(trackIds[i]);
}
}
} catch (SAXException e) {
Log.e(TAG, "Unable to insert new drive file for " + driveFile.getId(), e);
} catch (IOException e) {
Log.e(TAG, "Unable to insert new drive file for " + driveFile.getId(), e);
} catch (ParserConfigurationException e) {
Log.e(TAG, "Unable to insert new drive file for " + driveFile.getId(), e);
}
// Ignore if tracksId.length != 1
}
}
}
@@ -297,20 +326,15 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
* @param request the request
* @return a map of file id to file
*/
private Map<String, File> getFiles(Files.List request) {
private Map<String, File> getFiles(Files.List request) throws IOException {
Map<String, File> idToFileMap = new HashMap<String, File>();
do {
try {
FileList files = request.execute();
FileList files = request.execute();
for (File file : files.getItems()) {
idToFileMap.put(file.getId(), file);
}
request.setPageToken(files.getNextPageToken());
} catch (IOException e) {
Log.e(TAG, "IOException", e);
request.setPageToken(null);
for (File file : files.getItems()) {
idToFileMap.put(file.getId(), file);
}
request.setPageToken(files.getNextPageToken());
} while (request.getPageToken() != null && request.getPageToken().length() > 0);
return idToFileMap;
}
@@ -323,38 +347,35 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
* @param changes a map of drive id to file for the changes
* @return an updated largest change id
*/
private long getDriveChanges(String folderId, long changeId, Map<String, File> changes) {
try {
Changes.List request = drive.changes().list().setStartChangeId(changeId + 1);
do {
ChangeList changeList = request.execute();
long newId = changeList.getLargestChangeId().longValue();
private long getDriveChanges(String folderId, long changeId, Map<String, File> changes)
throws IOException {
Changes.List request = drive.changes().list().setStartChangeId(changeId + 1);
do {
ChangeList changeList = request.execute();
long newId = changeList.getLargestChangeId().longValue();
for (Change change : changeList.getItems()) {
if (change.getDeleted()) {
changes.put(change.getFileId(), null);
} else {
File file = change.getFile();
if (SyncUtils.isInFolder(file, folderId)) {
if (file.getLabels().getTrashed()) {
changes.put(change.getFileId(), null);
} else {
changes.put(change.getFileId(), file);
}
} else if (SyncUtils.isSharedWithMe(file)) {
for (Change change : changeList.getItems()) {
if (change.getDeleted()) {
changes.put(change.getFileId(), null);
} else {
File file = change.getFile();
if (SyncUtils.isInFolder(file, folderId)) {
if (file.getLabels().getTrashed()) {
changes.put(change.getFileId(), null);
} else {
changes.put(change.getFileId(), file);
}
} else if (SyncUtils.isSharedWithMe(file)) {
changes.put(change.getFileId(), file);
}
}
if (newId > changeId) {
changeId = newId;
}
request.setPageToken(changeList.getNextPageToken());
} while (request.getPageToken() != null && request.getPageToken().length() > 0);
} catch (IOException e) {
Log.e(TAG, "IOException", e);
}
Log.d(TAG, "Got drive changes: " + changes.size() + " - " + changeId);
}
if (newId > changeId) {
changeId = newId;
}
request.setPageToken(changeList.getNextPageToken());
} while (request.getPageToken() != null && request.getPageToken().length() > 0);
Log.d(TAG, "Got drive changes: " + changes.size() + " " + changeId);
return changeId;
}
@@ -364,44 +385,77 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
* @param track the track
* @param driveFile the drive file
*/
private void mergeFiles(Track track, File driveFile) throws Exception {
private void merge(Track track, File driveFile) throws IOException {
long modifiedTime = track.getModifiedTime();
long driveModifiedTime = driveFile.getModifiedDate().getValue();
if (modifiedTime > driveModifiedTime) {
Log.d(TAG, "Updating track change " + track.getName());
if (!SyncUtils.updateDriveFile(context, myTracksProviderUtils, drive, driveFile, track)) {
// TODO: Should inform the user if cannot update the file
Log.d(TAG, "Updating track change for track " + track.getName() + " and drive file "
+ driveFile.getOriginalFilename());
if (!SyncUtils.updateDriveFile(
drive, driveFile, context, myTracksProviderUtils, track, true)) {
Log.e(TAG, "Unable to update drive file");
track.setModifiedTime(driveModifiedTime);
myTracksProviderUtils.updateTrack(track);
}
} else if (modifiedTime < driveModifiedTime) {
Log.d(TAG, "Updating drive change " + track.getName());
InputStream inputStream = downloadDriveFile(driveFile);
if (inputStream == null) {
// TODO: Should retry if cannot download
Log.e(TAG, "Unable to update drive change. Input stream is null for " + track.getName());
track.setModifiedTime(driveModifiedTime);
myTracksProviderUtils.updateTrack(track);
} else {
Log.d(TAG, "Updating drive change for track " + track.getName() + " and drive file "
+ driveFile.getOriginalFilename());
InputStream inputStream = downloadDriveFile(driveFile, true);
if (inputStream != null) {
KmlImporter kmlImporter = new KmlImporter(context, track.getId());
long[] tracksIds = kmlImporter.importFile(inputStream);
if (tracksIds.length == 1) {
Track newTrack = myTracksProviderUtils.getTrack(tracksIds[0]);
newTrack.setDriveId(driveFile.getId());
newTrack.setModifiedTime(driveModifiedTime);
newTrack.setSharedWithMe(driveFile.getSharedWithMeDate() != null);
myTracksProviderUtils.updateTrack(newTrack);
} else {
/*
* TODO: Should revert the track back to the original.
*/
Log.e(
TAG, "Unable to update drive change. Imported size is not 1 for " + track.getName());
try {
long[] trackIds = kmlImporter.importFile(inputStream);
if (trackIds.length == 1) {
Track newTrack = myTracksProviderUtils.getTrack(trackIds[0]);
newTrack.setDriveId(driveFile.getId());
newTrack.setModifiedTime(driveModifiedTime);
newTrack.setSharedWithMe(driveFile.getSharedWithMeDate() != null);
myTracksProviderUtils.updateTrack(newTrack);
return;
} else {
Log.e(TAG, "Unable to merge, imported size is not 1");
}
} catch (SAXException e) {
Log.e(TAG, "Unable to merge", e);
} catch (ParserConfigurationException e) {
Log.e(TAG, "Unable to merge", e);
} catch (IOException e) {
Log.e(TAG, "Unable to merge", e);
}
}
Log.e(TAG, "Unable to update drive change");
track.setModifiedTime(driveModifiedTime);
myTracksProviderUtils.updateTrack(track);
}
}
/**
* Deletes a drive file.
*
* @param driveId the drive id
* @param folderId the folder id
* @param canRetry true if can retry the request
*/
private void deleteDriveFile(String driveId, String folderId, boolean canRetry)
throws UserRecoverableAuthIOException {
try {
File driveFile = drive.files().get(driveId).execute();
if (SyncUtils.isInFolder(driveFile, folderId)) {
if (!driveFile.getLabels().getTrashed()) {
drive.files().trash(driveId).execute();
}
// if trashed, ignore
} else if (SyncUtils.isSharedWithMe(driveFile)) {
drive.files().delete(driveId).execute();
}
} catch (UserRecoverableAuthIOException e) {
throw e;
} catch (IOException e) {
if (canRetry) {
deleteDriveFile(driveId, folderId, false);
return;
}
Log.e(TAG, "Unable to delete Drive file for " + driveId, e);
}
}
@@ -410,24 +464,26 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
*
* @param driveFile the drive file
*/
private InputStream downloadDriveFile(File driveFile) {
if (driveFile.getDownloadUrl() != null && driveFile.getDownloadUrl().length() > 0) {
try {
HttpResponse httpResponse = drive.getRequestFactory()
.buildGetRequest(new GenericUrl(driveFile.getDownloadUrl())).execute();
if (httpResponse != null) {
return httpResponse.getContent();
} else {
Log.e(TAG, "http response is null");
return null;
}
} catch (IOException e) {
Log.e(TAG, "IOException", e);
return null;
}
} else {
private InputStream downloadDriveFile(File driveFile, boolean canRetry) throws IOException {
if (driveFile.getDownloadUrl() == null || driveFile.getDownloadUrl().length() == 0) {
Log.d(TAG, "Drive file download url doesn't exist: " + driveFile.getTitle());
return null;
}
try {
HttpResponse httpResponse = drive.getRequestFactory()
.buildGetRequest(new GenericUrl(driveFile.getDownloadUrl())).execute();
if (httpResponse == null) {
Log.e(TAG, "http response is null");
return null;
}
return httpResponse.getContent();
} catch (UserRecoverableAuthIOException e) {
throw e;
} catch (IOException e) {
if (canRetry) {
return downloadDriveFile(driveFile, false);
}
throw e;
}
}
}
@@ -86,7 +86,7 @@ public class SyncUtils {
// Get My Tracks folder KML files
public static final String MY_TRACKS_FOLDER_FILES_QUERY = "'%s' in parents and mimeType = '"
+ KML_MIME_TYPE + "' and trashed = false and not sharedWithMe";
+ KML_MIME_TYPE + "' and trashed = false";
// Get shared with me KML files
public static final String SHARED_WITH_ME_FILES_QUERY = "sharedWithMe and mimeType = '"
@@ -107,6 +107,21 @@ public class SyncUtils {
private SyncUtils() {}
/**
* Gets the google account credential.
*
* @param context the context
* @param accountName the account name
*/
public static GoogleAccountCredential getGoogleAccountCredential(
Context context, String accountName) throws IOException, GoogleAuthException {
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(
context, DriveScopes.DRIVE);
credential.setSelectedAccountName(accountName);
credential.getToken();
return credential;
}
/**
* Checks permission by an activity. Will start an activity to request
* permission with request code {@link #DRIVE_PERMISSION_REQUEST_CODE}.
@@ -130,7 +145,7 @@ public class SyncUtils {
});
} catch (UserRecoverableAuthException e) {
activity.startActivityForResult(e.getIntent(), DRIVE_PERMISSION_REQUEST_CODE);
} catch (GoogleAuthException e) {
} catch (GoogleAuthException e) {
Log.e(TAG, "GoogleAuthException", e);
activity.runOnUiThread(new Runnable() {
@Override
@@ -155,24 +170,26 @@ public class SyncUtils {
}
/**
* Gets the drive credential. Needs to be run in a background thread.
* Sends a notification to request permission.
*
* @param context the context
* @param accountName the account name
* @param intent the intent
*/
public static GoogleAccountCredential getCredential(Context context, String accountName) {
try {
return getGoogleAccountCredential(context, accountName);
} catch (UserRecoverableAuthException e) {
sendNotification(context, accountName, e.getIntent());
} catch (GoogleAuthException e) {
Log.e(TAG, "GoogleAuthException", e);
} catch (UserRecoverableAuthIOException e) {
sendNotification(context, accountName, e.getIntent());
} catch (IOException e) {
Log.e(TAG, "IOException", e);
}
return null;
public static void sendNotification(Context context, String accountName, Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).addFlags(Intent.FLAG_FROM_BACKGROUND);
PendingIntent pendingIntent = PendingIntent.getActivity(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context).setAutoCancel(true)
.setContentIntent(pendingIntent)
.setContentText(context.getString(R.string.permission_request_message, accountName))
.setContentTitle(context.getString(R.string.permission_request_title))
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setTicker(context.getString(R.string.permission_request_title));
NotificationManager notificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
/**
@@ -310,17 +327,19 @@ public class SyncUtils {
}
/**
* Adds a Drive file.
* Inserts a Drive file.
*
* @param context the context
* @param myTracksProviderUtils the myTracksProviderUtils
* @param drive the drive
* @param folderId the folder id
* @param context the context
* @param myTracksProviderUtils the myTracksProviderUtils
* @param track the track
* @param canRetry true if can retry
* @return the added drive file id or null.
*/
public static String addDriveFile(Context context, MyTracksProviderUtils myTracksProviderUtils,
Drive drive, String folderId, Track track) throws IOException {
public static String insertDriveFile(Drive drive, String folderId, Context context,
MyTracksProviderUtils myTracksProviderUtils, Track track, boolean canRetry)
throws IOException {
java.io.File file = getFile(context, myTracksProviderUtils, track);
if (file == null) {
Log.e(TAG, "Unable to add Drive file. File is null for track " + track.getName());
@@ -329,21 +348,7 @@ public class SyncUtils {
try {
Log.d(TAG, "Add Drive file for track " + track.getName());
FileContent fileContent = new FileContent(KML_MIME_TYPE, file);
// file's parent
ParentReference parentReference = new ParentReference();
parentReference.setId(folderId);
ArrayList<ParentReference> parents = new ArrayList<ParentReference>();
parents.add(parentReference);
// file's metadata
File newMetaData = new File();
newMetaData.setTitle(track.getName() + "." + TrackFileFormat.KML.getExtension());
newMetaData.setMimeType(KML_MIME_TYPE);
newMetaData.setParents(parents);
File uploadedFile = drive.files().insert(newMetaData, fileContent).execute();
File uploadedFile = insertDriveFile(drive, folderId, track.getName(), file, canRetry);
if (uploadedFile == null) {
Log.e(TAG, "Unable to add Drive file. Uploaded file is null for track " + track.getName());
return null;
@@ -360,16 +365,55 @@ public class SyncUtils {
}
/**
* Updates a Drive file.
* Inserts a Drive file.
*
* @param drive the drive
* @param folderId the folder id
* @param name the track name
* @param file the track file
* @param canRetry true if can retry
*/
private static File insertDriveFile(
Drive drive, String folderId, String name, java.io.File file, boolean canRetry)
throws IOException {
try {
FileContent fileContent = new FileContent(KML_MIME_TYPE, file);
// file's parent
ParentReference parentReference = new ParentReference();
parentReference.setId(folderId);
ArrayList<ParentReference> parents = new ArrayList<ParentReference>();
parents.add(parentReference);
// file's metadata
File newMetaData = new File();
newMetaData.setTitle(name + "." + TrackFileFormat.KML.getExtension());
newMetaData.setMimeType(KML_MIME_TYPE);
newMetaData.setParents(parents);
return drive.files().insert(newMetaData, fileContent).execute();
} catch (UserRecoverableAuthIOException e) {
throw e;
} catch (IOException e) {
if (canRetry) {
return insertDriveFile(drive, folderId, name, file, false);
}
throw e;
}
}
/**
* Updates a Drive file. Returns true if successful.
*
* @param context the context
* @param myTracksProviderUtils the myTracksProviderUtils
* @param drive the drive
* @param driveFile the drive file
* @param context the context
* @param myTracksProviderUtils the myTracksProviderUtils
* @param track the track
* @param canRetry true if can retry
*/
public static boolean updateDriveFile(Context context,
MyTracksProviderUtils myTracksProviderUtils, Drive drive, File driveFile, Track track)
public static boolean updateDriveFile(Drive drive, File driveFile, Context context,
MyTracksProviderUtils myTracksProviderUtils, Track track, boolean canRetry)
throws IOException {
Log.d(TAG, "Update drive file for track " + track.getName());
java.io.File file = SyncUtils.getFile(context, myTracksProviderUtils, track);
@@ -379,18 +423,13 @@ public class SyncUtils {
return false;
} else {
try {
FileContent fileContent = new FileContent(KML_MIME_TYPE, file);
driveFile.setTitle(track.getName() + "." + TrackFileFormat.KML.getExtension());
File updatedFile = drive.files()
.update(driveFile.getId(), driveFile, fileContent).execute();
File updatedFile = updateDriveFile(drive, driveFile, track.getName(), file, canRetry);
if (updatedFile == null) {
Log.e(TAG,
"Unable to update drive file. Updated file is null for track " + track.getName());
return false;
}
long newModifiedTime = updatedFile.getModifiedDate().getValue();
track.setModifiedTime(newModifiedTime);
track.setModifiedTime(updatedFile.getModifiedDate().getValue());
myTracksProviderUtils.updateTrack(track);
return true;
} finally {
@@ -400,43 +439,31 @@ public class SyncUtils {
}
/**
* Gets the google account credential.
* Updates a Drive file.
*
* @param context the context
* @param accountName the account name
* @param drive the drive
* @param driveFile the drive file
* @param name the track name
* @param file the track file
* @param canRetry true if can retry
*/
public static GoogleAccountCredential getGoogleAccountCredential(
Context context, String accountName) throws IOException, GoogleAuthException {
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(
context, DriveScopes.DRIVE);
credential.setSelectedAccountName(accountName);
credential.getToken();
return credential;
private static File updateDriveFile(
Drive drive, File driveFile, String name, java.io.File file, boolean canRetry)
throws IOException {
try {
FileContent fileContent = new FileContent(KML_MIME_TYPE, file);
driveFile.setTitle(name + "." + TrackFileFormat.KML.getExtension());
return drive.files().update(driveFile.getId(), driveFile, fileContent).execute();
} catch (UserRecoverableAuthIOException e) {
throw e;
} catch (IOException e) {
if (canRetry) {
return updateDriveFile(drive, driveFile, name, file, false);
}
throw e;
}
}
/**
* Sends a notification to request permission.
*
* @param context the context
* @param accountName the account name
* @param intent the intent
*/
public static void sendNotification(Context context, String accountName, Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).addFlags(Intent.FLAG_FROM_BACKGROUND);
PendingIntent pendingIntent = PendingIntent.getActivity(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context).setAutoCancel(
true).setContentIntent(pendingIntent)
.setContentText(context.getString(R.string.permission_request_message, accountName))
.setContentTitle(context.getString(R.string.permission_request_title))
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setTicker(context.getString(R.string.permission_request_title));
NotificationManager notificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
/**
* Gets a file from a track.
*
@@ -24,8 +24,6 @@ import com.google.api.services.drive.Drive;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import java.io.IOException;
/**
* Tests the situation when user use multiple account in MyTracks.
*
@@ -63,10 +61,8 @@ public class MultiAccountsSyncTest extends ActivityInstrumentationTestCase2<Trac
* <li>8. Enable sync with account2.</li>
* <li>9. Check</li>
* </ul>
*
* @throws IOException
*/
public void testSyncTracksWithMultiAccounts() throws IOException {
public void testSyncTracksWithMultiAccounts() throws Exception {
if (!SyncTestUtils.runSyncTest) {
return;
}
@@ -108,10 +104,8 @@ public class MultiAccountsSyncTest extends ActivityInstrumentationTestCase2<Trac
* <li>5. Sync with account 1 (account 1 should have 1 file, account2 should
* also have 1 file).</li>
* </ul>
*
* @throws IOException
*/
public void testDeleteTracksWithMultiAccounts() throws IOException {
public void testDeleteTracksWithMultiAccounts() throws Exception {
if (!SyncTestUtils.runSyncTest) {
return;
}
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.endtoendtest.EndToEndTestUtils;
import com.google.android.apps.mytracks.endtoendtest.GoogleUtils;
import com.google.android.apps.mytracks.io.sync.SyncUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.maps.mytracks.R;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
@@ -61,10 +62,9 @@ public class SyncTestUtils {
* @param instrumentation the instrumentation is used for test
* @param trackListActivity the startup activity
* @return a Google Drive object
* @throws IOException
*/
public static Drive setUpForSyncTest(Instrumentation instrumentation,
TrackListActivity trackListActivity) throws IOException {
TrackListActivity trackListActivity) throws IOException, GoogleAuthException {
if (runSyncTest || !isCheckedRunSyncTest) {
EndToEndTestUtils.setupForAllTest(instrumentation, trackListActivity);
}
@@ -116,10 +116,11 @@ public class SyncTestUtils {
* @param context the context of application
* @return a Google Drive object
*/
public static Drive getGoogleDrive(Context context) {
String googleAccount = PreferencesUtils.getString(context, R.string.google_account_key,
PreferencesUtils.GOOGLE_ACCOUNT_DEFAULT);
GoogleAccountCredential credential = SyncUtils.getCredential(context, googleAccount);
public static Drive getGoogleDrive(Context context) throws IOException, GoogleAuthException {
String googleAccount = PreferencesUtils.getString(
context, R.string.google_account_key, PreferencesUtils.GOOGLE_ACCOUNT_DEFAULT);
GoogleAccountCredential credential = SyncUtils.getGoogleAccountCredential(
context, googleAccount);
return SyncUtils.getDriveService(credential);
}