Add GoogleTaskSyncAdapter

pull/437/head
Alex Baker 10 years ago
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) {
}
}

@ -1,5 +1,7 @@
package org.tasks; package org.tasks;
import android.accounts.Account;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -15,7 +17,7 @@ public class AccountManager {
return Collections.emptyList(); return Collections.emptyList();
} }
public boolean hasAccount(String account) { public Account getAccount(String userName) {
return false; return null;
} }
} }

@ -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() {
}
}

@ -30,7 +30,7 @@ public class GtasksMetadataServiceTest extends DatabaseTestCase {
private final GtasksTestPreferenceService service; private final GtasksTestPreferenceService service;
public GtasksMetadataServiceTestModule(Context context) { public GtasksMetadataServiceTestModule(Context context) {
service = new GtasksTestPreferenceService(context, new Preferences(context, null), null); service = new GtasksTestPreferenceService(new Preferences(context, null));
} }
@Provides @Provides

@ -5,21 +5,12 @@
*/ */
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import android.content.Context;
import org.tasks.AccountManager;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
public class GtasksTestPreferenceService extends GtasksPreferenceService { public class GtasksTestPreferenceService extends GtasksPreferenceService {
public GtasksTestPreferenceService(Context context, Preferences preferences, AccountManager accountManager) { public GtasksTestPreferenceService(Preferences preferences) {
super(context, preferences, accountManager); super(preferences);
}
@Override
public boolean isLoggedIn() {
return false;
} }
@Override @Override

@ -1,4 +1,4 @@
package com.todoroo.astrid.gtasks.sync; package org.tasks.gtasks;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
@ -9,13 +9,13 @@ import org.tasks.time.DateTime;
import static com.natpryce.makeiteasy.MakeItEasy.with; import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE; import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE;
import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE_TIME; import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE_TIME;
import static com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider.mergeDates; import static org.tasks.gtasks.GoogleTaskSyncAdapter.mergeDates;
import static org.tasks.makers.TaskMaker.DUE_DATE; import static org.tasks.makers.TaskMaker.DUE_DATE;
import static org.tasks.makers.TaskMaker.DUE_TIME; import static org.tasks.makers.TaskMaker.DUE_TIME;
import static org.tasks.makers.TaskMaker.HIDE_TYPE; import static org.tasks.makers.TaskMaker.HIDE_TYPE;
import static org.tasks.makers.TaskMaker.newTask; import static org.tasks.makers.TaskMaker.newTask;
public class GtasksSyncV2ProviderTest extends AndroidTestCase { public class GoogleTaskSyncAdapterTest extends AndroidTestCase {
public void testMergeDate() { public void testMergeDate() {
Task remote = newTask(with(DUE_DATE, new DateTime(2016, 3, 11))); Task remote = newTask(with(DUE_DATE, new DateTime(2016, 3, 11)));

@ -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) {
}
}

@ -1,5 +1,7 @@
package org.tasks; package org.tasks;
import android.accounts.Account;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -15,7 +17,7 @@ public class AccountManager {
return Collections.emptyList(); return Collections.emptyList();
} }
public boolean hasAccount(String account) { public Account getAccount(String userName) {
return false; return null;
} }
} }

@ -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() {
}
}

@ -26,6 +26,9 @@
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" /> <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_SYNC_STATS"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
<application> <application>
@ -147,6 +150,19 @@
android:label="@string/app_name" android:label="@string/app_name"
android:exported="true" /> android:exported="true" />
<!-- SyncService for Google Tasks -->
<service
android:name=".gtasks.GoogleTaskSyncService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter" />
</service>
</application> </application>
</manifest> </manifest>

@ -8,6 +8,8 @@ package com.todoroo.astrid.gtasks;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter; import com.todoroo.astrid.api.GtasksFilter;
import org.tasks.gtasks.SyncAdapterHelper;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@ -24,17 +26,17 @@ import static java.util.Collections.emptyList;
public class GtasksFilterExposer { public class GtasksFilterExposer {
private final GtasksListService gtasksListService; private final GtasksListService gtasksListService;
private final GtasksPreferenceService gtasksPreferenceService; private final SyncAdapterHelper syncAdapterHelper;
@Inject @Inject
public GtasksFilterExposer(GtasksListService gtasksListService, GtasksPreferenceService gtasksPreferenceService) { public GtasksFilterExposer(GtasksListService gtasksListService, SyncAdapterHelper syncAdapterHelper) {
this.gtasksListService = gtasksListService; this.gtasksListService = gtasksListService;
this.gtasksPreferenceService = gtasksPreferenceService; this.syncAdapterHelper = syncAdapterHelper;
} }
public List<Filter> getFilters() { public List<Filter> getFilters() {
// if we aren't logged in (or we are logged in to astrid.com), don't expose features // if we aren't logged in (or we are logged in to astrid.com), don't expose features
if(!gtasksPreferenceService.isLoggedIn()) { if(!syncAdapterHelper.isEnabled()) {
return emptyList(); return emptyList();
} }
@ -46,7 +48,7 @@ public class GtasksFilterExposer {
} }
public Filter getFilter(long id) { public Filter getFilter(long id) {
if (gtasksPreferenceService.isLoggedIn()) { if (syncAdapterHelper.isEnabled()) {
GtasksList list = gtasksListService.getList(id); GtasksList list = gtasksListService.getList(id);
if (list != null) { if (list != null) {
return filterFromList(list); return filterFromList(list);

@ -27,8 +27,6 @@ import org.tasks.Broadcaster;
import org.tasks.R; import org.tasks.R;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.sync.IndeterminateProgressBarSyncResultCallback;
import org.tasks.sync.SyncThrottle;
import org.tasks.themes.ThemeCache; import org.tasks.themes.ThemeCache;
import org.tasks.ui.CheckBoxes; import org.tasks.ui.CheckBoxes;
@ -52,7 +50,6 @@ public class GtasksListFragment extends SubtasksListFragment {
@Inject SyncV2Service syncService; @Inject SyncV2Service syncService;
@Inject TaskAttachmentDao taskAttachmentDao; @Inject TaskAttachmentDao taskAttachmentDao;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject SyncThrottle syncThrottle;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject Broadcaster broadcaster; @Inject Broadcaster broadcaster;
@Inject CheckBoxes checkBoxes; @Inject CheckBoxes checkBoxes;
@ -91,17 +88,6 @@ public class GtasksListFragment extends SubtasksListFragment {
outState.putParcelable(EXTRA_STORE_OBJECT, list.getStoreObject()); outState.putParcelable(EXTRA_STORE_OBJECT, list.getStoreObject());
} }
@Override
protected void initiateAutomaticSyncImpl() {
if (list != null && syncThrottle.canSync(list.getId())) {
syncData();
}
}
private void syncData() {
syncService.synchronizeList(list, new IndeterminateProgressBarSyncResultCallback(this, gtasksPreferenceService, broadcaster));
}
@Override @Override
protected void onTaskDelete(Task task) { protected void onTaskDelete(Task task) {
helper.onDeleteTask(task); helper.onDeleteTask(task);
@ -129,7 +115,7 @@ public class GtasksListFragment extends SubtasksListFragment {
public void finished() { public void finished() {
setSyncOngoing(false); setSyncOngoing(false);
syncData(); onRefresh();
} }
}); });
} }

@ -21,11 +21,11 @@ import org.tasks.activities.GoogleTaskListSelectionDialog;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking; import org.tasks.analytics.Tracking;
import org.tasks.gtasks.GoogleTaskListSelectionHandler; import org.tasks.gtasks.GoogleTaskListSelectionHandler;
import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.injection.ActivityComponent; import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity; import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.preferences.ActivityPermissionRequestor; import org.tasks.preferences.ActivityPermissionRequestor;
import org.tasks.preferences.PermissionRequestor; import org.tasks.preferences.PermissionRequestor;
import org.tasks.scheduling.BackgroundScheduler;
import javax.inject.Inject; import javax.inject.Inject;
@ -37,10 +37,10 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
private static final int REQUEST_LOGOUT = 1; private static final int REQUEST_LOGOUT = 1;
@Inject GtasksPreferenceService gtasksPreferenceService; @Inject GtasksPreferenceService gtasksPreferenceService;
@Inject BackgroundScheduler backgroundScheduler;
@Inject ActivityPermissionRequestor permissionRequestor; @Inject ActivityPermissionRequestor permissionRequestor;
@Inject GtasksListService gtasksListService; @Inject GtasksListService gtasksListService;
@Inject Tracker tracker; @Inject Tracker tracker;
@Inject SyncAdapterHelper syncAdapterHelper;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -49,7 +49,7 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
addPreferencesFromResource(R.xml.preferences_gtasks); addPreferencesFromResource(R.xml.preferences_gtasks);
Preference gtaskPreference = findPreference(getString(R.string.sync_gtasks)); Preference gtaskPreference = findPreference(getString(R.string.sync_gtasks));
((CheckBoxPreference) gtaskPreference).setChecked(gtasksPreferenceService.isLoggedIn()); ((CheckBoxPreference) gtaskPreference).setChecked(syncAdapterHelper.isEnabled());
gtaskPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { gtaskPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
@ -59,6 +59,7 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
} }
return false; return false;
} else { } else {
syncAdapterHelper.enableSynchronization(false);
tracker.reportEvent(Tracking.Events.GTASK_DISABLED); tracker.reportEvent(Tracking.Events.GTASK_DISABLED);
gtasksPreferenceService.stopOngoing(); gtasksPreferenceService.stopOngoing();
return true; return true;
@ -70,6 +71,13 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
DateUtilities.getDateStringWithTime(GtasksPreferences.this, DateUtilities.getDateStringWithTime(GtasksPreferences.this,
gtasksPreferenceService.getLastSyncDate()))); gtasksPreferenceService.getLastSyncDate())));
} }
findPreference(getString(R.string.gtasks_GPr_interval_key)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
syncAdapterHelper.setSynchronizationInterval(Integer.parseInt((String) o));
return true;
}
});
findPreference(getString(R.string.sync_SPr_forget_key)).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { findPreference(getString(R.string.sync_SPr_forget_key)).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
@ -101,11 +109,13 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
if (requestCode == REQUEST_LOGIN) { if (requestCode == REQUEST_LOGIN) {
boolean enabled = resultCode == RESULT_OK; boolean enabled = resultCode == RESULT_OK;
if (enabled) { if (enabled) {
syncAdapterHelper.enableSynchronization(true);
tracker.reportEvent(Tracking.Events.GTASK_ENABLED); tracker.reportEvent(Tracking.Events.GTASK_ENABLED);
} }
((CheckBoxPreference) findPreference(getString(R.string.sync_gtasks))).setChecked(enabled); ((CheckBoxPreference) findPreference(getString(R.string.sync_gtasks))).setChecked(enabled);
} else if(requestCode == REQUEST_LOGOUT) { } else if(requestCode == REQUEST_LOGOUT) {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
syncAdapterHelper.enableSynchronization(false);
tracker.reportEvent(Tracking.Events.GTASK_LOGOUT); tracker.reportEvent(Tracking.Events.GTASK_LOGOUT);
finish(); finish();
} }
@ -125,12 +135,6 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
} }
} }
@Override
protected void onPause() {
super.onPause();
backgroundScheduler.scheduleGtaskSync();
}
@Override @Override
public void inject(ActivityComponent component) { public void inject(ActivityComponent component) {
component.inject(this); component.inject(this);

@ -23,9 +23,10 @@ import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.gtasks.api.HttpNotFoundException; import com.todoroo.astrid.gtasks.api.HttpNotFoundException;
import com.todoroo.astrid.gtasks.api.MoveRequest; import com.todoroo.astrid.gtasks.api.MoveRequest;
import org.tasks.gtasks.SyncAdapterHelper;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -44,17 +45,20 @@ public class GtasksSyncService {
private final GtasksMetadata gtasksMetadataFactory; private final GtasksMetadata gtasksMetadataFactory;
private final GtasksInvoker gtasksInvoker; private final GtasksInvoker gtasksInvoker;
private final LinkedBlockingQueue<SyncOnSaveOperation> operationQueue = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<SyncOnSaveOperation> operationQueue = new LinkedBlockingQueue<>();
private final SyncAdapterHelper syncAdapterHelper;
@Inject @Inject
public GtasksSyncService(MetadataDao metadataDao, GtasksMetadataService gtasksMetadataService, public GtasksSyncService(MetadataDao metadataDao, GtasksMetadataService gtasksMetadataService,
TaskDao taskDao, GtasksPreferenceService gtasksPreferenceService, TaskDao taskDao, GtasksPreferenceService gtasksPreferenceService,
GtasksMetadata gtasksMetadataFactory, GtasksInvoker gtasksInvoker) { GtasksMetadata gtasksMetadataFactory, GtasksInvoker gtasksInvoker,
SyncAdapterHelper syncAdapterHelper) {
this.metadataDao = metadataDao; this.metadataDao = metadataDao;
this.gtasksMetadataService = gtasksMetadataService; this.gtasksMetadataService = gtasksMetadataService;
this.taskDao = taskDao; this.taskDao = taskDao;
this.gtasksPreferenceService = gtasksPreferenceService; this.gtasksPreferenceService = gtasksPreferenceService;
this.gtasksMetadataFactory = gtasksMetadataFactory; this.gtasksMetadataFactory = gtasksMetadataFactory;
this.gtasksInvoker = gtasksInvoker; this.gtasksInvoker = gtasksInvoker;
this.syncAdapterHelper = syncAdapterHelper;
new OperationPushThread(operationQueue).start(); new OperationPushThread(operationQueue).start();
} }
@ -88,19 +92,6 @@ public class GtasksSyncService {
} }
} }
private class NotifyOp implements SyncOnSaveOperation {
private final Semaphore sema;
public NotifyOp(Semaphore sema) {
this.sema = sema;
}
@Override
public void op(GtasksInvoker invoker) throws IOException {
sema.release();
}
}
public void enqueue(SyncOnSaveOperation operation) { public void enqueue(SyncOnSaveOperation operation) {
operationQueue.offer(operation); operationQueue.offer(operation);
} }
@ -131,17 +122,6 @@ public class GtasksSyncService {
} }
} }
public void waitUntilEmpty() {
Semaphore sema = new Semaphore(0);
operationQueue.offer(new NotifyOp(sema));
try {
sema.acquire();
} catch (InterruptedException e) {
// Ignored
Timber.e(e, e.getMessage());
}
}
public void clearCompleted(String listId) { public void clearCompleted(String listId) {
operationQueue.offer(new ClearOp(listId)); operationQueue.offer(new ClearOp(listId));
} }
@ -161,7 +141,7 @@ public class GtasksSyncService {
{ {
return; return;
} }
if (!checkForToken()) { if (!syncAdapterHelper.isEnabled()) {
return; return;
} }
@ -290,8 +270,4 @@ public class GtasksSyncService {
metadataDao.saveExisting(model); metadataDao.saveExisting(model);
} }
} }
private boolean checkForToken() {
return gtasksPreferenceService.isLoggedIn();
}
} }

