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"?>
|
||||
<resources>
|
||||
<bool name="sync_supported">true</bool>
|
||||
<bool name="location_enabled">true</bool>
|
||||
<bool name="google_play_store_available">true</bool>
|
||||
</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"?>
|
||||
<resources>
|
||||
<bool name="two_pane_layout">false</bool>
|
||||
<bool name="sync_supported">false</bool>
|
||||
<bool name="location_enabled">false</bool>
|
||||
<bool name="google_play_store_available">false</bool>
|
||||
</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