diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/drive/SendDriveAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/drive/SendDriveAsyncTask.java index eca7a6a30..6cd363b80 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/drive/SendDriveAsyncTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/drive/SendDriveAsyncTask.java @@ -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; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractImporter.java index 37a62fa48..a09884326 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractImporter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/AbstractImporter.java @@ -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; } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java index 9ac0c555b..3d1c24e10 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java @@ -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 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 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 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 driveFiles) throws Exception { + private void insertNewDriveFiles(Collection 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 getFiles(Files.List request) { + private Map getFiles(Files.List request) throws IOException { Map idToFileMap = new HashMap(); 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 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 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; + } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncUtils.java b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncUtils.java index bc207702c..65c153a78 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncUtils.java @@ -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 parents = new ArrayList(); - 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 parents = new ArrayList(); + 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. * diff --git a/MyTracksTest_E2E/src/com/google/android/apps/mytracks/endtoendtest/sync/MultiAccountsSyncTest.java b/MyTracksTest_E2E/src/com/google/android/apps/mytracks/endtoendtest/sync/MultiAccountsSyncTest.java index c075222a8..e1ed0309d 100644 --- a/MyTracksTest_E2E/src/com/google/android/apps/mytracks/endtoendtest/sync/MultiAccountsSyncTest.java +++ b/MyTracksTest_E2E/src/com/google/android/apps/mytracks/endtoendtest/sync/MultiAccountsSyncTest.java @@ -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 ActivityInstrumentationTestCase28. Enable sync with account2. *
  • 9. Check
  • * - * - * @throws IOException */ - public void testSyncTracksWithMultiAccounts() throws IOException { + public void testSyncTracksWithMultiAccounts() throws Exception { if (!SyncTestUtils.runSyncTest) { return; } @@ -108,10 +104,8 @@ public class MultiAccountsSyncTest extends ActivityInstrumentationTestCase25. Sync with account 1 (account 1 should have 1 file, account2 should * also have 1 file). * - * - * @throws IOException */ - public void testDeleteTracksWithMultiAccounts() throws IOException { + public void testDeleteTracksWithMultiAccounts() throws Exception { if (!SyncTestUtils.runSyncTest) { return; } diff --git a/MyTracksTest_E2E/src/com/google/android/apps/mytracks/endtoendtest/sync/SyncTestUtils.java b/MyTracksTest_E2E/src/com/google/android/apps/mytracks/endtoendtest/sync/SyncTestUtils.java index f756b6077..f16751e75 100644 --- a/MyTracksTest_E2E/src/com/google/android/apps/mytracks/endtoendtest/sync/SyncTestUtils.java +++ b/MyTracksTest_E2E/src/com/google/android/apps/mytracks/endtoendtest/sync/SyncTestUtils.java @@ -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); }