@ -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();
}
}
});
}
}
}

@ -2,11 +2,14 @@ package org.tasks;
import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.gtasks.GtasksTaskListUpdater; import com.todoroo.astrid.gtasks.GtasksTaskListUpdater;
import com.todoroo.astrid.gtasks.sync.GtasksSyncService;
import org.tasks.billing.InventoryHelper; import org.tasks.billing.InventoryHelper;
import org.tasks.billing.PurchaseHelper; import org.tasks.billing.PurchaseHelper;
import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.receivers.TeslaUnreadReceiver; import org.tasks.receivers.TeslaUnreadReceiver;
import org.tasks.sync.SyncExecutor;
import javax.inject.Inject; import javax.inject.Inject;
@ -16,21 +19,26 @@ public class FlavorSetup {
private final GtasksPreferenceService gtasksPreferenceService; private final GtasksPreferenceService gtasksPreferenceService;
private final TeslaUnreadReceiver teslaUnreadReceiver; private final TeslaUnreadReceiver teslaUnreadReceiver;
private final InventoryHelper inventoryHelper; private final InventoryHelper inventoryHelper;
private final SyncAdapterHelper syncAdapterHelper;
@Inject @Inject
public FlavorSetup(Preferences preferences, GtasksPreferenceService gtasksPreferenceService, public FlavorSetup(Preferences preferences, GtasksPreferenceService gtasksPreferenceService,
@SuppressWarnings("UnusedParameters") GtasksTaskListUpdater gtasksTaskListUpdater, @SuppressWarnings("UnusedParameters") GtasksTaskListUpdater gtasksTaskListUpdater,
@SuppressWarnings("UnusedParameters") PurchaseHelper purchaseHelper, @SuppressWarnings("UnusedParameters") PurchaseHelper purchaseHelper,
TeslaUnreadReceiver teslaUnreadReceiver, InventoryHelper inventoryHelper) { @SuppressWarnings("UnusedParameters") SyncExecutor syncExecutor,
TeslaUnreadReceiver teslaUnreadReceiver, InventoryHelper inventoryHelper,
SyncAdapterHelper syncAdapterHelper) {
this.preferences = preferences; this.preferences = preferences;
this.gtasksPreferenceService = gtasksPreferenceService; this.gtasksPreferenceService = gtasksPreferenceService;
this.teslaUnreadReceiver = teslaUnreadReceiver; this.teslaUnreadReceiver = teslaUnreadReceiver;
this.inventoryHelper = inventoryHelper; this.inventoryHelper = inventoryHelper;
this.syncAdapterHelper = syncAdapterHelper;
} }
public void setup() { public void setup() {
inventoryHelper.initialize(); inventoryHelper.initialize();
teslaUnreadReceiver.setEnabled(preferences.getBoolean(R.string.p_tesla_unread_enabled, false)); teslaUnreadReceiver.setEnabled(preferences.getBoolean(R.string.p_tesla_unread_enabled, false));
gtasksPreferenceService.stopOngoing(); // if sync ongoing flag was set, clear it gtasksPreferenceService.stopOngoing(); // if sync ongoing flag was set, clear it
syncAdapterHelper.enableSynchronization(syncAdapterHelper.isEnabled());
} }
} }

@ -3,7 +3,8 @@ package org.tasks.activities;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider; import com.todoroo.astrid.gtasks.GtasksMetadataService;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import org.tasks.R; import org.tasks.R;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
@ -14,8 +15,9 @@ import javax.inject.Inject;
public class ClearGtaskDataActivity extends InjectingAppCompatActivity { public class ClearGtaskDataActivity extends InjectingAppCompatActivity {
@Inject GtasksSyncV2Provider gtasksSyncV2Provider;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject GtasksPreferenceService gtasksPreferenceService;
@Inject GtasksMetadataService gtasksMetadataService;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -25,7 +27,9 @@ public class ClearGtaskDataActivity extends InjectingAppCompatActivity {
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
gtasksSyncV2Provider.signOut(); gtasksPreferenceService.clearLastSyncDate();
gtasksPreferenceService.setUserName(null);
gtasksMetadataService.clearMetadata();
setResult(RESULT_OK); setResult(RESULT_OK);
} }
}) })

@ -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 {
}

@ -16,6 +16,7 @@ import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.gtasks.sync.GtasksSyncService; import com.todoroo.astrid.gtasks.sync.GtasksSyncService;
import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.TaskService;
import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.injection.BroadcastComponent; import org.tasks.injection.BroadcastComponent;
import org.tasks.injection.InjectingBroadcastReceiver; import org.tasks.injection.InjectingBroadcastReceiver;
@ -31,12 +32,13 @@ public class GoogleTaskPushReceiver extends InjectingBroadcastReceiver {
@Inject GtasksPreferenceService gtasksPreferenceService; @Inject GtasksPreferenceService gtasksPreferenceService;
@Inject GtasksSyncService gtasksSyncService; @Inject GtasksSyncService gtasksSyncService;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Inject SyncAdapterHelper syncAdapterHelper;
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent); super.onReceive(context, intent);
if(!gtasksPreferenceService.isLoggedIn()) { if(!syncAdapterHelper.isEnabled()) {
return; return;
} }

