From 84c3a4228c51d5730733a79f9b816f0eb5cbe41d Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 3 Aug 2016 12:00:15 -0500 Subject: [PATCH] Add Google Tasks unsuccessful response handler --- .../astrid/gtasks/api/GtasksInvoker.java | 43 +++++------- .../java/org/tasks/AccountManager.java | 13 ---- .../tasks/gtasks/GoogleTaskSyncAdapter.java | 45 ++++++------- ...oogleTasksUnsuccessfulResponseHandler.java | 66 +++++++++++++++++++ 4 files changed, 103 insertions(+), 64 deletions(-) create mode 100644 src/googleplay/java/org/tasks/gtasks/GoogleTasksUnsuccessfulResponseHandler.java diff --git a/src/googleplay/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.java b/src/googleplay/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.java index 8651d749d..3e395aa77 100644 --- a/src/googleplay/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.java +++ b/src/googleplay/java/com/todoroo/astrid/gtasks/api/GtasksInvoker.java @@ -3,7 +3,8 @@ package com.todoroo.astrid.gtasks.api; import android.content.Context; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; -import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.GenericJson; import com.google.api.client.json.jackson2.JacksonFactory; @@ -17,6 +18,8 @@ import com.todoroo.astrid.gtasks.GtasksPreferenceService; import org.tasks.AccountManager; import org.tasks.BuildConfig; +import org.tasks.analytics.Tracker; +import org.tasks.gtasks.GoogleTasksUnsuccessfulResponseHandler; import org.tasks.injection.ForApplication; import java.io.IOException; @@ -36,13 +39,15 @@ import timber.log.Timber; @Singleton public class GtasksInvoker { - private AccountManager accountManager; + private final Context context; + private final Tracker tracker; private final GoogleAccountCredential credential; - private Tasks service; + private final Tasks service; @Inject - public GtasksInvoker(@ForApplication Context context, GtasksPreferenceService preferenceService, AccountManager accountManager) { - this.accountManager = accountManager; + public GtasksInvoker(@ForApplication Context context, GtasksPreferenceService preferenceService, Tracker tracker) { + this.context = context; + this.tracker = tracker; credential = GoogleAccountCredential.usingOAuth2(context, Collections.singletonList(TasksScopes.TASKS)); setUserName(preferenceService.getUserName()); service = new Tasks.Builder(new NetHttpTransport(), new JacksonFactory(), credential) @@ -54,25 +59,6 @@ public class GtasksInvoker { credential.setSelectedAccountName(username); } - //If we get a 401 or 403, try revalidating the auth token before bailing - private synchronized void handleException(IOException e) throws IOException { - Timber.e(e, e.getMessage()); - if (e instanceof HttpResponseException) { - HttpResponseException h = (HttpResponseException) e; - int statusCode = h.getStatusCode(); - if (statusCode == 401 || statusCode == 403) { - accountManager.clearToken(credential); - } else if (statusCode == 400 || statusCode == 500) { - throw h; - } else if (statusCode == 404) { - throw new HttpNotFoundException(h); - } else { - Timber.e(e, "%s: %s", statusCode, h.getStatusMessage()); - } - // 503 errors are generally either 1) quota limit reached or 2) problems on Google's end - } - } - public TaskLists allGtaskLists(String pageToken) throws IOException { return execute(service .tasklists() @@ -141,10 +127,13 @@ public class GtasksInvoker { Timber.d("%s request: %s", caller, request); T response; try { - response = request.execute(); + HttpRequest httpRequest = request.buildHttpRequest(); + httpRequest.setUnsuccessfulResponseHandler(new GoogleTasksUnsuccessfulResponseHandler(context, credential)); + HttpResponse httpResponse = httpRequest.execute(); + response = httpResponse.parseAs(request.getResponseClass()); } catch (IOException e) { - handleException(e); - response = request.execute(); + tracker.reportException(e); + throw e; } Timber.d("%s response: %s", caller, prettyPrint(response)); return response; diff --git a/src/googleplay/java/org/tasks/AccountManager.java b/src/googleplay/java/org/tasks/AccountManager.java index 647f9bf12..a91b4ccb2 100644 --- a/src/googleplay/java/org/tasks/AccountManager.java +++ b/src/googleplay/java/org/tasks/AccountManager.java @@ -8,7 +8,6 @@ import com.google.android.gms.auth.GoogleAuthException; import com.google.android.gms.auth.GoogleAuthUtil; import com.google.android.gms.auth.UserRecoverableAuthException; import com.google.api.client.googleapis.extensions.android.accounts.GoogleAccountManager; -import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.services.tasks.TasksScopes; import com.google.common.base.Function; import com.google.common.base.Predicate; @@ -31,18 +30,6 @@ import static java.util.Arrays.asList; public class AccountManager { - public void clearToken(GoogleAccountCredential credential) throws IOException { - try { - String token = credential.getToken(); - Timber.d("Invalidating %s", token); - GoogleAuthUtil.clearToken(context, token); - GoogleAuthUtil.getTokenWithNotification(context, credential.getSelectedAccount(), "oauth2:" + TasksScopes.TASKS, null); - } catch (GoogleAuthException e) { - Timber.e(e, e.getMessage()); - throw new IOException(e); - } - } - public interface AuthResultHandler { void authenticationSuccessful(String accountName); void authenticationFailed(String message); diff --git a/src/googleplay/java/org/tasks/gtasks/GoogleTaskSyncAdapter.java b/src/googleplay/java/org/tasks/gtasks/GoogleTaskSyncAdapter.java index 2d7f153c0..4fcd97131 100644 --- a/src/googleplay/java/org/tasks/gtasks/GoogleTaskSyncAdapter.java +++ b/src/googleplay/java/org/tasks/gtasks/GoogleTaskSyncAdapter.java @@ -54,6 +54,7 @@ import com.todoroo.astrid.service.TaskService; import org.tasks.Broadcaster; import org.tasks.R; +import org.tasks.analytics.Tracker; import org.tasks.injection.InjectingAbstractThreadedSyncAdapter; import org.tasks.injection.SyncAdapterComponent; import org.tasks.preferences.Preferences; @@ -97,6 +98,7 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter @Inject TaskDao taskDao; @Inject MetadataDao metadataDao; @Inject GtasksMetadata gtasksMetadataFactory; + @Inject Tracker tracker; public GoogleTaskSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); @@ -139,38 +141,33 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter synchronize(); gtasksPreferenceService.recordSuccessfulSync(); } catch (Exception e) { - Timber.e(e, e.getMessage()); + tracker.reportException(e); } finally { callback.finished(); Timber.d("%s: end sync", account); } } - private void synchronize() { + private void synchronize() throws IOException { pushLocalChanges(); List gtaskLists = new ArrayList<>(); - try { - String nextPageToken = null; - do { - TaskLists remoteLists = gtasksInvoker.allGtaskLists(nextPageToken); - if (remoteLists == null) { - break; - } - List items = remoteLists.getItems(); - if (items != null) { - gtaskLists.addAll(items); - } - nextPageToken = remoteLists.getNextPageToken(); - } while (nextPageToken != null); - gtasksListService.updateLists(gtaskLists); - if (gtasksListService.getList(gtasksPreferenceService.getDefaultList()) == null) { - gtasksPreferenceService.setDefaultList(null); + String nextPageToken = null; + do { + TaskLists remoteLists = gtasksInvoker.allGtaskLists(nextPageToken); + if (remoteLists == null) { + break; } - } catch (IOException e) { - Timber.e(e, e.getMessage()); + List items = remoteLists.getItems(); + if (items != null) { + gtaskLists.addAll(items); + } + nextPageToken = remoteLists.getNextPageToken(); + } while (nextPageToken != null); + gtasksListService.updateLists(gtaskLists); + if (gtasksListService.getList(gtasksPreferenceService.getDefaultList()) == null) { + gtasksPreferenceService.setDefaultList(null); } - for (final GtasksList list : gtasksListService.getListsToUpdate(gtaskLists)) { fetchAndApplyRemoteChanges(list); } @@ -184,7 +181,7 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter try { pushTask(task, task.getMergedValues(), gtasksInvoker); } catch (IOException e) { - Timber.e(e, e.getMessage()); + tracker.reportException(e); } } } @@ -268,7 +265,7 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter try { invoker.updateGtask(listId, remoteModel); } catch(HttpNotFoundException e) { - Timber.e("Received 404 response, deleting %s", gtasksMetadata); + tracker.reportException(e); metadataDao.delete(gtasksMetadata.getId()); return; } @@ -336,7 +333,7 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter gtasksTaskListUpdater.correctOrderAndIndentForList(listId); } } catch (IOException e) { - Timber.e(e, e.getMessage()); + tracker.reportException(e); } } diff --git a/src/googleplay/java/org/tasks/gtasks/GoogleTasksUnsuccessfulResponseHandler.java b/src/googleplay/java/org/tasks/gtasks/GoogleTasksUnsuccessfulResponseHandler.java new file mode 100644 index 000000000..976732898 --- /dev/null +++ b/src/googleplay/java/org/tasks/gtasks/GoogleTasksUnsuccessfulResponseHandler.java @@ -0,0 +1,66 @@ +package org.tasks.gtasks; + +import android.content.Context; + +import com.google.android.gms.auth.GoogleAuthException; +import com.google.android.gms.auth.GoogleAuthUtil; +import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; +import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.http.HttpUnsuccessfulResponseHandler; +import com.google.api.client.util.BackOff; +import com.google.api.client.util.ExponentialBackOff; +import com.google.api.services.tasks.TasksScopes; +import com.todoroo.astrid.gtasks.api.HttpNotFoundException; + +import java.io.IOException; + +import timber.log.Timber; + +public class GoogleTasksUnsuccessfulResponseHandler implements HttpUnsuccessfulResponseHandler { + + private static final BackOff BACKOFF = new ExponentialBackOff.Builder().build(); + + private final Context context; + private final GoogleAccountCredential googleAccountCredential; + private final HttpBackOffUnsuccessfulResponseHandler backoffHandler = new HttpBackOffUnsuccessfulResponseHandler(BACKOFF); + + public GoogleTasksUnsuccessfulResponseHandler(Context context, GoogleAccountCredential googleAccountCredential) { + this.context = context; + this.googleAccountCredential = googleAccountCredential; + } + + @Override + public boolean handleResponse(HttpRequest request, HttpResponse response, boolean supportsRetry) throws IOException { + HttpResponseException httpResponseException = new HttpResponseException(response); + Timber.e(httpResponseException, httpResponseException.getMessage()); + if (!supportsRetry) { + return false; + } + int statusCode = response.getStatusCode(); + if ((statusCode == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED || statusCode == HttpStatusCodes.STATUS_CODE_FORBIDDEN)) { + clearToken(googleAccountCredential); + } else if (statusCode == 400) { // bad request + throw httpResponseException; + } else if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) { + throw new HttpNotFoundException(httpResponseException); + } + + return backoffHandler.handleResponse(request, response, supportsRetry); + } + + private void clearToken(GoogleAccountCredential credential) throws IOException { + try { + String token = credential.getToken(); + Timber.d("Invalidating %s", token); + GoogleAuthUtil.clearToken(context, token); + GoogleAuthUtil.getTokenWithNotification(context, credential.getSelectedAccount(), "oauth2:" + TasksScopes.TASKS, null); + } catch (GoogleAuthException e) { + Timber.e(e, e.getMessage()); + throw new IOException(e); + } + } +}