Display notification for recoverable auth errors

pull/467/head
Alex Baker 9 years ago
parent a8692ffd76
commit 904edd9b43

@ -17,7 +17,6 @@ import com.google.api.services.tasks.model.TaskLists;
import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.analytics.Tracker;
import org.tasks.gtasks.GoogleTasksUnsuccessfulResponseHandler; import org.tasks.gtasks.GoogleTasksUnsuccessfulResponseHandler;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
@ -39,14 +38,12 @@ import timber.log.Timber;
public class GtasksInvoker { public class GtasksInvoker {
private final Context context; private final Context context;
private final Tracker tracker;
private final GoogleAccountCredential credential; private final GoogleAccountCredential credential;
private final Tasks service; private final Tasks service;
@Inject @Inject
public GtasksInvoker(@ForApplication Context context, GtasksPreferenceService preferenceService, Tracker tracker) { public GtasksInvoker(@ForApplication Context context, GtasksPreferenceService preferenceService) {
this.context = context; this.context = context;
this.tracker = tracker;
credential = GoogleAccountCredential.usingOAuth2(context, Collections.singletonList(TasksScopes.TASKS)); credential = GoogleAccountCredential.usingOAuth2(context, Collections.singletonList(TasksScopes.TASKS));
setUserName(preferenceService.getUserName()); setUserName(preferenceService.getUserName());
service = new Tasks.Builder(new NetHttpTransport(), new JacksonFactory(), credential) service = new Tasks.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
@ -124,16 +121,10 @@ public class GtasksInvoker {
private synchronized <T> T execute(TasksRequest<T> request) throws IOException { private synchronized <T> T execute(TasksRequest<T> request) throws IOException {
String caller = getCaller(); String caller = getCaller();
Timber.d("%s request: %s", caller, request); Timber.d("%s request: %s", caller, request);
T response;
try {
HttpRequest httpRequest = request.buildHttpRequest(); HttpRequest httpRequest = request.buildHttpRequest();
httpRequest.setUnsuccessfulResponseHandler(new GoogleTasksUnsuccessfulResponseHandler(context, credential)); httpRequest.setUnsuccessfulResponseHandler(new GoogleTasksUnsuccessfulResponseHandler(context, credential));
HttpResponse httpResponse = httpRequest.execute(); HttpResponse httpResponse = httpRequest.execute();
response = httpResponse.parseAs(request.getResponseClass()); T response = httpResponse.parseAs(request.getResponseClass());
} catch (IOException e) {
tracker.reportException(e);
throw e;
}
Timber.d("%s response: %s", caller, prettyPrint(response)); Timber.d("%s response: %s", caller, prettyPrint(response));
return response; return response;
} }

@ -7,6 +7,7 @@ package com.todoroo.astrid.gtasks.sync;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field; import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Functions;
@ -24,6 +25,7 @@ import com.todoroo.astrid.gtasks.OrderedMetadataListUpdater;
import com.todoroo.astrid.gtasks.api.GtasksInvoker; import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.gtasks.api.MoveRequest; import com.todoroo.astrid.gtasks.api.MoveRequest;
import org.tasks.analytics.Tracker;
import org.tasks.gtasks.SyncAdapterHelper; import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
@ -46,17 +48,19 @@ public class GtasksSyncService {
private final GtasksInvoker gtasksInvoker; private final GtasksInvoker gtasksInvoker;
private final LinkedBlockingQueue<SyncOnSaveOperation> operationQueue = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<SyncOnSaveOperation> operationQueue = new LinkedBlockingQueue<>();
private final SyncAdapterHelper syncAdapterHelper; private final SyncAdapterHelper syncAdapterHelper;
private final Tracker tracker;
@Inject @Inject
public GtasksSyncService(MetadataDao metadataDao, TaskDao taskDao, public GtasksSyncService(MetadataDao metadataDao, TaskDao taskDao,
GtasksPreferenceService gtasksPreferenceService, GtasksPreferenceService gtasksPreferenceService,
GtasksInvoker gtasksInvoker, GtasksInvoker gtasksInvoker,
SyncAdapterHelper syncAdapterHelper) { SyncAdapterHelper syncAdapterHelper, Tracker tracker) {
this.metadataDao = metadataDao; this.metadataDao = metadataDao;
this.taskDao = taskDao; this.taskDao = taskDao;
this.gtasksPreferenceService = gtasksPreferenceService; this.gtasksPreferenceService = gtasksPreferenceService;
this.gtasksInvoker = gtasksInvoker; this.gtasksInvoker = gtasksInvoker;
this.syncAdapterHelper = syncAdapterHelper; this.syncAdapterHelper = syncAdapterHelper;
this.tracker = tracker;
new OperationPushThread(operationQueue).start(); new OperationPushThread(operationQueue).start();
} }
@ -110,8 +114,10 @@ public class GtasksSyncService {
} }
try { try {
op.op(gtasksInvoker); op.op(gtasksInvoker);
} catch (UserRecoverableAuthIOException ignored) {
} catch (IOException e) { } catch (IOException e) {
Timber.e(e, e.getMessage()); tracker.reportException(e);
} }
} }
} }

@ -17,14 +17,18 @@
package org.tasks.gtasks; package org.tasks.gtasks;
import android.accounts.Account; import android.accounts.Account;
import android.app.PendingIntent;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SyncResult; import android.content.SyncResult;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.google.api.services.tasks.model.TaskLists; import com.google.api.services.tasks.model.TaskLists;
import com.google.api.services.tasks.model.Tasks; import com.google.api.services.tasks.model.Tasks;
@ -50,12 +54,14 @@ import com.todoroo.astrid.gtasks.api.HttpNotFoundException;
import com.todoroo.astrid.gtasks.sync.GtasksSyncService; import com.todoroo.astrid.gtasks.sync.GtasksSyncService;
import com.todoroo.astrid.gtasks.sync.GtasksTaskContainer; import com.todoroo.astrid.gtasks.sync.GtasksTaskContainer;
import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.utility.Constants;
import org.tasks.Broadcaster; import org.tasks.Broadcaster;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.injection.InjectingAbstractThreadedSyncAdapter; import org.tasks.injection.InjectingAbstractThreadedSyncAdapter;
import org.tasks.injection.SyncAdapterComponent; import org.tasks.injection.SyncAdapterComponent;
import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.sync.RecordSyncStatusCallback; import org.tasks.sync.RecordSyncStatusCallback;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
@ -98,6 +104,7 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
@Inject MetadataDao metadataDao; @Inject MetadataDao metadataDao;
@Inject GtasksMetadata gtasksMetadataFactory; @Inject GtasksMetadata gtasksMetadataFactory;
@Inject Tracker tracker; @Inject Tracker tracker;
@Inject NotificationManager notificationManager;
public GoogleTaskSyncAdapter(Context context, boolean autoInitialize) { public GoogleTaskSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize); super(context, autoInitialize);
@ -135,6 +142,11 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
callback.started(); callback.started();
synchronize(); synchronize();
gtasksPreferenceService.recordSuccessfulSync(); gtasksPreferenceService.recordSuccessfulSync();
} catch (UserRecoverableAuthIOException e) {
Timber.e(e, e.getMessage());
sendNotification(getContext(), e.getIntent());
} catch (IOException e) {
Timber.e(e, e.getMessage());
} catch (Exception e) { } catch (Exception e) {
tracker.reportException(e); tracker.reportException(e);
} finally { } finally {
@ -143,6 +155,20 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
} }
} }
private void sendNotification(Context context, Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND);
PendingIntent resolve = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context).setAutoCancel(true)
.setContentIntent(resolve)
.setContentTitle(context.getString(R.string.sync_error_permissions))
.setContentText(context.getString(R.string.common_google_play_services_notification_ticker))
.setAutoCancel(true)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setTicker(context.getString(R.string.common_google_play_services_notification_ticker));
notificationManager.notify(Constants.NOTIFICATION_SYNC_ERROR, builder.build());
}
private void synchronize() throws IOException { private void synchronize() throws IOException {
pushLocalChanges(); pushLocalChanges();
@ -168,13 +194,15 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
} }
} }
private void pushLocalChanges() { private void pushLocalChanges() throws UserRecoverableAuthIOException {
List<Task> tasks = taskDao.toList(Query.select(Task.PROPERTIES) List<Task> tasks = taskDao.toList(Query.select(Task.PROPERTIES)
.join(Join.left(Metadata.TABLE, Criterion.and(MetadataDao.MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK)))) .join(Join.left(Metadata.TABLE, Criterion.and(MetadataDao.MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK))))
.where(Criterion.or(Task.MODIFICATION_DATE.gt(GtasksMetadata.LAST_SYNC), GtasksMetadata.ID.eq("")))); .where(Criterion.or(Task.MODIFICATION_DATE.gt(GtasksMetadata.LAST_SYNC), GtasksMetadata.ID.eq(""))));
for (Task task : tasks) { for (Task task : tasks) {
try { try {
pushTask(task, task.getMergedValues(), gtasksInvoker); pushTask(task, task.getMergedValues(), gtasksInvoker);
} catch (UserRecoverableAuthIOException e) {
throw e;
} catch (IOException e) { } catch (IOException e) {
tracker.reportException(e); tracker.reportException(e);
} }
@ -292,7 +320,7 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
MetadataDao.MetadataCriteria.isDeleted())); MetadataDao.MetadataCriteria.isDeleted()));
} }
private synchronized void fetchAndApplyRemoteChanges(GtasksList list) { private synchronized void fetchAndApplyRemoteChanges(GtasksList list) throws UserRecoverableAuthIOException {
String listId = list.getRemoteId(); String listId = list.getRemoteId();
long lastSyncDate = list.getLastSync(); long lastSyncDate = list.getLastSync();
@ -327,6 +355,8 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
storeObjectDao.persist(list); storeObjectDao.persist(list);
gtasksTaskListUpdater.correctOrderAndIndentForList(listId); gtasksTaskListUpdater.correctOrderAndIndentForList(listId);
} }
} catch (UserRecoverableAuthIOException e) {
throw e;
} catch (IOException e) { } catch (IOException e) {
tracker.reportException(e); tracker.reportException(e);
} }