@ -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>

@ -11,7 +11,6 @@ import android.os.Bundle;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.activity.TaskListActivity; import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
@ -117,17 +116,6 @@ public class TagViewFragment extends TaskListFragment {
outState.putParcelable(EXTRA_TAG_DATA, tagData); outState.putParcelable(EXTRA_TAG_DATA, tagData);
} }
@Override
protected void initiateAutomaticSyncImpl() {
if (tagData != null) {
long lastAutosync = tagData.getLastAutosync();
if(DateUtilities.now() - lastAutosync > AUTOSYNC_INTERVAL) {
tagData.setLastAutosync(DateUtilities.now());
tagDataDao.saveExisting(tagData);
}
}
}
@Override @Override
protected boolean hasDraggableOption() { protected boolean hasDraggableOption() {
return tagData != null; return tagData != null;

@ -12,6 +12,8 @@ import android.content.ContentValues;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
@ -46,6 +48,7 @@ import org.tasks.dialogs.SortDialog;
import org.tasks.fragments.CommentBarFragment; import org.tasks.fragments.CommentBarFragment;
import org.tasks.fragments.TaskEditControlSetFragmentManager; import org.tasks.fragments.TaskEditControlSetFragmentManager;
import org.tasks.gtasks.GoogleTaskListSelectionHandler; import org.tasks.gtasks.GoogleTaskListSelectionHandler;
import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.injection.ActivityComponent; import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingAppCompatActivity; import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.intents.TaskIntents; import org.tasks.intents.TaskIntents;
@ -94,6 +97,7 @@ public class TaskListActivity extends InjectingAppCompatActivity implements
@Inject Theme theme; @Inject Theme theme;
@Inject Broadcaster broadcaster; @Inject Broadcaster broadcaster;
@Inject ThemeCache themeCache; @Inject ThemeCache themeCache;
@Inject SyncAdapterHelper syncAdapterHelper;
@BindView(R.id.drawer_layout) DrawerLayout drawerLayout; @BindView(R.id.drawer_layout) DrawerLayout drawerLayout;
@ -233,6 +237,28 @@ public class TaskListActivity extends InjectingAppCompatActivity implements
registerReceiver( registerReceiver(
repeatConfirmationReceiver, repeatConfirmationReceiver,
new IntentFilter(AstridApiConstants.BROADCAST_EVENT_TASK_REPEATED)); new IntentFilter(AstridApiConstants.BROADCAST_EVENT_TASK_REPEATED));
if (syncAdapterHelper.shouldShowBackgroundSyncWarning() && !preferences.getBoolean(R.string.p_sync_warning_shown, false)) {
TaskListFragment taskListFragment = getTaskListFragment();
if (taskListFragment != null) {
taskListFragment.makeSnackbar(R.string.master_sync_warning)
.setAction(R.string.TLA_menu_settings, new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(Settings.ACTION_SYNC_SETTINGS) {{
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}});
}
})
.setCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar snackbar) {
preferences.setBoolean(R.string.p_sync_warning_shown, true);
}
})
.show();
}
}
} }
@Override @Override

@ -14,6 +14,8 @@ import android.content.IntentFilter;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
@ -57,7 +59,6 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment; import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.gtasks.GtasksListFragment; import com.todoroo.astrid.gtasks.GtasksListFragment;
import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.helper.SyncActionHelper;
import com.todoroo.astrid.service.TaskCreator; import com.todoroo.astrid.service.TaskCreator;
import com.todoroo.astrid.service.TaskDeleter; import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.service.TaskDuplicator; import com.todoroo.astrid.service.TaskDuplicator;
@ -73,6 +74,7 @@ import org.tasks.Broadcaster;
import org.tasks.R; import org.tasks.R;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.SortDialog; import org.tasks.dialogs.SortDialog;
import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.injection.ForActivity; import org.tasks.injection.ForActivity;
import org.tasks.injection.FragmentComponent; import org.tasks.injection.FragmentComponent;
import org.tasks.injection.InjectingListFragment; import org.tasks.injection.InjectingListFragment;
@ -120,7 +122,6 @@ public class TaskListFragment extends InjectingListFragment implements
// --- activities // --- activities
public static final long AUTOSYNC_INTERVAL = 90000L;
public static final int ACTIVITY_REQUEST_NEW_FILTER = 5; public static final int ACTIVITY_REQUEST_NEW_FILTER = 5;
// --- menu codes // --- menu codes
@ -132,6 +133,7 @@ public class TaskListFragment extends InjectingListFragment implements
// --- instance variables // --- instance variables
@Inject SyncAdapterHelper syncAdapterHelper;
@Inject TaskService taskService; @Inject TaskService taskService;
@Inject TaskDeleter taskDeleter; @Inject TaskDeleter taskDeleter;
@Inject TaskDuplicator taskDuplicator; @Inject TaskDuplicator taskDuplicator;
@ -141,7 +143,6 @@ public class TaskListFragment extends InjectingListFragment implements
@Inject TaskAttachmentDao taskAttachmentDao; @Inject TaskAttachmentDao taskAttachmentDao;
@Inject GtasksPreferenceService gtasksPreferenceService; @Inject GtasksPreferenceService gtasksPreferenceService;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject SyncActionHelper syncActionHelper;
@Inject CheckBoxes checkBoxes; @Inject CheckBoxes checkBoxes;
@Inject VoiceInputAssistant voiceInputAssistant; @Inject VoiceInputAssistant voiceInputAssistant;
@Inject TaskCreator taskCreator; @Inject TaskCreator taskCreator;
@ -152,6 +153,7 @@ public class TaskListFragment extends InjectingListFragment implements
@BindView(R.id.swipe_layout) SwipeRefreshLayout swipeRefreshLayout; @BindView(R.id.swipe_layout) SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.swipe_layout_empty) SwipeRefreshLayout emptyView; @BindView(R.id.swipe_layout_empty) SwipeRefreshLayout emptyView;
@BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.task_list_coordinator) CoordinatorLayout coordinatorLayout;
private TaskAdapter taskAdapter = null; private TaskAdapter taskAdapter = null;
private RefreshReceiver refreshReceiver = new RefreshReceiver(); private RefreshReceiver refreshReceiver = new RefreshReceiver();
@ -168,7 +170,7 @@ public class TaskListFragment extends InjectingListFragment implements
@Override @Override
public void onRefresh() { public void onRefresh() {
if (!syncActionHelper.performSyncAction()) { if (!syncAdapterHelper.initiateManualSync()) {
refresh(); refresh();
} }
} }
@ -470,19 +472,18 @@ public class TaskListFragment extends InjectingListFragment implements
getActivity().registerReceiver(refreshReceiver, new IntentFilter(AstridApiConstants.BROADCAST_EVENT_REFRESH)); getActivity().registerReceiver(refreshReceiver, new IntentFilter(AstridApiConstants.BROADCAST_EVENT_REFRESH));
initiateAutomaticSyncImpl();
refresh(); refresh();
} }
/** public Snackbar makeSnackbar(int resId) {
* Implementation of initiation automatic sync. Subclasses should override this method; return makeSnackbar(getString(resId));
* the above method takes care of calling it in the correct way }
*/
protected void initiateAutomaticSyncImpl() { public Snackbar makeSnackbar(String text) {
if (BuiltInFilterExposer.isInbox(context, filter)) { Snackbar snackbar = Snackbar.make(coordinatorLayout, text, 8000)
syncActionHelper.initiateAutomaticSync(); .setActionTextColor(getResources().getColor(R.color.snackbar_text_color));
} snackbar.getView().setBackgroundColor(getResources().getColor(R.color.snackbar_background));
return snackbar;
} }
@Override @Override

