mirror of https://github.com/tasks/tasks
Add GoogleTaskSyncAdapter
parent
8bf14181b5
commit
b0d14a15f9
@ -1,30 +0,0 @@
|
|||||||
package com.todoroo.astrid.gtasks.sync;
|
|
||||||
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksList;
|
|
||||||
import com.todoroo.astrid.sync.SyncResultCallback;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class GtasksSyncV2Provider {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public GtasksSyncV2Provider() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActive() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeActiveTasks(SyncResultCallback callback) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeList(GtasksList list, SyncResultCallback callback) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearCompleted(GtasksList list, SyncResultCallback callback) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package org.tasks.injection;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = ApplicationModule.class)
|
||||||
|
public interface ApplicationComponent extends BaseApplicationComponent {
|
||||||
|
}
|
||||||
@ -1,10 +0,0 @@
|
|||||||
package org.tasks.scheduling;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class GtasksBackgroundService {
|
|
||||||
@Inject
|
|
||||||
public GtasksBackgroundService() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
package com.todoroo.astrid.gtasks.sync;
|
|
||||||
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksList;
|
|
||||||
import com.todoroo.astrid.sync.SyncResultCallback;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class GtasksSyncV2Provider {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public GtasksSyncV2Provider() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActive() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeActiveTasks(SyncResultCallback callback) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeList(GtasksList list, SyncResultCallback callback) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearCompleted(GtasksList list, SyncResultCallback callback) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package org.tasks.injection;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = ApplicationModule.class)
|
||||||
|
public interface ApplicationComponent extends BaseApplicationComponent {
|
||||||
|
}
|
||||||
@ -1,10 +0,0 @@
|
|||||||
package org.tasks.scheduling;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class GtasksBackgroundService {
|
|
||||||
@Inject
|
|
||||||
public GtasksBackgroundService() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,333 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.gtasks.sync;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.google.api.services.tasks.model.TaskList;
|
|
||||||
import com.google.api.services.tasks.model.TaskLists;
|
|
||||||
import com.google.api.services.tasks.model.Tasks;
|
|
||||||
import com.todoroo.andlib.data.AbstractModel;
|
|
||||||
import com.todoroo.andlib.data.TodorooCursor;
|
|
||||||
import com.todoroo.andlib.sql.Criterion;
|
|
||||||
import com.todoroo.andlib.sql.Join;
|
|
||||||
import com.todoroo.andlib.sql.Query;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
|
|
||||||
import com.todoroo.astrid.dao.StoreObjectDao;
|
|
||||||
import com.todoroo.astrid.dao.TaskDao;
|
|
||||||
import com.todoroo.astrid.data.Metadata;
|
|
||||||
import com.todoroo.astrid.data.SyncFlags;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksList;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksListService;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksMetadata;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksMetadataService;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksTaskListUpdater;
|
|
||||||
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
|
|
||||||
import com.todoroo.astrid.service.TaskService;
|
|
||||||
import com.todoroo.astrid.sync.SyncResultCallback;
|
|
||||||
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.injection.ForApplication;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.sync.SyncExecutor;
|
|
||||||
import org.tasks.time.DateTime;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class GtasksSyncV2Provider {
|
|
||||||
|
|
||||||
public class SyncExceptionHandler {
|
|
||||||
public void handleException(Exception e) {
|
|
||||||
Timber.e(e, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final SyncExceptionHandler handler = new SyncExceptionHandler();
|
|
||||||
|
|
||||||
private void finishSync(SyncResultCallback callback) {
|
|
||||||
gtasksPreferenceService.recordSuccessfulSync();
|
|
||||||
callback.finished();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TaskService taskService;
|
|
||||||
private final StoreObjectDao storeObjectDao;
|
|
||||||
private final GtasksPreferenceService gtasksPreferenceService;
|
|
||||||
private final GtasksSyncService gtasksSyncService;
|
|
||||||
private final GtasksListService gtasksListService;
|
|
||||||
private final GtasksMetadataService gtasksMetadataService;
|
|
||||||
private final GtasksTaskListUpdater gtasksTaskListUpdater;
|
|
||||||
private final Context context;
|
|
||||||
private final Preferences preferences;
|
|
||||||
private final SyncExecutor executor;
|
|
||||||
private final GtasksInvoker gtasksInvoker;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public GtasksSyncV2Provider(TaskService taskService, StoreObjectDao storeObjectDao, GtasksPreferenceService gtasksPreferenceService,
|
|
||||||
GtasksSyncService gtasksSyncService, GtasksListService gtasksListService, GtasksMetadataService gtasksMetadataService,
|
|
||||||
GtasksTaskListUpdater gtasksTaskListUpdater, @ForApplication Context context, Preferences preferences,
|
|
||||||
SyncExecutor executor, GtasksInvoker gtasksInvoker) {
|
|
||||||
this.taskService = taskService;
|
|
||||||
this.storeObjectDao = storeObjectDao;
|
|
||||||
this.gtasksPreferenceService = gtasksPreferenceService;
|
|
||||||
this.gtasksSyncService = gtasksSyncService;
|
|
||||||
this.gtasksListService = gtasksListService;
|
|
||||||
this.gtasksMetadataService = gtasksMetadataService;
|
|
||||||
this.gtasksTaskListUpdater = gtasksTaskListUpdater;
|
|
||||||
this.context = context;
|
|
||||||
this.preferences = preferences;
|
|
||||||
this.executor = executor;
|
|
||||||
this.gtasksInvoker = gtasksInvoker;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getName() {
|
|
||||||
return context.getString(R.string.gtasks_GPr_header);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void signOut() {
|
|
||||||
gtasksPreferenceService.clearLastSyncDate();
|
|
||||||
gtasksPreferenceService.setUserName(null);
|
|
||||||
gtasksMetadataService.clearMetadata();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActive() {
|
|
||||||
return gtasksPreferenceService.isLoggedIn();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeActiveTasks(final SyncResultCallback callback) {
|
|
||||||
executor.execute(callback, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
callback.started();
|
|
||||||
|
|
||||||
try {
|
|
||||||
TaskLists remoteLists = null;
|
|
||||||
try {
|
|
||||||
List<TaskList> gtaskLists = new ArrayList<>();
|
|
||||||
String nextPageToken = null;
|
|
||||||
do {
|
|
||||||
remoteLists = gtasksInvoker.allGtaskLists(nextPageToken);
|
|
||||||
if (remoteLists == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
List<TaskList> 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);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
handler.handleException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remoteLists == null) {
|
|
||||||
finishSync(callback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GtasksList> listsToUpdate = gtasksListService.getListsToUpdate(remoteLists);
|
|
||||||
|
|
||||||
if (listsToUpdate.isEmpty()) {
|
|
||||||
finishSync(callback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final AtomicInteger finisher = new AtomicInteger(listsToUpdate.size());
|
|
||||||
|
|
||||||
for (final GtasksList list : listsToUpdate) {
|
|
||||||
executor.execute(callback, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
synchronizeListHelper(list, gtasksInvoker, handler);
|
|
||||||
if (finisher.decrementAndGet() == 0) {
|
|
||||||
pushUpdated(gtasksInvoker);
|
|
||||||
finishSync(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
handler.handleException(e);
|
|
||||||
callback.finished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void pushUpdated(GtasksInvoker invoker) {
|
|
||||||
TodorooCursor<Task> queued = taskService.query(Query.select(Task.PROPERTIES).
|
|
||||||
join(Join.left(Metadata.TABLE, Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK)))).where(
|
|
||||||
Criterion.or(Task.MODIFICATION_DATE.gt(GtasksMetadata.LAST_SYNC), Metadata.KEY.isNull())));
|
|
||||||
pushTasks(queued, invoker);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void pushTasks(TodorooCursor<Task> queued, GtasksInvoker invoker) {
|
|
||||||
try {
|
|
||||||
for (queued.moveToFirst(); !queued.isAfterLast(); queued.moveToNext()) {
|
|
||||||
Task task = new Task(queued);
|
|
||||||
try {
|
|
||||||
gtasksSyncService.pushTaskOnSave(task, task.getMergedValues(), invoker);
|
|
||||||
} catch (IOException e) {
|
|
||||||
handler.handleException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
queued.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearCompleted(final GtasksList list, final SyncResultCallback callback) {
|
|
||||||
executor.execute(callback, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
callback.started();
|
|
||||||
|
|
||||||
try {
|
|
||||||
gtasksSyncService.clearCompleted(list.getRemoteId());
|
|
||||||
} finally {
|
|
||||||
callback.finished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeList(final GtasksList gtasksList, final SyncResultCallback callback) {
|
|
||||||
executor.execute(callback, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
callback.started();
|
|
||||||
|
|
||||||
try {
|
|
||||||
gtasksSyncService.waitUntilEmpty();
|
|
||||||
synchronizeListHelper(gtasksList, gtasksInvoker, null);
|
|
||||||
} finally {
|
|
||||||
callback.finished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void synchronizeListHelper(GtasksList list, GtasksInvoker invoker,
|
|
||||||
SyncExceptionHandler errorHandler) {
|
|
||||||
String listId = list.getRemoteId();
|
|
||||||
long lastSyncDate = list.getLastSync();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find tasks which have been associated with the list internally, but have not yet been
|
|
||||||
* pushed to Google Tasks (and so haven't yet got a valid ID).
|
|
||||||
*/
|
|
||||||
Criterion not_pushed_tasks = Criterion.and(
|
|
||||||
Metadata.KEY.eq(GtasksMetadata.METADATA_KEY),
|
|
||||||
GtasksMetadata.LIST_ID.eq(listId),
|
|
||||||
GtasksMetadata.ID.eq("")
|
|
||||||
);
|
|
||||||
TodorooCursor<Task> qs = taskService.query(Query.select(Task.PROPERTIES).
|
|
||||||
join(Join.left(Metadata.TABLE, Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK)))).where(not_pushed_tasks)
|
|
||||||
);
|
|
||||||
pushTasks(qs, invoker);
|
|
||||||
|
|
||||||
boolean includeDeletedAndHidden = lastSyncDate != 0;
|
|
||||||
try {
|
|
||||||
List<com.google.api.services.tasks.model.Task> tasks = new ArrayList<>();
|
|
||||||
String nextPageToken = null;
|
|
||||||
do {
|
|
||||||
Tasks taskList = invoker.getAllGtasksFromListId(listId, includeDeletedAndHidden,
|
|
||||||
includeDeletedAndHidden, lastSyncDate + 1000L, nextPageToken);
|
|
||||||
if (taskList == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
List<com.google.api.services.tasks.model.Task> items = taskList.getItems();
|
|
||||||
if (items != null) {
|
|
||||||
tasks.addAll(items);
|
|
||||||
}
|
|
||||||
nextPageToken = taskList.getNextPageToken();
|
|
||||||
} while (nextPageToken != null);
|
|
||||||
|
|
||||||
if (!tasks.isEmpty()) {
|
|
||||||
for (com.google.api.services.tasks.model.Task t : tasks) {
|
|
||||||
GtasksTaskContainer container = new GtasksTaskContainer(t, listId, GtasksMetadata.createEmptyMetadataWithoutList(AbstractModel.NO_ID));
|
|
||||||
gtasksMetadataService.findLocalMatch(container);
|
|
||||||
container.gtaskMetadata.setValue(GtasksMetadata.GTASKS_ORDER, Long.parseLong(t.getPosition()));
|
|
||||||
container.gtaskMetadata.setValue(GtasksMetadata.PARENT_TASK, gtasksMetadataService.localIdForGtasksId(t.getParent()));
|
|
||||||
container.gtaskMetadata.setValue(GtasksMetadata.LAST_SYNC, DateUtilities.now() + 1000L);
|
|
||||||
write(container);
|
|
||||||
lastSyncDate = Math.max(lastSyncDate, container.getUpdateTime());
|
|
||||||
}
|
|
||||||
list.setLastSync(lastSyncDate);
|
|
||||||
storeObjectDao.persist(list);
|
|
||||||
gtasksTaskListUpdater.correctOrderAndIndentForList(listId);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (errorHandler != null) {
|
|
||||||
errorHandler.handleException(e);
|
|
||||||
} else {
|
|
||||||
Timber.e(e, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void write(GtasksTaskContainer task) {
|
|
||||||
// merge astrid dates with google dates
|
|
||||||
|
|
||||||
if(task.task.isSaved()) {
|
|
||||||
Task local = taskService.fetchById(task.task.getId(), Task.PROPERTIES);
|
|
||||||
if (local == null) {
|
|
||||||
task.task.clearValue(Task.ID);
|
|
||||||
task.task.clearValue(Task.UUID);
|
|
||||||
} else {
|
|
||||||
mergeDates(task.task, local);
|
|
||||||
}
|
|
||||||
} else { // Set default importance and reminders for remotely created tasks
|
|
||||||
task.task.setImportance(preferences.getIntegerFromString(
|
|
||||||
R.string.p_default_importance_key, Task.IMPORTANCE_SHOULD_DO));
|
|
||||||
TaskDao.setDefaultReminders(preferences, task.task);
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(task.task.getTitle())) {
|
|
||||||
task.task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
|
|
||||||
gtasksMetadataService.saveTaskAndMetadata(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mergeDates(Task remote, Task local) {
|
|
||||||
if (remote.hasDueDate() && local.hasDueTime()) {
|
|
||||||
DateTime oldDate = newDateTime(local.getDueDate());
|
|
||||||
DateTime newDate = newDateTime(remote.getDueDate())
|
|
||||||
.withHourOfDay(oldDate.getHourOfDay())
|
|
||||||
.withMinuteOfHour(oldDate.getMinuteOfHour())
|
|
||||||
.withSecondOfMinute(oldDate.getSecondOfMinute());
|
|
||||||
local.setDueDateAdjustingHideUntil(
|
|
||||||
Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, newDate.getMillis()));
|
|
||||||
} else {
|
|
||||||
local.setDueDateAdjustingHideUntil(remote.getDueDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
remote.setHideUntil(local.getHideUntil());
|
|
||||||
remote.setDueDate(local.getDueDate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.service;
|
||||||
|
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksList;
|
||||||
|
import com.todoroo.astrid.gtasks.sync.GtasksSyncService;
|
||||||
|
import com.todoroo.astrid.sync.SyncResultCallback;
|
||||||
|
|
||||||
|
import org.tasks.gtasks.SyncAdapterHelper;
|
||||||
|
import org.tasks.sync.SyncExecutor;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SyncV2Service is a simplified synchronization interface for supporting
|
||||||
|
* next-generation sync interfaces such as Google Tasks and Astrid.com
|
||||||
|
*
|
||||||
|
* @author Tim Su <tim@astrid.com>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SyncV2Service {
|
||||||
|
/*
|
||||||
|
* At present, sync provider interactions are handled through code. If
|
||||||
|
* there is enough interest, the Astrid team could create an interface
|
||||||
|
* for responding to sync requests through this new API.
|
||||||
|
*/
|
||||||
|
private final SyncExecutor syncExecutor;
|
||||||
|
private final SyncAdapterHelper syncAdapterHelper;
|
||||||
|
private final GtasksSyncService gtasksSyncService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SyncV2Service(SyncExecutor syncExecutor, SyncAdapterHelper syncAdapterHelper, GtasksSyncService gtasksSyncService) {
|
||||||
|
this.syncExecutor = syncExecutor;
|
||||||
|
this.syncAdapterHelper = syncAdapterHelper;
|
||||||
|
this.gtasksSyncService = gtasksSyncService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearCompleted(final GtasksList list, final SyncResultCallback callback) {
|
||||||
|
if (syncAdapterHelper.isEnabled()) {
|
||||||
|
syncExecutor.execute(callback, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
callback.started();
|
||||||
|
try {
|
||||||
|
gtasksSyncService.clearCompleted(list.getRemoteId());
|
||||||
|
} finally {
|
||||||
|
callback.finished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,296 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.tasks.gtasks;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.content.ContentProviderClient;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SyncResult;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.google.api.services.tasks.model.TaskList;
|
||||||
|
import com.google.api.services.tasks.model.TaskLists;
|
||||||
|
import com.google.api.services.tasks.model.Tasks;
|
||||||
|
import com.todoroo.andlib.data.AbstractModel;
|
||||||
|
import com.todoroo.andlib.data.TodorooCursor;
|
||||||
|
import com.todoroo.andlib.sql.Criterion;
|
||||||
|
import com.todoroo.andlib.sql.Join;
|
||||||
|
import com.todoroo.andlib.sql.Query;
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities;
|
||||||
|
import com.todoroo.astrid.dao.MetadataDao;
|
||||||
|
import com.todoroo.astrid.dao.StoreObjectDao;
|
||||||
|
import com.todoroo.astrid.dao.TaskDao;
|
||||||
|
import com.todoroo.astrid.data.Metadata;
|
||||||
|
import com.todoroo.astrid.data.SyncFlags;
|
||||||
|
import com.todoroo.astrid.data.Task;
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksList;
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksListService;
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksMetadata;
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksMetadataService;
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksTaskListUpdater;
|
||||||
|
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
|
||||||
|
import com.todoroo.astrid.gtasks.sync.GtasksSyncService;
|
||||||
|
import com.todoroo.astrid.gtasks.sync.GtasksTaskContainer;
|
||||||
|
import com.todoroo.astrid.service.TaskService;
|
||||||
|
|
||||||
|
import org.tasks.Broadcaster;
|
||||||
|
import org.tasks.R;
|
||||||
|
import org.tasks.injection.InjectingAbstractThreadedSyncAdapter;
|
||||||
|
import org.tasks.injection.SyncAdapterComponent;
|
||||||
|
import org.tasks.preferences.Preferences;
|
||||||
|
import org.tasks.sync.RecordSyncStatusCallback;
|
||||||
|
import org.tasks.time.DateTime;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static org.tasks.date.DateTimeUtils.newDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a sync adapter for the app.
|
||||||
|
*
|
||||||
|
* <p>This class is instantiated in {@link GoogleTaskSyncService}, which also binds SyncAdapter to the system.
|
||||||
|
* SyncAdapter should only be initialized in SyncService, never anywhere else.
|
||||||
|
*
|
||||||
|
* <p>The system calls onPerformSync() via an RPC call through the IBinder object supplied by
|
||||||
|
* SyncService.
|
||||||
|
*/
|
||||||
|
public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter {
|
||||||
|
|
||||||
|
@Inject GtasksPreferenceService gtasksPreferenceService;
|
||||||
|
@Inject Broadcaster broadcaster;
|
||||||
|
@Inject TaskService taskService;
|
||||||
|
@Inject StoreObjectDao storeObjectDao;
|
||||||
|
@Inject GtasksSyncService gtasksSyncService;
|
||||||
|
@Inject GtasksListService gtasksListService;
|
||||||
|
@Inject GtasksMetadataService gtasksMetadataService;
|
||||||
|
@Inject GtasksTaskListUpdater gtasksTaskListUpdater;
|
||||||
|
@Inject Preferences preferences;
|
||||||
|
@Inject GtasksInvoker gtasksInvoker;
|
||||||
|
|
||||||
|
|
||||||
|
public GoogleTaskSyncAdapter(Context context, boolean autoInitialize) {
|
||||||
|
super(context, autoInitialize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GoogleTaskSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) {
|
||||||
|
super(context, autoInitialize, allowParallelSyncs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the Android system in response to a request to run the sync adapter. The work
|
||||||
|
* required to read data from the network, parse it, and store it in the content provider is
|
||||||
|
* done here. Extending AbstractThreadedSyncAdapter ensures that all methods within SyncAdapter
|
||||||
|
* run on a background thread. For this reason, blocking I/O and other long-running tasks can be
|
||||||
|
* run <em>in situ</em>, and you don't have to set up a separate thread for them.
|
||||||
|
.
|
||||||
|
*
|
||||||
|
* <p>This is where we actually perform any work required to perform a sync.
|
||||||
|
* {@link android.content.AbstractThreadedSyncAdapter} guarantees that this will be called on a non-UI thread,
|
||||||
|
* so it is safe to peform blocking I/O here.
|
||||||
|
*
|
||||||
|
* <p>The syncResult argument allows you to pass information back to the method that triggered
|
||||||
|
* the sync.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPerformSync(Account account, Bundle extras, String authority,
|
||||||
|
ContentProviderClient provider, SyncResult syncResult) {
|
||||||
|
if (!account.name.equals(gtasksPreferenceService.getUserName())) {
|
||||||
|
Timber.d("Sync not enabled for %s", account);
|
||||||
|
syncResult.stats.numAuthExceptions++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
|
||||||
|
preferences.setBoolean(R.string.p_sync_warning_shown, false);
|
||||||
|
}
|
||||||
|
Timber.d("%s: start sync", account);
|
||||||
|
RecordSyncStatusCallback callback = new RecordSyncStatusCallback(gtasksPreferenceService, broadcaster);
|
||||||
|
try {
|
||||||
|
callback.started();
|
||||||
|
synchronize();
|
||||||
|
gtasksPreferenceService.recordSuccessfulSync();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Timber.e(e, e.getMessage());
|
||||||
|
} finally {
|
||||||
|
callback.finished();
|
||||||
|
Timber.d("%s: end sync", account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void synchronize() {
|
||||||
|
TaskLists remoteLists = null;
|
||||||
|
try {
|
||||||
|
List<TaskList> gtaskLists = new ArrayList<>();
|
||||||
|
String nextPageToken = null;
|
||||||
|
do {
|
||||||
|
remoteLists = gtasksInvoker.allGtaskLists(nextPageToken);
|
||||||
|
if (remoteLists == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
List<TaskList> 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);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteLists == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final GtasksList list : gtasksListService.getListsToUpdate(remoteLists)) {
|
||||||
|
synchronizeListHelper(list, gtasksInvoker);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushUpdated(gtasksInvoker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void pushUpdated(GtasksInvoker invoker) {
|
||||||
|
TodorooCursor<Task> queued = taskService.query(Query.select(Task.PROPERTIES).
|
||||||
|
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), Metadata.KEY.isNull())));
|
||||||
|
pushTasks(queued, invoker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void pushTasks(TodorooCursor<Task> queued, GtasksInvoker invoker) {
|
||||||
|
try {
|
||||||
|
for (queued.moveToFirst(); !queued.isAfterLast(); queued.moveToNext()) {
|
||||||
|
Task task = new Task(queued);
|
||||||
|
try {
|
||||||
|
gtasksSyncService.pushTaskOnSave(task, task.getMergedValues(), invoker);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
queued.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void synchronizeListHelper(GtasksList list, GtasksInvoker invoker) {
|
||||||
|
String listId = list.getRemoteId();
|
||||||
|
long lastSyncDate = list.getLastSync();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find tasks which have been associated with the list internally, but have not yet been
|
||||||
|
* pushed to Google Tasks (and so haven't yet got a valid ID).
|
||||||
|
*/
|
||||||
|
Criterion not_pushed_tasks = Criterion.and(
|
||||||
|
Metadata.KEY.eq(GtasksMetadata.METADATA_KEY),
|
||||||
|
GtasksMetadata.LIST_ID.eq(listId),
|
||||||
|
GtasksMetadata.ID.eq("")
|
||||||
|
);
|
||||||
|
TodorooCursor<Task> qs = taskService.query(Query.select(Task.PROPERTIES).
|
||||||
|
join(Join.left(Metadata.TABLE, Criterion.and(MetadataDao.MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK)))).where(not_pushed_tasks)
|
||||||
|
);
|
||||||
|
pushTasks(qs, invoker);
|
||||||
|
|
||||||
|
boolean includeDeletedAndHidden = lastSyncDate != 0;
|
||||||
|
try {
|
||||||
|
List<com.google.api.services.tasks.model.Task> tasks = new ArrayList<>();
|
||||||
|
String nextPageToken = null;
|
||||||
|
do {
|
||||||
|
Tasks taskList = invoker.getAllGtasksFromListId(listId, includeDeletedAndHidden,
|
||||||
|
includeDeletedAndHidden, lastSyncDate + 1000L, nextPageToken);
|
||||||
|
if (taskList == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
List<com.google.api.services.tasks.model.Task> items = taskList.getItems();
|
||||||
|
if (items != null) {
|
||||||
|
tasks.addAll(items);
|
||||||
|
}
|
||||||
|
nextPageToken = taskList.getNextPageToken();
|
||||||
|
} while (nextPageToken != null);
|
||||||
|
|
||||||
|
if (!tasks.isEmpty()) {
|
||||||
|
for (com.google.api.services.tasks.model.Task t : tasks) {
|
||||||
|
GtasksTaskContainer container = new GtasksTaskContainer(t, listId, GtasksMetadata.createEmptyMetadataWithoutList(AbstractModel.NO_ID));
|
||||||
|
gtasksMetadataService.findLocalMatch(container);
|
||||||
|
container.gtaskMetadata.setValue(GtasksMetadata.GTASKS_ORDER, Long.parseLong(t.getPosition()));
|
||||||
|
container.gtaskMetadata.setValue(GtasksMetadata.PARENT_TASK, gtasksMetadataService.localIdForGtasksId(t.getParent()));
|
||||||
|
container.gtaskMetadata.setValue(GtasksMetadata.LAST_SYNC, DateUtilities.now() + 1000L);
|
||||||
|
write(container);
|
||||||
|
lastSyncDate = Math.max(lastSyncDate, container.getUpdateTime());
|
||||||
|
}
|
||||||
|
list.setLastSync(lastSyncDate);
|
||||||
|
storeObjectDao.persist(list);
|
||||||
|
gtasksTaskListUpdater.correctOrderAndIndentForList(listId);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(GtasksTaskContainer task) {
|
||||||
|
// merge astrid dates with google dates
|
||||||
|
|
||||||
|
if(task.task.isSaved()) {
|
||||||
|
Task local = taskService.fetchById(task.task.getId(), Task.PROPERTIES);
|
||||||
|
if (local == null) {
|
||||||
|
task.task.clearValue(Task.ID);
|
||||||
|
task.task.clearValue(Task.UUID);
|
||||||
|
} else {
|
||||||
|
mergeDates(task.task, local);
|
||||||
|
}
|
||||||
|
} else { // Set default importance and reminders for remotely created tasks
|
||||||
|
task.task.setImportance(preferences.getIntegerFromString(
|
||||||
|
R.string.p_default_importance_key, Task.IMPORTANCE_SHOULD_DO));
|
||||||
|
TaskDao.setDefaultReminders(preferences, task.task);
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(task.task.getTitle())) {
|
||||||
|
task.task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
|
||||||
|
gtasksMetadataService.saveTaskAndMetadata(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mergeDates(Task remote, Task local) {
|
||||||
|
if (remote.hasDueDate() && local.hasDueTime()) {
|
||||||
|
DateTime oldDate = newDateTime(local.getDueDate());
|
||||||
|
DateTime newDate = newDateTime(remote.getDueDate())
|
||||||
|
.withHourOfDay(oldDate.getHourOfDay())
|
||||||
|
.withMinuteOfHour(oldDate.getMinuteOfHour())
|
||||||
|
.withSecondOfMinute(oldDate.getSecondOfMinute());
|
||||||
|
local.setDueDateAdjustingHideUntil(
|
||||||
|
Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, newDate.getMillis()));
|
||||||
|
} else {
|
||||||
|
local.setDueDateAdjustingHideUntil(remote.getDueDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
remote.setHideUntil(local.getHideUntil());
|
||||||
|
remote.setDueDate(local.getDueDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void inject(SyncAdapterComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.tasks.gtasks;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/** Service to handle sync requests.
|
||||||
|
*
|
||||||
|
* <p>This service is invoked in response to Intents with action android.content.SyncAdapter, and
|
||||||
|
* returns a Binder connection to SyncAdapter.
|
||||||
|
*
|
||||||
|
* <p>For performance, only one sync adapter will be initialized within this application's context.
|
||||||
|
*
|
||||||
|
* <p>Note: The SyncService itself is not notified when a new sync occurs. It's role is to
|
||||||
|
* manage the lifecycle of our {@link GoogleTaskSyncAdapter} and provide a handle to said SyncAdapter to the
|
||||||
|
* OS on request.
|
||||||
|
*/
|
||||||
|
public class GoogleTaskSyncService extends Service {
|
||||||
|
private static final Object sSyncAdapterLock = new Object();
|
||||||
|
private static GoogleTaskSyncAdapter sGoogleTaskSyncAdapter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe constructor, creates static {@link GoogleTaskSyncAdapter} instance.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
Timber.d("Service created");
|
||||||
|
synchronized (sSyncAdapterLock) {
|
||||||
|
if (sGoogleTaskSyncAdapter == null) {
|
||||||
|
sGoogleTaskSyncAdapter = new GoogleTaskSyncAdapter(getApplicationContext(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/**
|
||||||
|
* Logging-only destructor.
|
||||||
|
*/
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
Timber.d("Service destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return Binder handle for IPC communication with {@link GoogleTaskSyncAdapter}.
|
||||||
|
*
|
||||||
|
* <p>New sync requests will be sent directly to the SyncAdapter using this channel.
|
||||||
|
*
|
||||||
|
* @param intent Calling intent
|
||||||
|
* @return Binder handle for {@link GoogleTaskSyncAdapter}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return sGoogleTaskSyncAdapter.getSyncAdapterBinder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.tasks.injection;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = ApplicationModule.class)
|
||||||
|
public interface ApplicationComponent extends BaseApplicationComponent {
|
||||||
|
SyncAdapterComponent plus(SyncAdapterModule syncAdapterModule);
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.tasks.injection;
|
||||||
|
|
||||||
|
import android.content.AbstractThreadedSyncAdapter;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public abstract class InjectingAbstractThreadedSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
|
|
||||||
|
public InjectingAbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
|
||||||
|
super(context, autoInitialize);
|
||||||
|
inject(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InjectingAbstractThreadedSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) {
|
||||||
|
super(context, autoInitialize, allowParallelSyncs);
|
||||||
|
inject(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inject(Context context) {
|
||||||
|
inject(((InjectingApplication) context.getApplicationContext())
|
||||||
|
.getComponent()
|
||||||
|
.plus(new SyncAdapterModule()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void inject(SyncAdapterComponent component);
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package org.tasks.injection;
|
||||||
|
|
||||||
|
import org.tasks.gtasks.GoogleTaskSyncAdapter;
|
||||||
|
|
||||||
|
import dagger.Subcomponent;
|
||||||
|
|
||||||
|
@Subcomponent(modules = SyncAdapterModule.class)
|
||||||
|
public interface SyncAdapterComponent {
|
||||||
|
void inject(GoogleTaskSyncAdapter googleTaskSyncAdapter);
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package org.tasks.injection;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class SyncAdapterModule {
|
||||||
|
}
|
||||||
@ -1,60 +0,0 @@
|
|||||||
package org.tasks.scheduling;
|
|
||||||
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
|
|
||||||
import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider;
|
|
||||||
|
|
||||||
import org.tasks.Broadcaster;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.injection.IntentServiceComponent;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.sync.RecordSyncStatusCallback;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
||||||
|
|
||||||
public class GtasksBackgroundService extends RecurringIntervalIntentService {
|
|
||||||
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
@Inject GtasksPreferenceService gtasksPreferenceService;
|
|
||||||
@Inject GtasksSyncV2Provider gtasksSyncV2Provider;
|
|
||||||
@Inject Broadcaster broadcaster;
|
|
||||||
|
|
||||||
public GtasksBackgroundService() {
|
|
||||||
super(GtasksBackgroundService.class.getSimpleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void run() {
|
|
||||||
if (gtasksPreferenceService.isOngoing()) {
|
|
||||||
Timber.d("aborting: sync ongoing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(gtasksSyncV2Provider.isActive()) {
|
|
||||||
gtasksSyncV2Provider.synchronizeActiveTasks(new RecordSyncStatusCallback(gtasksPreferenceService, broadcaster));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
long intervalMillis() {
|
|
||||||
try {
|
|
||||||
return SECONDS.toMillis(preferences.getIntegerFromString(R.string.gtasks_GPr_interval_key, 0));
|
|
||||||
} catch(Exception e) {
|
|
||||||
Timber.e(e, e.getMessage());
|
|
||||||
preferences.setString(R.string.gtasks_GPr_interval_key, "0");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String getLastRunPreference() {
|
|
||||||
return "gtasks_last_sync";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(IntentServiceComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<bool name="sync_supported">true</bool>
|
|
||||||
<bool name="location_enabled">true</bool>
|
<bool name="location_enabled">true</bool>
|
||||||
<bool name="google_play_store_available">true</bool>
|
<bool name="google_play_store_available">true</bool>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,77 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.helper;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.activity.TaskListFragment;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
|
|
||||||
import com.todoroo.astrid.service.SyncV2Service;
|
|
||||||
import com.todoroo.astrid.sync.SyncResultCallback;
|
|
||||||
|
|
||||||
import org.tasks.Broadcaster;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.sync.RecordSyncStatusCallback;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncActionHelper is a helper class for encapsulating UI actions
|
|
||||||
* responsible for performing sync and prompting user to sign up for a new
|
|
||||||
* sync service.
|
|
||||||
* <p/>
|
|
||||||
* In order to make this work you need to call register() and unregister() in
|
|
||||||
* onResume and onPause, respectively.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@astrid.com>
|
|
||||||
*/
|
|
||||||
public class SyncActionHelper {
|
|
||||||
|
|
||||||
public static final String PREF_LAST_AUTO_SYNC = "taskListLastAutoSync"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
public final SyncResultCallback syncResultCallback;
|
|
||||||
|
|
||||||
private final SyncV2Service syncService;
|
|
||||||
private final Preferences preferences;
|
|
||||||
|
|
||||||
// --- boilerplate
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SyncActionHelper(GtasksPreferenceService gtasksPreferenceService, SyncV2Service syncService,
|
|
||||||
Preferences preferences, Broadcaster broadcaster) {
|
|
||||||
this.syncService = syncService;
|
|
||||||
this.preferences = preferences;
|
|
||||||
syncResultCallback = new RecordSyncStatusCallback(gtasksPreferenceService, broadcaster);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- automatic sync logic
|
|
||||||
|
|
||||||
public void initiateAutomaticSync() {
|
|
||||||
long tasksPushedAt = preferences.getLong(PREF_LAST_AUTO_SYNC, 0);
|
|
||||||
if (DateUtilities.now() - tasksPushedAt > TaskListFragment.AUTOSYNC_INTERVAL) {
|
|
||||||
performSyncServiceV2Sync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- sync logic
|
|
||||||
|
|
||||||
protected void performSyncServiceV2Sync() {
|
|
||||||
boolean syncOccurred = syncService.synchronizeActiveTasks(syncResultCallback);
|
|
||||||
if (syncOccurred) {
|
|
||||||
preferences.setLong(PREF_LAST_AUTO_SYNC, DateUtilities.now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean performSyncAction() {
|
|
||||||
if (syncService.isActive()) {
|
|
||||||
syncService.synchronizeActiveTasks(syncResultCallback);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.service;
|
|
||||||
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksList;
|
|
||||||
import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider;
|
|
||||||
import com.todoroo.astrid.sync.SyncResultCallback;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncV2Service is a simplified synchronization interface for supporting
|
|
||||||
* next-generation sync interfaces such as Google Tasks and Astrid.com
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@astrid.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class SyncV2Service {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* At present, sync provider interactions are handled through code. If
|
|
||||||
* there is enough interest, the Astrid team could create an interface
|
|
||||||
* for responding to sync requests through this new API.
|
|
||||||
*/
|
|
||||||
private final GtasksSyncV2Provider provider;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SyncV2Service(GtasksSyncV2Provider gtasksSyncV2Provider) {
|
|
||||||
provider = gtasksSyncV2Provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActive() {
|
|
||||||
return provider.isActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiate synchronization of active tasks
|
|
||||||
*
|
|
||||||
* @param callback result callback
|
|
||||||
* @return true if any servide was logged in and initiated a sync
|
|
||||||
*/
|
|
||||||
public boolean synchronizeActiveTasks(SyncResultCallback callback) {
|
|
||||||
if (provider.isActive()) {
|
|
||||||
provider.synchronizeActiveTasks(callback);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiate synchronization of task list
|
|
||||||
*
|
|
||||||
* @param list list object
|
|
||||||
* @param callback result callback
|
|
||||||
*/
|
|
||||||
public void synchronizeList(GtasksList list, SyncResultCallback callback) {
|
|
||||||
if(provider.isActive()) {
|
|
||||||
provider.synchronizeList(list, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearCompleted(GtasksList list, SyncResultCallback callback) {
|
|
||||||
if (provider.isActive()) {
|
|
||||||
provider.clearCompleted(list, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
package org.tasks.gtasks;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
|
||||||
|
|
||||||
|
import org.tasks.AccountManager;
|
||||||
|
import org.tasks.R;
|
||||||
|
import org.tasks.preferences.Preferences;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class SyncAdapterHelper {
|
||||||
|
|
||||||
|
private static final String AUTHORITY = "org.tasks";
|
||||||
|
|
||||||
|
private final AccountManager accountManager;
|
||||||
|
private final Preferences preferences;
|
||||||
|
private final GtasksPreferenceService gtasksPreferenceService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SyncAdapterHelper(AccountManager accountManager, Preferences preferences,
|
||||||
|
GtasksPreferenceService gtasksPreferenceService) {
|
||||||
|
this.accountManager = accountManager;
|
||||||
|
this.preferences = preferences;
|
||||||
|
this.gtasksPreferenceService = gtasksPreferenceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to trigger an immediate sync ("refresh").
|
||||||
|
*
|
||||||
|
* <p>This should only be used when we need to preempt the normal sync schedule. Typically, this
|
||||||
|
* means the user has pressed the "refresh" button.
|
||||||
|
*
|
||||||
|
* Note that SYNC_EXTRAS_MANUAL will cause an immediate sync, without any optimization to
|
||||||
|
* preserve battery life. If you know new data is available (perhaps via a GCM notification),
|
||||||
|
* but the user is not actively waiting for that data, you should omit this flag; this will give
|
||||||
|
* the OS additional freedom in scheduling your sync request.
|
||||||
|
*/
|
||||||
|
public boolean initiateManualSync() {
|
||||||
|
Account account = getAccount();
|
||||||
|
if (account == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Bundle extras = new Bundle();
|
||||||
|
// Disable sync backoff and ignore sync preferences. In other words...perform sync NOW!
|
||||||
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||||
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
|
||||||
|
ContentResolver.requestSync(account, AUTHORITY, extras);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return preferences.getBoolean(R.string.sync_gtasks, false) && getAccount() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean masterSyncEnabled() {
|
||||||
|
return ContentResolver.getMasterSyncAutomatically();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableSynchronization(boolean enabled) {
|
||||||
|
Account account = getAccount();
|
||||||
|
if (account != null) {
|
||||||
|
Timber.d("enableSynchronization=%s", enabled);
|
||||||
|
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
|
||||||
|
if (enabled) {
|
||||||
|
setSynchronizationInterval(preferences.getIntegerFromString(R.string.gtasks_GPr_interval_key, 0));
|
||||||
|
} else {
|
||||||
|
setSynchronizationInterval(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSynchronizationInterval(int seconds) {
|
||||||
|
Account account = getAccount();
|
||||||
|
if (account != null) {
|
||||||
|
boolean syncAutomatically = seconds > 0;
|
||||||
|
ContentResolver.setSyncAutomatically(account, AUTHORITY, syncAutomatically);
|
||||||
|
Timber.d("syncAutomatically=%s, syncInterval=%s", syncAutomatically, seconds);
|
||||||
|
if (syncAutomatically) {
|
||||||
|
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle.EMPTY, seconds);
|
||||||
|
} else {
|
||||||
|
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle.EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Account getAccount() {
|
||||||
|
return accountManager.getAccount(gtasksPreferenceService.getUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldShowBackgroundSyncWarning() {
|
||||||
|
return isEnabled() && !masterSyncEnabled() && !ContentResolver.getPeriodicSyncs(getAccount(), AUTHORITY).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,32 +0,0 @@
|
|||||||
package org.tasks.sync;
|
|
||||||
|
|
||||||
import com.todoroo.astrid.activity.TaskListFragment;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
|
|
||||||
|
|
||||||
import org.tasks.Broadcaster;
|
|
||||||
|
|
||||||
public class IndeterminateProgressBarSyncResultCallback extends RecordSyncStatusCallback {
|
|
||||||
|
|
||||||
private final TaskListFragment taskListFragment;
|
|
||||||
private final GtasksPreferenceService gtasksPreferenceService;
|
|
||||||
|
|
||||||
public IndeterminateProgressBarSyncResultCallback(TaskListFragment taskListFragment, GtasksPreferenceService gtasksPreferenceService, Broadcaster broadcaster) {
|
|
||||||
super(gtasksPreferenceService, broadcaster);
|
|
||||||
this.taskListFragment = taskListFragment;
|
|
||||||
this.gtasksPreferenceService = gtasksPreferenceService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void started() {
|
|
||||||
super.started();
|
|
||||||
|
|
||||||
taskListFragment.setSyncOngoing(gtasksPreferenceService.isOngoing());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finished() {
|
|
||||||
super.finished();
|
|
||||||
|
|
||||||
taskListFragment.setSyncOngoing(gtasksPreferenceService.isOngoing());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
package org.tasks.sync;
|
|
||||||
|
|
||||||
import org.tasks.time.DateTime;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class SyncThrottle {
|
|
||||||
|
|
||||||
private final Map<Long, DateTime> lastSync = new HashMap<>();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SyncThrottle() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean canSync(long listId) {
|
|
||||||
DateTime now = new DateTime();
|
|
||||||
DateTime last = lastSync.get(listId);
|
|
||||||
lastSync.put(listId, now);
|
|
||||||
return last == null || last.isBefore(now.minusMinutes(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<bool name="two_pane_layout">false</bool>
|
<bool name="two_pane_layout">false</bool>
|
||||||
<bool name="sync_supported">false</bool>
|
|
||||||
<bool name="location_enabled">false</bool>
|
<bool name="location_enabled">false</bool>
|
||||||
<bool name="google_play_store_available">false</bool>
|
<bool name="google_play_store_available">false</bool>
|
||||||
</resources>
|
</resources>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:accountType="com.google"
|
||||||
|
android:contentAuthority="org.tasks"
|
||||||
|
android:supportsUploading="true"
|
||||||
|
android:userVisible="false" />
|
||||||
Loading…
Reference in New Issue