@ -42,7 +42,10 @@ public class GoogleTasksUnsuccessfulResponseHandler implements HttpUnsuccessfulR
} }
int statusCode = response.getStatusCode(); int statusCode = response.getStatusCode();
if ((statusCode == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED || statusCode == HttpStatusCodes.STATUS_CODE_FORBIDDEN)) { if ((statusCode == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED || statusCode == HttpStatusCodes.STATUS_CODE_FORBIDDEN)) {
clearToken(googleAccountCredential); boolean shouldRetry = clearToken(googleAccountCredential);
if (!shouldRetry) {
return false;
}
} else if (statusCode == 400) { // bad request } else if (statusCode == 400) { // bad request
throw httpResponseException; throw httpResponseException;
} else if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) { } else if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
@ -52,15 +55,19 @@ public class GoogleTasksUnsuccessfulResponseHandler implements HttpUnsuccessfulR
return backoffHandler.handleResponse(request, response, supportsRetry); return backoffHandler.handleResponse(request, response, supportsRetry);
} }
private void clearToken(GoogleAccountCredential credential) throws IOException { private boolean clearToken(GoogleAccountCredential credential){
try { try {
String token = credential.getToken(); String token = credential.getToken();
Timber.d("Invalidating %s", token); Timber.d("Invalidating %s", token);
GoogleAuthUtil.clearToken(context, token); GoogleAuthUtil.clearToken(context, token);
GoogleAuthUtil.getTokenWithNotification(context, credential.getSelectedAccount(), "oauth2:" + TasksScopes.TASKS, null); GoogleAuthUtil.getToken(context, credential.getSelectedAccount(), "oauth2:" + TasksScopes.TASKS, null);
return true;
} catch (GoogleAuthException e) { } catch (GoogleAuthException e) {
Timber.e(e, e.getMessage()); Timber.e(e, e.getMessage());
throw new IOException(e); return false;
} catch (IOException e) {
Timber.e(e, e.getMessage());
return true;
} }
} }
} }

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

@ -18,4 +18,5 @@ public final class Constants {
/** Notification Manager id for timing */ /** Notification Manager id for timing */
public static final int NOTIFICATION_TIMER = -2; public static final int NOTIFICATION_TIMER = -2;
public static final int NOTIFICATION_SYNC_ERROR = -3;
} }

@ -822,5 +822,6 @@ File %1$s contained %2$s.\n\n
<string name="widget_row_settings">Row settings</string> <string name="widget_row_settings">Row settings</string>
<string name="notification_shade">Notification shade</string> <string name="notification_shade">Notification shade</string>
<string name="notification_description">Show task description</string> <string name="notification_description">Show task description</string>
<string name="sync_error_permissions">Tasks requires permission</string>
</resources> </resources>

Loading…
Cancel
Save