@ -5,17 +5,12 @@
*/ */
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import android.content.Context;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import org.tasks.AccountManager;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
/** /**
* Methods for working with GTasks preferences * Methods for working with GTasks preferences
@ -23,12 +18,9 @@ import javax.inject.Singleton;
* @author timsu * @author timsu
* *
*/ */
@Singleton
public class GtasksPreferenceService { public class GtasksPreferenceService {
private final Context context;
private final Preferences preferences; private final Preferences preferences;
private final AccountManager accountManager;
public static final String IDENTIFIER = "gtasks"; //$NON-NLS-1$ public static final String IDENTIFIER = "gtasks"; //$NON-NLS-1$
@ -36,11 +28,8 @@ public class GtasksPreferenceService {
private static final String PREF_USER_NAME = IDENTIFIER + "_user"; //$NON-NLS-1$ private static final String PREF_USER_NAME = IDENTIFIER + "_user"; //$NON-NLS-1$
@Inject @Inject
public GtasksPreferenceService(@ForApplication Context context, Preferences preferences, public GtasksPreferenceService(Preferences preferences) {
AccountManager accountManager) {
this.context = context;
this.preferences = preferences; this.preferences = preferences;
this.accountManager = accountManager;
} }
public String getDefaultList() { public String getDefaultList() {
@ -63,15 +52,6 @@ public class GtasksPreferenceService {
protected static final String PREF_ONGOING = "_ongoing"; //$NON-NLS-1$ protected static final String PREF_ONGOING = "_ongoing"; //$NON-NLS-1$
/**
* @return true if we have a token for this user, false otherwise
*/
public boolean isLoggedIn() {
return context.getResources().getBoolean(R.bool.sync_supported) &&
preferences.getBoolean(R.string.sync_gtasks, false) &&
accountManager.hasAccount(preferences.getStringValue(PREF_USER_NAME));
}
/** @return Last Successful Sync Date, or 0 */ /** @return Last Successful Sync Date, or 0 */
public long getLastSyncDate() { public long getLastSyncDate() {
return preferences.getLong(IDENTIFIER + PREF_LAST_SYNC, 0); return preferences.getLong(IDENTIFIER + PREF_LAST_SYNC, 0);
@ -101,4 +81,8 @@ public class GtasksPreferenceService {
public void recordSyncStart() { public void recordSyncStart() {
preferences.setBoolean(IDENTIFIER + PREF_ONGOING, true); preferences.setBoolean(IDENTIFIER + PREF_ONGOING, true);
} }
public int getSyncInterval() {
return preferences.getIntegerFromString(R.string.gtasks_GPr_interval_key, 0);
}
} }

@ -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);
}
}
}

@ -8,16 +8,13 @@ import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskListMetadataDao; import com.todoroo.astrid.dao.TaskListMetadataDao;
import com.todoroo.astrid.dao.UserActivityDao; import com.todoroo.astrid.dao.UserActivityDao;
import com.todoroo.astrid.service.SyncV2Service;
import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagService; import com.todoroo.astrid.tags.TagService;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.filters.FilterCounter;
import org.tasks.injection.ApplicationComponent; import org.tasks.injection.ApplicationComponent;
import org.tasks.injection.InjectingApplication; import org.tasks.injection.InjectingApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncThrottle;
import javax.inject.Inject; import javax.inject.Inject;
@ -33,10 +30,8 @@ public class Tasks extends InjectingApplication {
@Inject TaskAttachmentDao taskAttachmentDao; @Inject TaskAttachmentDao taskAttachmentDao;
@Inject TaskListMetadataDao taskListMetadataDao; @Inject TaskListMetadataDao taskListMetadataDao;
@Inject TaskService taskService; @Inject TaskService taskService;
@Inject SyncV2Service syncV2Service;
@Inject TagService tagService; @Inject TagService tagService;
@Inject Broadcaster broadcaster; @Inject Broadcaster broadcaster;
@Inject SyncThrottle syncThrottle;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject Tracker tracker; @Inject Tracker tracker;
@Inject FlavorSetup flavorSetup; @Inject FlavorSetup flavorSetup;

@ -22,11 +22,11 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.GtasksList; import com.todoroo.astrid.gtasks.GtasksList;
import com.todoroo.astrid.gtasks.GtasksListService; import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.gtasks.GtasksMetadata; import com.todoroo.astrid.gtasks.GtasksMetadata;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.tags.TagService; import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.tags.TaskToTagMetadata; import com.todoroo.astrid.tags.TaskToTagMetadata;
import org.tasks.R; import org.tasks.R;
import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import java.util.List; import java.util.List;
@ -48,18 +48,20 @@ public class FilterCriteriaProvider {
private Context context; private Context context;
private TagService tagService; private TagService tagService;
private GtasksPreferenceService gtasksPreferenceService;
private GtasksListService gtasksListService; private GtasksListService gtasksListService;
private GtasksMetadata gtasksMetadata; private GtasksMetadata gtasksMetadata;
private Resources r; private Resources r;
private final SyncAdapterHelper syncAdapterHelper;
@Inject @Inject
public FilterCriteriaProvider(@ForApplication Context context, TagService tagService, GtasksPreferenceService gtasksPreferenceService, GtasksListService gtasksListService, GtasksMetadata gtasksMetadata) { public FilterCriteriaProvider(@ForApplication Context context, TagService tagService,
GtasksListService gtasksListService,
GtasksMetadata gtasksMetadata, SyncAdapterHelper syncAdapterHelper) {
this.context = context; this.context = context;
this.tagService = tagService; this.tagService = tagService;
this.gtasksPreferenceService = gtasksPreferenceService;
this.gtasksListService = gtasksListService; this.gtasksListService = gtasksListService;
this.gtasksMetadata = gtasksMetadata; this.gtasksMetadata = gtasksMetadata;
this.syncAdapterHelper = syncAdapterHelper;
r = context.getResources(); r = context.getResources();
} }
@ -72,7 +74,7 @@ public class FilterCriteriaProvider {
result.add(getDueDateFilter()); result.add(getDueDateFilter());
result.add(getImportanceFilter()); result.add(getImportanceFilter());
result.add(getTaskTitleContainsFilter()); result.add(getTaskTitleContainsFilter());
if (gtasksPreferenceService.isLoggedIn()) { if (syncAdapterHelper.isEnabled()) {
result.add(getGtasksFilterCriteria()); result.add(getGtasksFilterCriteria());
} }

@ -6,7 +6,6 @@ import android.app.FragmentManager;
import com.todoroo.astrid.activity.BeastModePreferences; import com.todoroo.astrid.activity.BeastModePreferences;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.files.FilesControlSet; import com.todoroo.astrid.files.FilesControlSet;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.repeats.RepeatControlSet; import com.todoroo.astrid.repeats.RepeatControlSet;
import com.todoroo.astrid.tags.TagsControlSet; import com.todoroo.astrid.tags.TagsControlSet;
import com.todoroo.astrid.timers.TimerControlSet; import com.todoroo.astrid.timers.TimerControlSet;
@ -16,6 +15,7 @@ import com.todoroo.astrid.ui.ReminderControlSet;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.R; import org.tasks.R;
import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.ui.CalendarControlSet; import org.tasks.ui.CalendarControlSet;
import org.tasks.ui.DeadlineControlSet; import org.tasks.ui.DeadlineControlSet;
@ -78,12 +78,12 @@ public class TaskEditControlSetFragmentManager {
private final Map<String, Integer> controlSetFragments = new LinkedHashMap<>(); private final Map<String, Integer> controlSetFragments = new LinkedHashMap<>();
private final List<String> displayOrder; private final List<String> displayOrder;
private final FragmentManager fragmentManager; private final FragmentManager fragmentManager;
private final SyncAdapterHelper syncAdapterHelper;
private int numRows; private int numRows;
private GtasksPreferenceService gtasksPreferenceService;
@Inject @Inject
public TaskEditControlSetFragmentManager(Activity activity, Preferences preferences, GtasksPreferenceService gtasksPreferenceService) { public TaskEditControlSetFragmentManager(Activity activity, Preferences preferences, SyncAdapterHelper syncAdapterHelper) {
this.gtasksPreferenceService = gtasksPreferenceService; this.syncAdapterHelper = syncAdapterHelper;
displayOrder = BeastModePreferences.constructOrderedControlList(preferences, activity); displayOrder = BeastModePreferences.constructOrderedControlList(preferences, activity);
displayOrder.add(0, activity.getString(EditTitleControlSet.TAG)); displayOrder.add(0, activity.getString(EditTitleControlSet.TAG));
displayOrder.add(1, activity.getString(CommentBarFragment.TAG)); displayOrder.add(1, activity.getString(CommentBarFragment.TAG));
@ -166,7 +166,7 @@ public class TaskEditControlSetFragmentManager {
case CommentBarFragment.TAG: case CommentBarFragment.TAG:
return new CommentBarFragment(); return new CommentBarFragment();
case GoogleTaskListFragment.TAG: case GoogleTaskListFragment.TAG:
return gtasksPreferenceService.isLoggedIn() return syncAdapterHelper.isEnabled()
? new GoogleTaskListFragment() ? new GoogleTaskListFragment()
: null; : null;
default: default:

@ -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();
}
}

@ -6,10 +6,7 @@ import javax.inject.Singleton;
import dagger.Component; import dagger.Component;
@Singleton public interface BaseApplicationComponent {
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
Tasks inject(Tasks tasks); Tasks inject(Tasks tasks);
ActivityComponent plus(ActivityModule module); ActivityComponent plus(ActivityModule module);

@ -4,7 +4,6 @@ import org.tasks.location.GeofenceTransitionsIntentService;
import org.tasks.scheduling.BackupIntentService; import org.tasks.scheduling.BackupIntentService;
import org.tasks.scheduling.CalendarNotificationIntentService; import org.tasks.scheduling.CalendarNotificationIntentService;
import org.tasks.scheduling.GeofenceSchedulingIntentService; import org.tasks.scheduling.GeofenceSchedulingIntentService;
import org.tasks.scheduling.GtasksBackgroundService;
import org.tasks.scheduling.RefreshSchedulerIntentService; import org.tasks.scheduling.RefreshSchedulerIntentService;
import org.tasks.scheduling.ReminderSchedulerIntentService; import org.tasks.scheduling.ReminderSchedulerIntentService;
@ -23,6 +22,4 @@ public interface IntentServiceComponent {
void inject(BackupIntentService backupIntentService); void inject(BackupIntentService backupIntentService);
void inject(GeofenceTransitionsIntentService geofenceTransitionsIntentService); void inject(GeofenceTransitionsIntentService geofenceTransitionsIntentService);
void inject(GtasksBackgroundService gtasksBackgroundService);
} }

@ -6,10 +6,11 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.view.View; import android.view.View;
import android.view.WindowManager;
import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.TaskService;
@ -45,6 +46,14 @@ public class RepeatConfirmationReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(final Context context, final Intent intent) { public void onReceive(final Context context, final Intent intent) {
TaskListFragment taskListFragment = null;
if (activity instanceof TaskListActivity) {
taskListFragment = ((TaskListActivity) activity).getTaskListFragment();
}
if (taskListFragment == null) {
Timber.d("No task list fragment");
return;
}
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, 0); long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, 0);
if (taskId > 0) { if (taskId > 0) {
@ -53,15 +62,7 @@ public class RepeatConfirmationReceiver extends BroadcastReceiver {
Task task = taskService.fetchById(taskId, REPEAT_RESCHEDULED_PROPERTIES); Task task = taskService.fetchById(taskId, REPEAT_RESCHEDULED_PROPERTIES);
try { try {
showSnackbar(activity.findViewById(R.id.task_list_coordinator), task, oldDueDate, newDueDate); showSnackbar(taskListFragment, task, oldDueDate, newDueDate);
} catch (WindowManager.BadTokenException e) { // Activity not running when tried to show dialog--rebroadcast
Timber.e(e, e.getMessage());
new Thread() {
@Override
public void run() {
context.sendBroadcast(intent);
}
}.start();
} catch (Exception e) { } catch (Exception e) {
Timber.e(e, e.getMessage()); Timber.e(e, e.getMessage());
tracker.reportException(e); tracker.reportException(e);
@ -69,12 +70,10 @@ public class RepeatConfirmationReceiver extends BroadcastReceiver {
} }
} }
private void showSnackbar(View view, final Task task, final long oldDueDate, final long newDueDate) { private void showSnackbar(TaskListFragment taskListFragment, final Task task, final long oldDueDate, final long newDueDate) {
String dueDateString = getRelativeDateAndTimeString(activity, newDueDate); String dueDateString = getRelativeDateAndTimeString(activity, newDueDate);
String snackbarText = activity.getString(R.string.repeat_snackbar, task.getTitle(), dueDateString); String snackbarText = activity.getString(R.string.repeat_snackbar, task.getTitle(), dueDateString);
taskListFragment.makeSnackbar(snackbarText)
Snackbar snackbar = Snackbar.make(view, snackbarText, Snackbar.LENGTH_LONG)
.setActionTextColor(activity.getResources().getColor(R.color.snackbar_text_color))
.setAction(R.string.DLG_undo, new View.OnClickListener() { .setAction(R.string.DLG_undo, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -82,9 +81,8 @@ public class RepeatConfirmationReceiver extends BroadcastReceiver {
task.setCompletionDate(0L); task.setCompletionDate(0L);
taskService.save(task); taskService.save(task);
} }
}); })
snackbar.getView().setBackgroundColor(activity.getResources().getColor(R.color.snackbar_background)); .show();
snackbar.show();
} }
private String getRelativeDateAndTimeString(Context context, long date) { private String getRelativeDateAndTimeString(Context context, long date) {

@ -3,7 +3,6 @@ package org.tasks.scheduling;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import org.tasks.R;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import javax.inject.Inject; import javax.inject.Inject;
@ -22,9 +21,6 @@ public class BackgroundScheduler {
scheduleBackupService(); scheduleBackupService();
scheduleMidnightRefresh(); scheduleMidnightRefresh();
scheduleCalendarNotifications(); scheduleCalendarNotifications();
if (context.getResources().getBoolean(R.bool.sync_supported)) {
scheduleGtaskSync();
}
} }
public void scheduleBackupService() { public void scheduleBackupService() {
@ -35,10 +31,6 @@ public class BackgroundScheduler {
context.startService(new Intent(context, RefreshSchedulerIntentService.class)); context.startService(new Intent(context, RefreshSchedulerIntentService.class));
} }
public void scheduleGtaskSync() {
context.startService(new Intent(context, GtasksBackgroundService.class));
}
public void scheduleCalendarNotifications() { public void scheduleCalendarNotifications() {
context.startService(new Intent(context, CalendarNotificationIntentService.class)); context.startService(new Intent(context, CalendarNotificationIntentService.class));
} }

@ -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());
}
}

@ -18,6 +18,7 @@ public class RecordSyncStatusCallback implements SyncResultCallback {
@Override @Override
public void started() { public void started() {
gtasksPreferenceService.recordSyncStart(); gtasksPreferenceService.recordSyncStart();
broadcaster.refresh();
} }
@Override @Override

@ -9,11 +9,13 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import timber.log.Timber; import timber.log.Timber;
import static java.util.concurrent.Executors.newSingleThreadExecutor; import static java.util.concurrent.Executors.newSingleThreadExecutor;
@Singleton
public class SyncExecutor { public class SyncExecutor {
private final ExecutorService executor = newSingleThreadExecutor( private final ExecutorService executor = newSingleThreadExecutor(

@ -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));
}
}

@ -185,16 +185,12 @@
<string name="randomly">عشوائياً</string> <string name="randomly">عشوائياً</string>
<string name="pick_a_date_and_time">اختر تاريخ و وقت</string> <string name="pick_a_date_and_time">اختر تاريخ و وقت</string>
<string name="pick_a_location">أختر الموقع</string> <string name="pick_a_location">أختر الموقع</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">تعطيل</string>
<item>تعطيل</item> <string name="sync_interval_one_hour">كل ساعة</string>
<item>كل 15 دقيقة</item> <string name="sync_interval_three_hours">كل ثلاث ساعات</string>
<item>كل ثلاثين دقيقة</item> <string name="sync_interval_six_hours">كل ست ساعات</string>
<item>كل ساعة</item> <string name="sync_interval_twelve_hours">كل 12 ساعة</string>
<item>كل ثلاث ساعات</item> <string name="sync_interval_one_day">كل يوم</string>
<item>كل ست ساعات</item> <string name="sync_interval_three_days">كل ثلاث أيام</string>
<item>كل 12 ساعة</item> <string name="sync_interval_one_week">كل أسبوع</string>
<item>كل يوم</item>
<item>كل ثلاث أيام</item>
<item>كل أسبوع</item>
</string-array>
</resources> </resources>

@ -414,16 +414,12 @@
<string name="theme_light">Светла</string> <string name="theme_light">Светла</string>
<string name="theme_dark">Тъмна</string> <string name="theme_dark">Тъмна</string>
<string name="default_value">По подразбиране</string> <string name="default_value">По подразбиране</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">изключено</string>
<item>изключено</item> <string name="sync_interval_one_hour">на всеки час</string>
<item>на всеки 15 минути</item> <string name="sync_interval_three_hours">на всеки три часа</string>
<item>на всеки 30 минути</item> <string name="sync_interval_six_hours">на всеки шест часа</string>
<item>на всеки час</item> <string name="sync_interval_twelve_hours">на всеки дванадесет часа</string>
<item>на всеки три часа</item> <string name="sync_interval_one_day">всеки ден</string>
<item>на всеки шест часа</item> <string name="sync_interval_three_days">на всеки три дена</string>
<item>на всеки дванадесет часа</item> <string name="sync_interval_one_week">всяка седмица</string>
<item>всеки ден</item>
<item>на всеки три дена</item>
<item>всяка седмица</item>
</string-array>
</resources> </resources>

@ -145,16 +145,12 @@
<string name="sync_SPr_forget">Surt</string> <string name="sync_SPr_forget">Surt</string>
<string name="sync_SPr_forget_description">Esborra tota la informació de sincronització</string> <string name="sync_SPr_forget_description">Esborra tota la informació de sincronització</string>
<string name="sync_forget_confirm">Tancar sessió / esborra la informació de sincronització?</string> <string name="sync_forget_confirm">Tancar sessió / esborra la informació de sincronització?</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">desactivat</string>
<item>desactivat</item> <string name="sync_interval_one_hour">cada hora</string>
<item>cada quince minuts</item> <string name="sync_interval_three_hours">cada tres hores</string>
<item>cada trenta minuts</item> <string name="sync_interval_six_hours">cada sis hores</string>
<item>cada hora</item> <string name="sync_interval_twelve_hours">cada dotze hores</string>
<item>cada tres hores</item> <string name="sync_interval_one_day">diàriament</string>
<item>cada sis hores</item> <string name="sync_interval_three_days">cada tres dies</string>
<item>cada dotze hores</item> <string name="sync_interval_one_week">setmanalment</string>
<item>diàriament</item>
<item>cada tres dies</item>
<item>setmanalment</item>
</string-array>
</resources> </resources>

@ -249,16 +249,12 @@
<string name="default_priority">Výchozí</string> <string name="default_priority">Výchozí</string>
<string name="low_priority">Nízká</string> <string name="low_priority">Nízká</string>
<string name="notification_priority">Priorita oznámení</string> <string name="notification_priority">Priorita oznámení</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">zakázat</string>
<item>zakázat</item> <string name="sync_interval_one_hour">každou hodinu</string>
<item>každých patnáct minut</item> <string name="sync_interval_three_hours">každé tři hodiny</string>
<item>každých třicet minut</item> <string name="sync_interval_six_hours">každých šest hodin</string>
<item>každou hodinu</item> <string name="sync_interval_twelve_hours">každých dvanáct hodin</string>
<item>každé tři hodiny</item> <string name="sync_interval_one_day">každý den</string>
<item>každých šest hodin</item> <string name="sync_interval_three_days">každé tři dny</string>
<item>každých dvanáct hodin</item> <string name="sync_interval_one_week">každý týden</string>
<item>každý den</item>
<item>každé tři dny</item>
<item>každý týden</item>
</string-array>
</resources> </resources>

@ -132,16 +132,11 @@
<string name="sync_SPr_interval_title">Baggrunds Synk</string> <string name="sync_SPr_interval_title">Baggrunds Synk</string>
<string name="sync_SPr_forget">Log af</string> <string name="sync_SPr_forget">Log af</string>
<string name="sync_SPr_forget_description">Sletter al synkroniserings data</string> <string name="sync_SPr_forget_description">Sletter al synkroniserings data</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_one_hour">hver time</string>
<item>disable</item> <string name="sync_interval_three_hours">hver 3. time</string>
<item>every fifteen minutes</item> <string name="sync_interval_six_hours">hver 6. time</string>
<item>every thirty minutes</item> <string name="sync_interval_twelve_hours">hver 12. time</string>
<item>hver time</item> <string name="sync_interval_one_day">hver dag</string>
<item>hver 3. time</item> <string name="sync_interval_three_days">hver 3. dag</string>
<item>hver 6. time</item> <string name="sync_interval_one_week">hver uge</string>
<item>hver 12. time</item>
<item>hver dag</item>
<item>hver 3. dag</item>
<item>hver uge</item>
</string-array>
</resources> </resources>

@ -398,16 +398,12 @@
<string name="theme_light">Hell</string> <string name="theme_light">Hell</string>
<string name="theme_dark">Dunkel</string> <string name="theme_dark">Dunkel</string>
<string name="default_value">Standard</string> <string name="default_value">Standard</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">deaktivieren</string>
<item>deaktivieren</item> <string name="sync_interval_one_hour">stündlich</string>
<item>alle 15 Minuten</item> <string name="sync_interval_three_hours">alle 3 Stunden</string>
<item>alle 30 Minuten</item> <string name="sync_interval_six_hours">alle 6 Stunden</string>
<item>stündlich</item> <string name="sync_interval_twelve_hours">alle 12 Stunden</string>
<item>alle 3 Stunden</item> <string name="sync_interval_one_day">täglich</string>
<item>alle 6 Stunden</item> <string name="sync_interval_three_days">jeden dritten Tag</string>
<item>alle 12 Stunden</item> <string name="sync_interval_one_week">wöchentlich</string>
<item>täglich</item>
<item>jeden dritten Tag</item>
<item>wöchentlich</item>
</string-array>
</resources> </resources>

@ -238,16 +238,11 @@
<string name="sync_SPr_interval_title">Συγχρονισμός παρασκηνίου</string> <string name="sync_SPr_interval_title">Συγχρονισμός παρασκηνίου</string>
<string name="sync_SPr_forget">Αποσύνδεση</string> <string name="sync_SPr_forget">Αποσύνδεση</string>
<string name="TLA_menu_donate">Δωρίστε</string> <string name="TLA_menu_donate">Δωρίστε</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_one_hour">κάθε ώρα</string>
<item>disable</item> <string name="sync_interval_three_hours">κάθε τρεις ώρες</string>
<item>every fifteen minutes</item> <string name="sync_interval_six_hours">κάθε έξι ώρες</string>
<item>κάθε τριάντα λεπτά</item> <string name="sync_interval_twelve_hours">κάθε δώδεκα ώρες</string>
<item>κάθε ώρα</item> <string name="sync_interval_one_day">κάθε μέρα</string>
<item>κάθε τρεις ώρες</item> <string name="sync_interval_three_days">κάθε τρεις ημέρες</string>
<item>κάθε έξι ώρες</item> <string name="sync_interval_one_week">κάθε εβδομάδα</string>
<item>κάθε δώδεκα ώρες</item>
<item>κάθε μέρα</item>
<item>κάθε τρεις ημέρες</item>
<item>κάθε εβδομάδα</item>
</string-array>
</resources> </resources>

@ -408,16 +408,12 @@
<string name="theme_light">Claro</string> <string name="theme_light">Claro</string>
<string name="theme_dark">Oscuro</string> <string name="theme_dark">Oscuro</string>
<string name="default_value">Predeterminado</string> <string name="default_value">Predeterminado</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">deshabilitar</string>
<item>deshabilitar</item> <string name="sync_interval_one_hour">cada hora</string>
<item>cada quince minutos</item> <string name="sync_interval_three_hours">cada tres horas</string>
<item>cada treinta minutos</item> <string name="sync_interval_six_hours">cada seis horas</string>
<item>cada hora</item> <string name="sync_interval_twelve_hours">cada doce horas</string>
<item>cada tres horas</item> <string name="sync_interval_one_day">cada día</string>
<item>cada seis horas</item> <string name="sync_interval_three_days">cada tres días</string>
<item>cada doce horas</item> <string name="sync_interval_one_week">cada semana</string>
<item>cada día</item>
<item>cada tres días</item>
<item>cada semana</item>
</string-array>
</resources> </resources>

@ -272,16 +272,12 @@
<string name="disable_notification_light">خاموش کردن چراغ اعلان</string> <string name="disable_notification_light">خاموش کردن چراغ اعلان</string>
<string name="no_title">(بدون عنوان)</string> <string name="no_title">(بدون عنوان)</string>
<string name="default_list">لیست پیش فرض</string> <string name="default_list">لیست پیش فرض</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">غیرفعال</string>
<item>غیرفعال</item> <string name="sync_interval_one_hour">هر ساعت</string>
<item>هر پانزده دقیقه</item> <string name="sync_interval_three_hours">هر سه ساعت</string>
<item>هر سی دقیقه</item> <string name="sync_interval_six_hours">هر شش ساعت</string>
<item>هر ساعت</item> <string name="sync_interval_twelve_hours">هر دوازده ساعت</string>
<item>هر سه ساعت</item> <string name="sync_interval_one_day">هر روز</string>
<item>هر شش ساعت</item> <string name="sync_interval_three_days">هر سه روز</string>
<item>هر دوازده ساعت</item> <string name="sync_interval_one_week">هر هفته</string>
<item>هر روز</item>
<item>هر سه روز</item>
<item>هر هفته</item>
</string-array>
</resources> </resources>

@ -369,16 +369,12 @@
<string name="select_filter">Sélectionner le filtre</string> <string name="select_filter">Sélectionner le filtre</string>
<string name="filter">Filtre</string> <string name="filter">Filtre</string>
<string name="opacity">Transparence</string> <string name="opacity">Transparence</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">désactiver</string>
<item>désactiver</item> <string name="sync_interval_one_hour">toutes les heures</string>
<item>toutes les quinze minutes</item> <string name="sync_interval_three_hours">toutes les trois heures</string>
<item>toutes les trente minutes</item> <string name="sync_interval_six_hours">toutes les six heures</string>
<item>toutes les heures</item> <string name="sync_interval_twelve_hours">toutes les douze heures</string>
<item>toutes les trois heures</item> <string name="sync_interval_one_day">tous les jours</string>
<item>toutes les six heures</item> <string name="sync_interval_three_days">tous les trois jours</string>
<item>toutes les douze heures</item> <string name="sync_interval_one_week">toutes les semaines</string>
<item>tous les jours</item>
<item>tous les trois jours</item>
<item>toutes les semaines</item>
</string-array>
</resources> </resources>

@ -411,16 +411,12 @@ Se visualizzi questo errore più volte, ti consigliamo di cancellare tutti i dat
<string name="theme_light">Chiaro</string> <string name="theme_light">Chiaro</string>
<string name="theme_dark">Scuro</string> <string name="theme_dark">Scuro</string>
<string name="default_value">Predefinito</string> <string name="default_value">Predefinito</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">disabilita</string>
<item>disabilita</item> <string name="sync_interval_one_hour">ogni ora</string>
<item>ogni quindici minuti</item> <string name="sync_interval_three_hours">ogni tre ore</string>
<item>ogni trenta minuti</item> <string name="sync_interval_six_hours">ogni sei ore</string>
<item>ogni ora</item> <string name="sync_interval_twelve_hours">ogni dodici ore</string>
<item>ogni tre ore</item> <string name="sync_interval_one_day">ogni giorno</string>
<item>ogni sei ore</item> <string name="sync_interval_three_days">ogni tre giorni</string>
<item>ogni dodici ore</item> <string name="sync_interval_one_week">Ogni settimana</string>
<item>ogni giorno</item>
<item>ogni tre giorni</item>
<item>Ogni settimana</item>
</string-array>
</resources> </resources>

@ -406,16 +406,12 @@
<string name="theme_grey">אפור</string> <string name="theme_grey">אפור</string>
<string name="theme_blue_grey">כחול אפור</string> <string name="theme_blue_grey">כחול אפור</string>
<string name="theme_black">שחור</string> <string name="theme_black">שחור</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">מִנְעִי</string>
<item>מִנְעִי</item> <string name="sync_interval_one_hour">כל שעה</string>
<item>כל רבע שעה</item> <string name="sync_interval_three_hours">כל שלוש שעות</string>
<item>כל חצי שעה</item> <string name="sync_interval_six_hours">כל שש שעות</string>
<item>כל שעה</item> <string name="sync_interval_twelve_hours">כל שתים עשרה שעות</string>
<item>כל שלוש שעות</item> <string name="sync_interval_one_day">כל יום</string>
<item>כל שש שעות</item> <string name="sync_interval_three_days">כל שלושה ימים</string>
<item>כל שתים עשרה שעות</item> <string name="sync_interval_one_week">כל שבוע</string>
<item>כל יום</item>
<item>כל שלושה ימים</item>
<item>כל שבוע</item>
</string-array>
</resources> </resources>

@ -414,16 +414,12 @@
<string name="theme_light">ライト</string> <string name="theme_light">ライト</string>
<string name="theme_dark">ダーク</string> <string name="theme_dark">ダーク</string>
<string name="default_value">デフォルト</string> <string name="default_value">デフォルト</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">無効</string>
<item>無効</item> <string name="sync_interval_one_hour">1時間毎</string>
<item>15分毎</item> <string name="sync_interval_three_hours">3時間毎</string>
<item>30分毎</item> <string name="sync_interval_six_hours">6時間毎</string>
<item>1時間毎</item> <string name="sync_interval_twelve_hours">12時間毎</string>
<item>3時間毎</item> <string name="sync_interval_one_day">毎日</string>
<item>6時間毎</item> <string name="sync_interval_three_days">3日に一度</string>
<item>12時間毎</item> <string name="sync_interval_one_week">毎週</string>
<item>毎日</item>
<item>3日に一度</item>
<item>毎週</item>
</string-array>
</resources> </resources>

@ -409,16 +409,12 @@ Tasks의 백업에서 당신의 일정을 복구하시기 바랍니다.
<string name="theme_grey">회색</string> <string name="theme_grey">회색</string>
<string name="theme_blue_grey">회청색</string> <string name="theme_blue_grey">회청색</string>
<string name="theme_black">검정</string> <string name="theme_black">검정</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">사용안함</string>
<item>사용안함</item> <string name="sync_interval_one_hour">매시간</string>
<item>15분마다</item> <string name="sync_interval_three_hours">3시간마다</string>
<item>30분마다</item> <string name="sync_interval_six_hours">6시간마다</string>
<item>매시간</item> <string name="sync_interval_twelve_hours">12시간마다</string>
<item>3시간마다</item> <string name="sync_interval_one_day">매일</string>
<item>6시간마다</item> <string name="sync_interval_three_days">3일마다</string>
<item>12시간마다</item> <string name="sync_interval_one_week">일주일마다</string>
<item>매일</item>
<item>3일마다</item>
<item>일주일마다</item>
</string-array>
</resources> </resources>

@ -124,16 +124,12 @@
<string name="sync_SPr_forget">Logg ut</string> <string name="sync_SPr_forget">Logg ut</string>
<string name="sync_SPr_forget_description">Sletter all synkroniseringsdata</string> <string name="sync_SPr_forget_description">Sletter all synkroniseringsdata</string>
<string name="sync_forget_confirm">Logg ut / slett synkroniseringsdata?</string> <string name="sync_forget_confirm">Logg ut / slett synkroniseringsdata?</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">deaktiver</string>
<item>deaktiver</item> <string name="sync_interval_one_hour">hver time</string>
<item>hvert kvarter</item> <string name="sync_interval_three_hours">hver tredje time</string>
<item>hver halvtime</item> <string name="sync_interval_six_hours">hver sjette time</string>
<item>hver time</item> <string name="sync_interval_twelve_hours">hver tolvte time</string>
<item>hver tredje time</item> <string name="sync_interval_one_day">daglig</string>
<item>hver sjette time</item> <string name="sync_interval_three_days">hver tredje dag</string>
<item>hver tolvte time</item> <string name="sync_interval_one_week">hver uke</string>
<item>daglig</item>
<item>hver tredje dag</item>
<item>hver uke</item>
</string-array>
</resources> </resources>

@ -382,16 +382,12 @@
<string name="theme_grey">Grijs</string> <string name="theme_grey">Grijs</string>
<string name="theme_blue_grey">Blauwgrijs</string> <string name="theme_blue_grey">Blauwgrijs</string>
<string name="theme_black">Zwart</string> <string name="theme_black">Zwart</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">uitschakelen</string>
<item>uitschakelen</item> <string name="sync_interval_one_hour">elk uur</string>
<item>elke 15 min.</item> <string name="sync_interval_three_hours">elke 3 uur</string>
<item>elke 30 min.</item> <string name="sync_interval_six_hours">elke 6 uur</string>
<item>elk uur</item> <string name="sync_interval_twelve_hours">elke 12 uur</string>
<item>elke 3 uur</item> <string name="sync_interval_one_day">elke dag</string>
<item>elke 6 uur</item> <string name="sync_interval_three_days">elke 3 dagen</string>
<item>elke 12 uur</item> <string name="sync_interval_one_week">elke week</string>
<item>elke dag</item>
<item>elke 3 dagen</item>
<item>elke week</item>
</string-array>
</resources> </resources>

@ -276,16 +276,12 @@ i odzyskanie zadań z kopi zapasowej (Settings-&gt;Sync and backup-&gt;Backup-&g
<string name="doze_notifications_on">Android będzie pozwalał na ograniczone przerwania kiedy urządzenie jest w trybie drzemki.</string> <string name="doze_notifications_on">Android będzie pozwalał na ograniczone przerwania kiedy urządzenie jest w trybie drzemki.</string>
<string name="plugin_description">Tasks jest projektem na licencji open source utrzymywanym przez jednego developera. Płatne funkcje wspierają rozwój aplikacji.</string> <string name="plugin_description">Tasks jest projektem na licencji open source utrzymywanym przez jednego developera. Płatne funkcje wspierają rozwój aplikacji.</string>
<string name="opacity">Przezroczystość</string> <string name="opacity">Przezroczystość</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">Wyłączone</string>
<item>Wyłączone</item> <string name="sync_interval_one_hour">co godzinę</string>
<item>co 15 minut</item> <string name="sync_interval_three_hours">co 3 godziny</string>
<item>co 30 minut</item> <string name="sync_interval_six_hours">co 6 godzin</string>
<item>co godzinę</item> <string name="sync_interval_twelve_hours">co 12 godzin</string>
<item>co 3 godziny</item> <string name="sync_interval_one_day">raz dziennie</string>
<item>co 6 godzin</item> <string name="sync_interval_three_days">co 3 dni</string>
<item>co 12 godzin</item> <string name="sync_interval_one_week">co tydzień</string>
<item>raz dziennie</item>
<item>co 3 dni</item>
<item>co tydzień</item>
</string-array>
</resources> </resources>

@ -336,16 +336,12 @@
<string name="theme_blue_grey">Azul Acinzentado</string> <string name="theme_blue_grey">Azul Acinzentado</string>
<string name="theme_light">Claro</string> <string name="theme_light">Claro</string>
<string name="theme_dark">Escuro</string> <string name="theme_dark">Escuro</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">desabilitar</string>
<item>desabilitar</item> <string name="sync_interval_one_hour">a cada hora</string>
<item>a cada quinze minutos</item> <string name="sync_interval_three_hours">a cada três horas</string>
<item>a cada trinta minutos</item> <string name="sync_interval_six_hours">a cada seis horas</string>
<item>a cada hora</item> <string name="sync_interval_twelve_hours">a cada doze horas</string>
<item>a cada três horas</item> <string name="sync_interval_one_day">diariamente</string>
<item>a cada seis horas</item> <string name="sync_interval_three_days">a cada três dias</string>
<item>a cada doze horas</item> <string name="sync_interval_one_week">semanalmente</string>
<item>diariamente</item>
<item>a cada três dias</item>
<item>semanalmente</item>
</string-array>
</resources> </resources>

@ -328,16 +328,12 @@ das tarefas através de um backup em Definições-&gt;Sincronização e backup-&
<string name="show_completed">Mostrar terminadas</string> <string name="show_completed">Mostrar terminadas</string>
<string name="reverse">Reverter</string> <string name="reverse">Reverter</string>
<string name="task_count">%s tarefas</string> <string name="task_count">%s tarefas</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">desativar</string>
<item>desativar</item> <string name="sync_interval_one_hour">cada hora</string>
<item>cada 15 minutos</item> <string name="sync_interval_three_hours">cada 3 horas</string>
<item>cada 30 minutos</item> <string name="sync_interval_six_hours">cada 6 horas</string>
<item>cada hora</item> <string name="sync_interval_twelve_hours">cada 12 horas</string>
<item>cada 3 horas</item> <string name="sync_interval_one_day">todos os dias</string>
<item>cada 6 horas</item> <string name="sync_interval_three_days">cada 3 dias</string>
<item>cada 12 horas</item> <string name="sync_interval_one_week">todas as semanas</string>
<item>todos os dias</item>
<item>cada 3 dias</item>
<item>todas as semanas</item>
</string-array>
</resources> </resources>

@ -413,16 +413,12 @@
<string name="theme_light">Светлая</string> <string name="theme_light">Светлая</string>
<string name="theme_dark">Темная</string> <string name="theme_dark">Темная</string>
<string name="default_value">По умолчанию</string> <string name="default_value">По умолчанию</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">отключить</string>
<item>отключить</item> <string name="sync_interval_one_hour">каждый час</string>
<item>каждые 15 минут</item> <string name="sync_interval_three_hours">каждые 3 часа</string>
<item>каждые 30 минут</item> <string name="sync_interval_six_hours">каждые 6 часов</string>
<item>каждый час</item> <string name="sync_interval_twelve_hours">каждые 12 часов</string>
<item>каждые 3 часа</item> <string name="sync_interval_one_day">каждый день</string>
<item>каждые 6 часов</item> <string name="sync_interval_three_days">каждые 3 дня</string>
<item>каждые 12 часов</item> <string name="sync_interval_one_week">каждую неделю</string>
<item>каждый день</item>
<item>каждые 3 дня</item>
<item>каждую неделю</item>
</string-array>
</resources> </resources>

@ -308,16 +308,12 @@
<string name="show_hidden">Zobraziť skryté</string> <string name="show_hidden">Zobraziť skryté</string>
<string name="show_completed">Zobraziť dokončené</string> <string name="show_completed">Zobraziť dokončené</string>
<string name="task_count">%s úlohy</string> <string name="task_count">%s úlohy</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">zakázať</string>
<item>zakázať</item> <string name="sync_interval_one_hour">každú hodinu</string>
<item>každých pätnásť minút</item> <string name="sync_interval_three_hours">každé tri hodiny</string>
<item>každú tretiu minútu</item> <string name="sync_interval_six_hours">každých šesť hodín</string>
<item>každú hodinu</item> <string name="sync_interval_twelve_hours">každých dvanásť hodín</string>
<item>každé tri hodiny</item> <string name="sync_interval_one_day">každý deň</string>
<item>každých šesť hodín</item> <string name="sync_interval_three_days">každý tretí deň</string>
<item>každých dvanásť hodín</item> <string name="sync_interval_one_week">každý týždeň</string>
<item>každý deň</item>
<item>každý tretí deň</item>
<item>každý týždeň</item>
</string-array>
</resources> </resources>

@ -247,16 +247,12 @@
<string name="sync_SPr_forget_description">Zbriše vse usklajene podatke</string> <string name="sync_SPr_forget_description">Zbriše vse usklajene podatke</string>
<string name="sync_forget_confirm">Odjava/brisanje usklajenih podatkov?</string> <string name="sync_forget_confirm">Odjava/brisanje usklajenih podatkov?</string>
<string name="TLA_menu_donate">Donirajte</string> <string name="TLA_menu_donate">Donirajte</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">onemogoči</string>
<item>onemogoči</item> <string name="sync_interval_one_hour">vsako uro</string>
<item>vsakih 15 minut</item> <string name="sync_interval_three_hours">vsake 3 ure</string>
<item>vsakih 30 minut</item> <string name="sync_interval_six_hours">vsakih 6 ur</string>
<item>vsako uro</item> <string name="sync_interval_twelve_hours">vsakih 12 ur</string>
<item>vsake 3 ure</item> <string name="sync_interval_one_day">vsak dan</string>
<item>vsakih 6 ur</item> <string name="sync_interval_three_days">vsake 3 dni</string>
<item>vsakih 12 ur</item> <string name="sync_interval_one_week">vsak teden</string>
<item>vsak dan</item>
<item>vsake 3 dni</item>
<item>vsak teden</item>
</string-array>
</resources> </resources>

@ -402,16 +402,12 @@ och återställer dina aktuella uppgifter från en backup
<string name="theme_grey">Grå</string> <string name="theme_grey">Grå</string>
<string name="theme_blue_grey">Blågrå</string> <string name="theme_blue_grey">Blågrå</string>
<string name="theme_black">Svart</string> <string name="theme_black">Svart</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">inaktivera</string>
<item>inaktivera</item> <string name="sync_interval_one_hour">varje timme</string>
<item>varje kvartstimme</item> <string name="sync_interval_three_hours">var tredje timme</string>
<item>varje halvtimme</item> <string name="sync_interval_six_hours">var sjätte timme</string>
<item>varje timme</item> <string name="sync_interval_twelve_hours">var tolfte timme</string>
<item>var tredje timme</item> <string name="sync_interval_one_day">varje dag</string>
<item>var sjätte timme</item> <string name="sync_interval_three_days">var tredje dag</string>
<item>var tolfte timme</item> <string name="sync_interval_one_week">varje vecka</string>
<item>varje dag</item>
<item>var tredje dag</item>
<item>varje vecka</item>
</string-array>
</resources> </resources>

@ -215,16 +215,12 @@
<string name="sync_SPr_forget">Çıkış Yap</string> <string name="sync_SPr_forget">Çıkış Yap</string>
<string name="sync_SPr_forget_description">Bütün eşleme verilerini temizle</string> <string name="sync_SPr_forget_description">Bütün eşleme verilerini temizle</string>
<string name="sync_forget_confirm">Çıkış Yap / Senkron verisini sil?</string> <string name="sync_forget_confirm">Çıkış Yap / Senkron verisini sil?</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">devre dışı bırak</string>
<item>devre dışı bırak</item> <string name="sync_interval_one_hour">her saat</string>
<item>her 15 dakika</item> <string name="sync_interval_three_hours">her 3 saat</string>
<item>her 30 dakika</item> <string name="sync_interval_six_hours">her 6 saat</string>
<item>her saat</item> <string name="sync_interval_twelve_hours">her 12 saat</string>
<item>her 3 saat</item> <string name="sync_interval_one_day">hergün</string>
<item>her 6 saat</item> <string name="sync_interval_three_days">her 3 gün</string>
<item>her 12 saat</item> <string name="sync_interval_one_week">her hafta</string>
<item>hergün</item>
<item>her 3 gün</item>
<item>her hafta</item>
</string-array>
</resources> </resources>

@ -277,16 +277,12 @@
<string name="doze_notifications">Переривати режим \"Doze\" для повідомленнь</string> <string name="doze_notifications">Переривати режим \"Doze\" для повідомленнь</string>
<string name="doze_notifications_off">Андроїд зазвичай буде значно відкладати повідомлення коли пристрій в режимі \"Doze\"</string> <string name="doze_notifications_off">Андроїд зазвичай буде значно відкладати повідомлення коли пристрій в режимі \"Doze\"</string>
<string name="doze_notifications_on">Android дозволить деякі переривання в режимі \"Doze\"</string> <string name="doze_notifications_on">Android дозволить деякі переривання в режимі \"Doze\"</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">вимкнути</string>
<item>вимкнути</item> <string name="sync_interval_one_hour">кожну годину</string>
<item>що 15 хвилин</item> <string name="sync_interval_three_hours">кожних 3 години</string>
<item>що 30 хвилин</item> <string name="sync_interval_six_hours">кожних 6 годин</string>
<item>кожну годину</item> <string name="sync_interval_twelve_hours">кожних 12 годин</string>
<item>кожних 3 години</item> <string name="sync_interval_one_day">щодня</string>
<item>кожних 6 годин</item> <string name="sync_interval_three_days">кожного 3-го дня</string>
<item>кожних 12 годин</item> <string name="sync_interval_one_week">кожного тижня</string>
<item>щодня</item>
<item>кожного 3-го дня</item>
<item>кожного тижня</item>
</string-array>
</resources> </resources>

@ -207,16 +207,12 @@
<string name="sync_SPr_forget">登出</string> <string name="sync_SPr_forget">登出</string>
<string name="sync_SPr_forget_description">清除所有同步资料</string> <string name="sync_SPr_forget_description">清除所有同步资料</string>
<string name="sync_forget_confirm">登出/清除同步资料?</string> <string name="sync_forget_confirm">登出/清除同步资料?</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">停用</string>
<item>停用</item> <string name="sync_interval_one_hour">每小时</string>
<item>每15分钟</item> <string name="sync_interval_three_hours">每3小时</string>
<item>每30分钟</item> <string name="sync_interval_six_hours">每6小时</string>
<item>每小时</item> <string name="sync_interval_twelve_hours">每12小时</string>
<item>每3小时</item> <string name="sync_interval_one_day">每天</string>
<item>每6小时</item> <string name="sync_interval_three_days">每3天</string>
<item>每12小时</item> <string name="sync_interval_one_week">每周</string>
<item>每天</item>
<item>每3天</item>
<item>每周</item>
</string-array>
</resources> </resources>

@ -233,16 +233,12 @@
<string name="high_priority">最優先</string> <string name="high_priority">最優先</string>
<string name="default_priority">預設</string> <string name="default_priority">預設</string>
<string name="low_priority">最不急迫</string> <string name="low_priority">最不急迫</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">停用</string>
<item>停用</item> <string name="sync_interval_one_hour">每小時</string>
<item>每15分鐘</item> <string name="sync_interval_three_hours">每3小時</string>
<item>每30分鐘</item> <string name="sync_interval_six_hours">每6小時</string>
<item>每小時</item> <string name="sync_interval_twelve_hours">每12小時</string>
<item>每3小時</item> <string name="sync_interval_one_day">每天</string>
<item>每6小時</item> <string name="sync_interval_three_days">每3天</string>
<item>每12小時</item> <string name="sync_interval_one_week">每週</string>
<item>每天</item>
<item>每3天</item>
<item>每週</item>
</string-array>
</resources> </resources>

@ -40,6 +40,18 @@
<item>600</item> <item>600</item>
</string-array> </string-array>
<string-array name="sync_interval_entries">
<!-- sync_SPr_interval_entries: Synchronization Intervals -->
<item>@string/sync_interval_disable</item>
<item>@string/sync_interval_one_hour</item>
<item>@string/sync_interval_three_hours</item>
<item>@string/sync_interval_six_hours</item>
<item>@string/sync_interval_twelve_hours</item>
<item>@string/sync_interval_one_day</item>
<item>@string/sync_interval_three_days</item>
<item>@string/sync_interval_one_week</item>
</string-array>
<string-array name="TEA_hideUntil"> <string-array name="TEA_hideUntil">
<item>@string/due_date</item> <item>@string/due_date</item>
<item>@string/due_time</item> <item>@string/due_time</item>

@ -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>

@ -178,8 +178,6 @@
<string-array name="sync_SPr_interval_values"> <string-array name="sync_SPr_interval_values">
<!-- sync_SPr_interval_values: interval in seconds for sync entries (do not edit) --> <!-- sync_SPr_interval_values: interval in seconds for sync entries (do not edit) -->
<item>0</item> <item>0</item>
<item>900</item>
<item>1800</item>
<item>3600</item> <item>3600</item>
<item>10800</item> <item>10800</item>
<item>21600</item> <item>21600</item>
@ -306,5 +304,6 @@
<string name="p_theme_color">theme_color</string> <string name="p_theme_color">theme_color</string>
<string name="p_theme_accent">theme_accent</string> <string name="p_theme_accent">theme_accent</string>
<string name="p_gtasks_default_list">default_gtasks_list</string> <string name="p_gtasks_default_list">default_gtasks_list</string>
<string name="p_sync_warning_shown">sync_warning_shown</string>
</resources> </resources>

@ -894,18 +894,14 @@ File %1$s contained %2$s.\n\n
<string name="theme_dark">Dark</string> <string name="theme_dark">Dark</string>
<string name="default_value">Default</string> <string name="default_value">Default</string>
<string-array name="sync_SPr_interval_entries"> <string name="sync_interval_disable">disable</string>
<!-- sync_SPr_interval_entries: Synchronization Intervals --> <string name="sync_interval_one_hour">every hour</string>
<item>disable</item> <string name="sync_interval_three_hours">every three hours</string>
<item>every fifteen minutes</item> <string name="sync_interval_six_hours">every six hours</string>
<item>every thirty minutes</item> <string name="sync_interval_twelve_hours">every twelve hours</string>
<item>every hour</item> <string name="sync_interval_one_day">every day</string>
<item>every three hours</item> <string name="sync_interval_three_days">every three days</string>
<item>every six hours</item> <string name="sync_interval_one_week">every week</string>
<item>every twelve hours</item> <string name="master_sync_warning">Automatic synchronization is currently disabled by Android</string>
<item>every day</item>
<item>every three days</item>
<item>every week</item>
</string-array>
</resources> </resources>

@ -19,9 +19,9 @@
android:title="@string/default_list" /> android:title="@string/default_list" />
<com.todoroo.astrid.ui.MultilineListPreference <com.todoroo.astrid.ui.MultilineListPreference
android:defaultValue="0" android:defaultValue="3600"
android:dependency="@string/sync_gtasks" android:dependency="@string/sync_gtasks"
android:entries="@array/sync_SPr_interval_entries" android:entries="@array/sync_interval_entries"
android:entryValues="@array/sync_SPr_interval_values" android:entryValues="@array/sync_SPr_interval_values"
android:key="@string/gtasks_GPr_interval_key" android:key="@string/gtasks_GPr_interval_key"
android:title="@string/sync_SPr_interval_title" /> android:title="@string/sync_SPr_interval_title" />

@ -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…
Cancel
Save