Use android-job to sync Google Tasks

pull/699/head
Alex Baker 6 years ago
parent a20479d952
commit dee29eded4

@ -1,21 +1,13 @@
package org.tasks;
import javax.inject.Inject;
import org.tasks.preferences.Preferences;
public class FlavorSetup {
private final CaldavAccountManager caldavAccountManager;
private final Preferences preferences;
@Inject
public FlavorSetup(CaldavAccountManager caldavAccountManager, Preferences preferences) {
this.caldavAccountManager = caldavAccountManager;
this.preferences = preferences;
public FlavorSetup() {
}
public void setup() {
caldavAccountManager
.setBackgroundSynchronization(preferences.getBoolean(R.string.p_background_sync, true));
}
}

@ -4,7 +4,7 @@ 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_TIME;
import static junit.framework.Assert.assertEquals;
import static org.tasks.gtasks.GoogleTaskSyncAdapter.mergeDates;
import static org.tasks.gtasks.GoogleTaskSynchronizer.mergeDates;
import static org.tasks.makers.TaskMaker.DUE_DATE;
import static org.tasks.makers.TaskMaker.DUE_TIME;
import static org.tasks.makers.TaskMaker.HIDE_TYPE;
@ -17,7 +17,7 @@ import org.junit.runner.RunWith;
import org.tasks.time.DateTime;
@RunWith(AndroidJUnit4.class)
public class GoogleTaskSyncAdapterTest {
public class GoogleTaskSynchronizerTest {
@Test
public void testMergeDate() {

@ -1,21 +1,13 @@
package org.tasks;
import javax.inject.Inject;
import org.tasks.preferences.Preferences;
public class FlavorSetup {
private final CaldavAccountManager caldavAccountManager;
private final Preferences preferences;
@Inject
public FlavorSetup(CaldavAccountManager caldavAccountManager, Preferences preferences) {
this.caldavAccountManager = caldavAccountManager;
this.preferences = preferences;
public FlavorSetup() {
}
public void setup() {
caldavAccountManager
.setBackgroundSynchronization(preferences.getBoolean(R.string.p_background_sync, true));
}
}

@ -20,14 +20,7 @@
<!-- google task sync -->
<!-- **************** -->
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_STATS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<application tools:ignore="GoogleAppIndexingWarning">
@ -78,21 +71,6 @@
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>
<!-- SyncService for Google Tasks -->
<service
android:name=".gtasks.GoogleTaskSyncService"
android:exported="true"
android:permission="signature">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter_gtask" />
</service>
</application>
</manifest>

@ -1,41 +1,22 @@
package org.tasks;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import javax.inject.Inject;
import org.tasks.billing.InventoryHelper;
import org.tasks.gtasks.GoogleAccountManager;
import org.tasks.gtasks.PlayServices;
import org.tasks.jobs.JobManager;
import org.tasks.preferences.Preferences;
public class FlavorSetup {
private final GtasksPreferenceService gtasksPreferenceService;
private final InventoryHelper inventoryHelper;
private final Preferences preferences;
private final PlayServices playServices;
private final GoogleAccountManager googleAccountManager;
private final JobManager jobManager;
@Inject
public FlavorSetup(GtasksPreferenceService gtasksPreferenceService,
InventoryHelper inventoryHelper,
Preferences preferences, PlayServices playServices,
GoogleAccountManager googleAccountManager, JobManager jobManager) {
this.gtasksPreferenceService = gtasksPreferenceService;
public FlavorSetup(InventoryHelper inventoryHelper, PlayServices playServices) {
this.inventoryHelper = inventoryHelper;
this.preferences = preferences;
this.playServices = playServices;
this.googleAccountManager = googleAccountManager;
this.jobManager = jobManager;
}
public void setup() {
inventoryHelper.initialize();
gtasksPreferenceService.stopOngoing(); // if sync ongoing flag was set, clear it
boolean backgroundSyncEnabled = preferences.getBoolean(R.string.p_background_sync, true);
googleAccountManager.setBackgroundSynchronization(backgroundSyncEnabled);
jobManager.setBackgroundSynchronization(backgroundSyncEnabled);
playServices.refresh();
}
}

@ -1,7 +0,0 @@
<?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:isAlwaysSyncable="true"
android:userVisible="false" />

@ -126,7 +126,7 @@ public class TaskListFragment extends InjectingFragment implements
@Override
public void onRefresh() {
if (!syncAdapters.initiateManualSync()) {
if (!syncAdapters.syncNow()) {
refresh();
}
}
@ -440,7 +440,7 @@ public class TaskListFragment extends InjectingFragment implements
for (Task task : tasks) {
onTaskCreated(task.getUuid());
}
syncAdapters.requestSynchronization();
syncAdapters.syncNow();
}
public void onTaskCreated(String uuid) {

@ -6,7 +6,7 @@
package com.todoroo.astrid.gtasks.sync;
import static org.tasks.gtasks.GoogleTaskSyncAdapter.mergeDates;
import static org.tasks.gtasks.GoogleTaskSynchronizer.mergeDates;
import com.google.api.client.util.DateTime;
import com.todoroo.andlib.utility.DateUtilities;

@ -1,13 +1,14 @@
package org.tasks;
import com.evernote.android.job.JobManager;
import com.jakewharton.threetenabp.AndroidThreeTen;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.service.StartupService;
import javax.inject.Inject;
import org.tasks.analytics.Tracker;
import org.tasks.injection.ApplicationComponent;
import org.tasks.injection.InjectingApplication;
import org.tasks.jobs.JobCreator;
import org.tasks.jobs.JobManager;
import org.tasks.preferences.Preferences;
import org.tasks.receivers.Badger;
import org.tasks.themes.ThemeCache;
@ -23,6 +24,7 @@ public class Tasks extends InjectingApplication {
@Inject Badger badger;
@Inject JobManager jobManager;
@Inject JobCreator jobCreator;
@Inject GtasksPreferenceService gtasksPreferenceService;
@Override
public void onCreate() {
@ -45,6 +47,9 @@ public class Tasks extends InjectingApplication {
startupService.onStartupApplication();
jobManager.addJobCreator(jobCreator);
gtasksPreferenceService.stopOngoing(); // if sync ongoing flag was set, clear it
jobManager.updateBackgroundSync();
}
@Override

@ -80,24 +80,20 @@ public class CaldavSynchronizer {
this.taskDeleter = taskDeleter;
}
public boolean sync() {
public void sync() {
// required for dav4android (ServiceLoader)
Thread.currentThread().setContextClassLoader(context.getClassLoader());
boolean success = true;
for (CaldavAccount account : caldavDao.getAccounts()) {
success &= sync(account);
sync(account);
}
return success;
}
private boolean sync(CaldavAccount caldavAccount) {
// required for dav4android (ServiceLoader)
String uuid = caldavAccount.getUuid();
Timber.d("onPerformSync: %s [%s]", caldavAccount.getName(), uuid);
private void sync(CaldavAccount caldavAccount) {
if (isNullOrEmpty(caldavAccount.getPassword())) {
Timber.e("Missing password for %s", caldavAccount.getName());
return false;
Timber.e("Missing password for %s", caldavAccount);
return;
}
Timber.d("sync(%s)", caldavAccount);
BasicDigestAuthHandler basicDigestAuthHandler = new BasicDigestAuthHandler(null,
caldavAccount.getUsername(), caldavAccount.getPassword());
OkHttpClient httpClient = new OkHttpClient().newBuilder()
@ -130,7 +126,7 @@ public class CaldavSynchronizer {
if (localCtag != null && localCtag.equals(remoteCtag)) {
Timber.d("%s up to date", caldavAccount.getName());
return true;
return;
}
davCalendar.calendarQuery("VTODO", null, null);
@ -213,7 +209,6 @@ public class CaldavSynchronizer {
}
localBroadcastManager.broadcastRefresh();
return true;
}
private void pushLocalChanges(CaldavAccount caldavAccount, OkHttpClient httpClient,

@ -5,23 +5,17 @@ import static com.google.common.collect.Lists.transform;
import static java.util.Arrays.asList;
import android.accounts.Account;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
import com.google.common.base.Strings;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.PermissionChecker;
import timber.log.Timber;
public class GoogleAccountManager {
private static final String AUTHORITY = "org.tasks";
private final PermissionChecker permissionChecker;
private final android.accounts.AccountManager accountManager;
private final GtasksPreferenceService gtasksPreferenceService;
@ -53,20 +47,6 @@ public class GoogleAccountManager {
return getAccount(gtasksPreferenceService.getUserName());
}
public void setBackgroundSynchronization(boolean enabled) {
Account account = getSelectedAccount();
if (account != null) {
Timber.d("enableBackgroundSynchronization=%s", enabled);
ContentResolver.setSyncAutomatically(account, AUTHORITY, enabled);
if (enabled) {
ContentResolver
.addPeriodicSync(account, AUTHORITY, Bundle.EMPTY, TimeUnit.HOURS.toSeconds(1));
} else {
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle.EMPTY);
}
}
}
public Account getAccount(final String name) {
if (Strings.isNullOrEmpty(name)) {
return null;

@ -1,76 +0,0 @@
/*
* 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();
}
}

@ -1,30 +1,10 @@
/*
* 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 static org.tasks.date.DateTimeUtils.newDateTime;
import android.accounts.Account;
import android.app.PendingIntent;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
@ -59,8 +39,7 @@ import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.GoogleTaskListDao;
import org.tasks.injection.InjectingAbstractThreadedSyncAdapter;
import org.tasks.injection.SyncAdapterComponent;
import org.tasks.injection.ForApplication;
import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.Preferences;
@ -68,36 +47,49 @@ import org.tasks.sync.RecordSyncStatusCallback;
import org.tasks.time.DateTime;
import timber.log.Timber;
/**
* 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 {
public class GoogleTaskSynchronizer {
private static final String DEFAULT_LIST = "@default"; //$NON-NLS-1$
@Inject GtasksPreferenceService gtasksPreferenceService;
@Inject LocalBroadcastManager localBroadcastManager;
@Inject GoogleTaskListDao googleTaskListDao;
@Inject GtasksSyncService gtasksSyncService;
@Inject GtasksListService gtasksListService;
@Inject GtasksTaskListUpdater gtasksTaskListUpdater;
@Inject Preferences preferences;
@Inject GtasksInvoker gtasksInvoker;
@Inject TaskDao taskDao;
@Inject Tracker tracker;
@Inject NotificationManager notificationManager;
@Inject GoogleTaskDao googleTaskDao;
@Inject TaskCreator taskCreator;
@Inject DefaultFilterProvider defaultFilterProvider;
public GoogleTaskSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
private final Context context;
private final GtasksPreferenceService gtasksPreferenceService;
private final LocalBroadcastManager localBroadcastManager;
private final GoogleTaskListDao googleTaskListDao;
private final GtasksSyncService gtasksSyncService;
private final GtasksListService gtasksListService;
private final GtasksTaskListUpdater gtasksTaskListUpdater;
private final Preferences preferences;
private final GtasksInvoker gtasksInvoker;
private final TaskDao taskDao;
private final Tracker tracker;
private final NotificationManager notificationManager;
private final GoogleTaskDao googleTaskDao;
private final TaskCreator taskCreator;
private final DefaultFilterProvider defaultFilterProvider;
@Inject
public GoogleTaskSynchronizer(@ForApplication Context context,
GtasksPreferenceService gtasksPreferenceService, LocalBroadcastManager localBroadcastManager,
GoogleTaskListDao googleTaskListDao, GtasksSyncService gtasksSyncService,
GtasksListService gtasksListService, GtasksTaskListUpdater gtasksTaskListUpdater,
Preferences preferences, GtasksInvoker gtasksInvoker, TaskDao taskDao, Tracker tracker,
NotificationManager notificationManager, GoogleTaskDao googleTaskDao, TaskCreator taskCreator,
DefaultFilterProvider defaultFilterProvider) {
this.context = context;
this.gtasksPreferenceService = gtasksPreferenceService;
this.localBroadcastManager = localBroadcastManager;
this.googleTaskListDao = googleTaskListDao;
this.gtasksSyncService = gtasksSyncService;
this.gtasksListService = gtasksListService;
this.gtasksTaskListUpdater = gtasksTaskListUpdater;
this.preferences = preferences;
this.gtasksInvoker = gtasksInvoker;
this.taskDao = taskDao;
this.tracker = tracker;
this.notificationManager = notificationManager;
this.googleTaskDao = googleTaskDao;
this.taskCreator = taskCreator;
this.defaultFilterProvider = defaultFilterProvider;
}
public static void mergeDates(long remoteDueDate, Task local) {
@ -114,26 +106,9 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
}
}
/**
* 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++;
public void sync() {
String account = gtasksPreferenceService.getUserName();
if (TextUtils.isEmpty(account)) {
return;
}
Timber.d("%s: start sync", account);
@ -145,7 +120,7 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
gtasksPreferenceService.recordSuccessfulSync();
} catch (UserRecoverableAuthIOException e) {
Timber.e(e, e.getMessage());
sendNotification(getContext(), e.getIntent());
sendNotification(context, e.getIntent());
} catch (IOException e) {
Timber.e(e, e.getMessage());
} catch (Exception e) {
@ -215,9 +190,6 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
}
}
/**
* Synchronize with server when data changes
*/
private void pushTask(Task task, GtasksInvoker invoker) throws IOException {
for (GoogleTask deleted : googleTaskDao.getDeletedByTaskId(task.getId())) {
gtasksInvoker.deleteGtask(deleted.getListId(), deleted.getRemoteId());
@ -422,9 +394,4 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
googleTaskDao.insert(values);
}
}
@Override
protected void inject(SyncAdapterComponent component) {
component.inject(this);
}
}

@ -28,38 +28,6 @@ public class GtaskSyncAdapterHelper {
this.tracker = tracker;
}
/**
* 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 void requestSynchronization() {
Account account = getAccount();
if (account == null) {
return;
}
ContentResolver.requestSync(account, AUTHORITY, new Bundle());
}
public boolean isEnabled() {
return preferences.getBoolean(R.string.sync_gtasks, false) &&
playServices.isPlayServicesAvailable() &&

@ -9,8 +9,6 @@ import org.tasks.widget.ScrollableWidgetUpdateService;
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
SyncAdapterComponent plus(SyncAdapterModule syncAdapterModule);
void inject(DashClockExtension dashClockExtension);
void inject(Tasks tasks);

@ -1,20 +0,0 @@
package org.tasks.injection;
import android.content.AbstractThreadedSyncAdapter;
import android.content.Context;
public abstract class InjectingAbstractThreadedSyncAdapter extends AbstractThreadedSyncAdapter {
protected InjectingAbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
inject(context);
}
private void inject(Context context) {
inject(Dagger.get(context)
.getApplicationComponent()
.plus(new SyncAdapterModule()));
}
protected abstract void inject(SyncAdapterComponent component);
}

@ -1,10 +0,0 @@
package org.tasks.injection;
import dagger.Subcomponent;
import org.tasks.gtasks.GoogleTaskSyncAdapter;
@Subcomponent(modules = SyncAdapterModule.class)
public interface SyncAdapterComponent {
void inject(GoogleTaskSyncAdapter googleTaskSyncAdapter);
}

@ -1,8 +0,0 @@
package org.tasks.injection;
import dagger.Module;
@Module
public class SyncAdapterModule {
}

@ -1,20 +0,0 @@
package org.tasks.jobs;
import android.support.annotation.NonNull;
import com.evernote.android.job.Job;
import org.tasks.caldav.CaldavSynchronizer;
public class CaldavSyncJob extends Job{
private final CaldavSynchronizer caldavSynchronizer;
CaldavSyncJob(CaldavSynchronizer caldavSynchronizer) {
this.caldavSynchronizer = caldavSynchronizer;
}
@NonNull
@Override
protected Result onRunJob(@NonNull Params params) {
return caldavSynchronizer.sync() ? Result.SUCCESS : Result.FAILURE;
}
}

@ -9,6 +9,7 @@ import org.tasks.LocalBroadcastManager;
import org.tasks.Notifier;
import org.tasks.backup.TasksJsonExporter;
import org.tasks.caldav.CaldavSynchronizer;
import org.tasks.gtasks.GoogleTaskSynchronizer;
import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
@ -25,18 +26,19 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
private final RefreshScheduler refreshScheduler;
private final LocalBroadcastManager localBroadcastManager;
private final CaldavSynchronizer caldavSynchronizer;
private final GoogleTaskSynchronizer googleTaskSynchronizer;
static final String TAG_BACKUP = "tag_backup";
static final String TAG_REFRESH = "tag_refresh";
static final String TAG_MIDNIGHT_REFRESH = "tag_midnight_refresh";
static final String TAG_NOTIFICATION = "tag_notification";
static final String TAG_CALDAV_SYNC = "tag_caldav_sync";
static final String TAG_SYNC = "tag_sync";
@Inject
public JobCreator(@ForApplication Context context, Preferences preferences, Notifier notifier,
NotificationQueue notificationQueue, TasksJsonExporter tasksJsonExporter,
RefreshScheduler refreshScheduler, LocalBroadcastManager localBroadcastManager,
CaldavSynchronizer caldavSynchronizer) {
CaldavSynchronizer caldavSynchronizer, GoogleTaskSynchronizer googleTaskSynchronizer) {
this.context = context;
this.preferences = preferences;
this.notifier = notifier;
@ -45,6 +47,7 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
this.refreshScheduler = refreshScheduler;
this.localBroadcastManager = localBroadcastManager;
this.caldavSynchronizer = caldavSynchronizer;
this.googleTaskSynchronizer = googleTaskSynchronizer;
}
@Nullable
@ -53,8 +56,8 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
switch (tag) {
case TAG_NOTIFICATION:
return new NotificationJob(preferences, notifier, notificationQueue);
case TAG_CALDAV_SYNC:
return new CaldavSyncJob(caldavSynchronizer);
case TAG_SYNC:
return new SyncJob(caldavSynchronizer, googleTaskSynchronizer);
case TAG_BACKUP:
return new BackupJob(context, tasksJsonExporter, preferences);
case TAG_MIDNIGHT_REFRESH:

@ -1,5 +1,6 @@
package org.tasks.jobs;
import static android.text.TextUtils.isEmpty;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import static org.tasks.time.DateTimeUtils.printTimestamp;
@ -7,9 +8,12 @@ import com.evernote.android.job.DailyJob;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.JobRequest.Builder;
import com.evernote.android.job.JobRequest.NetworkType;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.injection.ApplicationScope;
import org.tasks.preferences.Preferences;
import timber.log.Timber;
@ApplicationScope
@ -24,10 +28,12 @@ public class JobManager {
public static final int JOB_ID_TASKER = 11;
private final com.evernote.android.job.JobManager jobManager;
private final Preferences preferences;
@Inject
public JobManager(com.evernote.android.job.JobManager jobManager) {
public JobManager(com.evernote.android.job.JobManager jobManager, Preferences preferences) {
this.jobManager = jobManager;
this.preferences = preferences;
}
public void scheduleNotification(long time) {
@ -57,9 +63,23 @@ public class JobManager {
TimeUnit.HOURS.toMillis(24) - 1);
}
public void setBackgroundSynchronization(boolean enabled) {
public void updateBackgroundSync() {
updateBackgroundSync(false, false);
}
public void updateBackgroundSync(boolean forceAccountPresent, boolean forceBackgroundEnabled) {
boolean backgroundEnabled =
forceBackgroundEnabled || preferences.getBoolean(R.string.p_background_sync, true);
boolean accountsPresent =
forceAccountPresent || preferences.getBoolean(R.string.p_sync_caldav, false) ||
!isEmpty(preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME));
scheduleBackgroundSynchronization(backgroundEnabled && accountsPresent);
}
private void scheduleBackgroundSynchronization(boolean enabled) {
Timber.d("background sync enabled: %s", enabled);
if (enabled) {
new JobRequest.Builder(JobCreator.TAG_CALDAV_SYNC)
new JobRequest.Builder(JobCreator.TAG_SYNC)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequirementsEnforced(true)
.setPeriodic(TimeUnit.HOURS.toMillis(1))
@ -67,18 +87,17 @@ public class JobManager {
.build()
.schedule();
} else {
jobManager.cancelAllForTag(JobCreator.TAG_CALDAV_SYNC);
jobManager.cancelAllForTag(JobCreator.TAG_SYNC);
}
}
public boolean syncCaldavNow() {
new JobRequest.Builder(JobCreator.TAG_CALDAV_SYNC)
public void syncNow() {
new JobRequest.Builder(JobCreator.TAG_SYNC)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequirementsEnforced(true)
.setExecutionWindow(1, 5000)
.build()
.schedule();
return true;
}
public void cancelNotifications() {
@ -94,4 +113,8 @@ public class JobManager {
private long calculateDelay(long time) {
return Math.max(5000, time - currentTimeMillis());
}
public void addJobCreator(JobCreator jobCreator) {
jobManager.addJobCreator(jobCreator);
}
}

@ -0,0 +1,25 @@
package org.tasks.jobs;
import android.support.annotation.NonNull;
import com.evernote.android.job.Job;
import org.tasks.caldav.CaldavSynchronizer;
import org.tasks.gtasks.GoogleTaskSynchronizer;
public class SyncJob extends Job{
private final CaldavSynchronizer caldavSynchronizer;
private final GoogleTaskSynchronizer googleTaskSynchronizer;
SyncJob(CaldavSynchronizer caldavSynchronizer, GoogleTaskSynchronizer googleTaskSynchronizer) {
this.caldavSynchronizer = caldavSynchronizer;
this.googleTaskSynchronizer = googleTaskSynchronizer;
}
@NonNull
@Override
protected Result onRunJob(@NonNull Params params) {
caldavSynchronizer.sync();
googleTaskSynchronizer.sync();
return Result.SUCCESS;
}
}

@ -1,5 +1,6 @@
package org.tasks.preferences;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo;
import static com.todoroo.andlib.utility.AndroidUtilities.preJellybean;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
@ -34,7 +35,7 @@ public class PermissionChecker {
}
public boolean canAccessAccounts() {
return checkPermission(Manifest.permission.GET_ACCOUNTS);
return atLeastOreo() || checkPermission(Manifest.permission.GET_ACCOUNTS);
}
public boolean canAccessLocation() {

@ -1,31 +0,0 @@
package org.tasks.receivers;
import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
import org.tasks.jobs.JobManager;
import org.tasks.sync.SyncAdapters;
public class CalDAVPushReceiver {
private final JobManager jobManager;
private final SyncAdapters syncAdapters;
@Inject
public CalDAVPushReceiver(JobManager jobManager, SyncAdapters syncAdapters) {
this.jobManager = jobManager;
this.syncAdapters = syncAdapters;
}
public void push(Task task, Task original) {
if (!syncAdapters.isCaldavSyncEnabled()) {
return;
}
if (task.checkTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC)) {
return;
}
jobManager.syncCaldavNow();
}
}

@ -1,39 +0,0 @@
package org.tasks.receivers;
import com.google.common.base.Strings;
import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
import org.tasks.sync.SyncAdapters;
public class GoogleTaskPusher {
private final SyncAdapters syncAdapters;
@Inject
public GoogleTaskPusher(SyncAdapters syncAdapters) {
this.syncAdapters = syncAdapters;
}
void push(Task task, Task original) {
if (!syncAdapters.isGoogleTaskSyncEnabled()) {
return;
}
if (task.checkTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC)) {
return;
}
if (original == null ||
!task.getTitle().equals(original.getTitle()) ||
(Strings.isNullOrEmpty(task.getNotes())
? !Strings.isNullOrEmpty(original.getNotes())
: !task.getNotes().equals(original.getNotes())) ||
!task.getDueDate().equals(original.getDueDate()) ||
!task.getCompletionDate().equals(original.getCompletionDate()) ||
!task.getDeletionDate().equals(original.getDeletionDate()) ||
task.checkAndClearTransitory(SyncFlags.FORCE_SYNC)) {
syncAdapters.requestSynchronization();
}
}
}

@ -1,21 +1,63 @@
package org.tasks.receivers;
import com.google.common.base.Strings;
import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
import org.tasks.jobs.JobManager;
import org.tasks.sync.SyncAdapters;
public class PushReceiver {
private final GoogleTaskPusher googleTaskPusher;
private final CalDAVPushReceiver calDAVPushReceiver;
private final SyncAdapters syncAdapters;
private final JobManager jobManager;
@Inject
public PushReceiver(GoogleTaskPusher googleTaskPusher, CalDAVPushReceiver calDAVPushReceiver) {
this.googleTaskPusher = googleTaskPusher;
this.calDAVPushReceiver = calDAVPushReceiver;
public PushReceiver(SyncAdapters syncAdapters, JobManager jobManager) {
this.syncAdapters = syncAdapters;
this.jobManager = jobManager;
}
public void push(Task task, Task original) {
googleTaskPusher.push(task, original);
calDAVPushReceiver.push(task, original);
if (!pushGoogleTasks(task, original)) {
pushCaldav(task, original);
}
}
private boolean pushGoogleTasks(Task task, Task original) {
if (!syncAdapters.isGoogleTaskSyncEnabled()) {
return false;
}
if (task.checkTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC)) {
return false;
}
if (original == null ||
!task.getTitle().equals(original.getTitle()) ||
(Strings.isNullOrEmpty(task.getNotes())
? !Strings.isNullOrEmpty(original.getNotes())
: !task.getNotes().equals(original.getNotes())) ||
!task.getDueDate().equals(original.getDueDate()) ||
!task.getCompletionDate().equals(original.getCompletionDate()) ||
!task.getDeletionDate().equals(original.getDeletionDate()) ||
task.checkAndClearTransitory(SyncFlags.FORCE_SYNC)) {
syncAdapters.syncNow();
return true;
}
return false;
}
private void pushCaldav(Task task, Task original) {
if (!syncAdapters.isCaldavSyncEnabled()) {
return;
}
if (task.checkTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC)) {
return;
}
jobManager.syncNow();
}
}

@ -22,17 +22,12 @@ public class SyncAdapters {
this.jobManager = jobManager;
}
public void requestSynchronization() {
gtaskSyncAdapterHelper.requestSynchronization();
jobManager.syncCaldavNow();
}
public boolean initiateManualSync() {
return gtaskSyncAdapterHelper.initiateManualSync() | jobManager.syncCaldavNow();
}
public boolean isMasterSyncEnabled() {
return ContentResolver.getMasterSyncAutomatically();
public boolean syncNow() {
if (isGoogleTaskSyncEnabled() || isCaldavSyncEnabled()) {
jobManager.syncNow();
return true;
}
return false;
}
public boolean isSyncEnabled() {

@ -60,8 +60,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
caldavEnabled.setChecked(syncAdapters.isCaldavSyncEnabled());
caldavEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
boolean enabled = ((boolean) newValue);
jobManager.setBackgroundSynchronization(
enabled && preferences.getBoolean(R.string.p_background_sync, true));
jobManager.updateBackgroundSync(enabled, false);
return enabled;
});
final CheckBoxPreference gtaskPreference = (CheckBoxPreference) findPreference(
@ -76,7 +75,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
}
return false;
} else {
googleAccountManager.setBackgroundSynchronization(false);
jobManager.updateBackgroundSync();
tracker.reportEvent(Tracking.Events.GTASK_DISABLED);
gtasksPreferenceService.stopOngoing();
return true;
@ -89,9 +88,8 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
}
findPreference(getString(R.string.p_background_sync))
.setOnPreferenceChangeListener((preference, o) -> {
boolean backgroundSyncEnabled = (Boolean) o;
jobManager.setBackgroundSynchronization(backgroundSyncEnabled);
googleAccountManager.setBackgroundSynchronization(backgroundSyncEnabled);
boolean enabled = (Boolean) o;
jobManager.updateBackgroundSync(false, enabled);
return true;
});
findPreference(getString(R.string.sync_SPr_forget_key))
@ -101,7 +99,6 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
gtasksPreferenceService.clearLastSyncDate();
gtasksPreferenceService.setUserName(null);
googleTaskDao.deleteAll();
googleAccountManager.setBackgroundSynchronization(false);
tracker.reportEvent(Tracking.Events.GTASK_LOGOUT);
gtaskPreference.setChecked(false);
})
@ -120,16 +117,8 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
protected void onResume() {
super.onResume();
CheckBoxPreference backgroundSync = (CheckBoxPreference) findPreference(
getString(R.string.p_background_sync));
if (syncAdapters.isMasterSyncEnabled()) {
backgroundSync.setSummary(null);
} else {
backgroundSync.setSummary(R.string.master_sync_warning);
}
if (!permissionChecker.canAccessAccounts()) {
((CheckBoxPreference) findPreference(getString(R.string.sync_gtasks))).setChecked(false);
((CheckBoxPreference) findPreference(getString(R.string.p_sync_caldav))).setChecked(false);
}
}
@ -138,9 +127,8 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
if (requestCode == REQUEST_LOGIN) {
boolean enabled = resultCode == RESULT_OK;
if (enabled) {
boolean backgroundSyncEnabled = preferences.getBoolean(R.string.p_background_sync, true);
googleAccountManager.setBackgroundSynchronization(backgroundSyncEnabled);
tracker.reportEvent(Tracking.Events.GTASK_ENABLED);
jobManager.updateBackgroundSync();
}
((CheckBoxPreference) findPreference(getString(R.string.sync_gtasks))).setChecked(enabled);
} else {

@ -166,7 +166,6 @@
<string name="show_completed">إظهار المكتملة</string>
<string name="plugin_description">تاسكس هو مشروع مفتوح المصدر مدموع من طرف مطور واحد. بعض الخيارات متوفرة عن الطريق الدفع من داخل التطبيق من أجل دعم التطوير</string>
<string name="opacity">التعتيم</string>
<string name="master_sync_warning">المزامنة التلقائية معطلة الآن من طرف أندرويد</string>
<string name="settings_localization">تخصيص اللغة و الجهة</string>
<string name="layout_direction">إتجاه التنسيق</string>
<string name="layout_direction_left_to_right">من اليمين إلى اليسار</string>

@ -425,7 +425,6 @@
<string name="theme_dark">Тъмна</string>
<string name="theme_wallpaper">Тапет</string>
<string name="theme_day_night">Ден/Нощ</string>
<string name="master_sync_warning">Автоматичната синхронизация в момента е деактивирана от Android</string>
<string name="settings_general">Общи</string>
<string name="language">Език</string>
<string name="restart_required">Tasks трябва да бъде рестартиран за да влязат промените в сила</string>

@ -293,7 +293,6 @@
<string name="theme_dark">Tmavý</string>
<string name="theme_wallpaper">Tapeta</string>
<string name="theme_day_night">Den/Noc</string>
<string name="master_sync_warning">Automatická synchronizace je právě zakázána Androidem</string>
<string name="settings_general">Všeobecný</string>
<string name="language">Jazyk</string>
<string name="restart_required">Úkoly musí být restartovány, aby se změny projevily</string>

@ -411,7 +411,6 @@
<string name="theme_dark">Dunkel</string>
<string name="theme_wallpaper">Hintergrundbild</string>
<string name="theme_day_night">Tag/Nacht</string>
<string name="master_sync_warning">Automatische Syncronisation ist aktuell im System deaktiviert</string>
<string name="settings_general">Allgemein</string>
<string name="language">Sprache</string>
<string name="restart_required">Tasks muss neugestartet werden um die Änderungen zu übernehmen</string>

@ -420,7 +420,6 @@
<string name="theme_dark">Oscuro</string>
<string name="theme_wallpaper">Fondo de pantalla</string>
<string name="theme_day_night">Día/Noche</string>
<string name="master_sync_warning">La sincronización automática está actualmente deshabilitada por Android</string>
<string name="language">Idioma</string>
<string name="restart_required">Debe reiniciar Tasks para que los cambios tengan efecto</string>
<string name="restart_now">Reiniciar ahora</string>

@ -271,7 +271,6 @@
<string name="theme_grey">خاکستری</string>
<string name="theme_light">روشن</string>
<string name="theme_dark">تیره</string>
<string name="master_sync_warning">درحال‌حاضر هماهنگ‌سازی خودکار توسط اندروید غیرفعال است</string>
<string name="settings_general">عمومی</string>
<string name="language">زبان</string>
<string name="restart_now">اکنون ریست کن</string>

@ -421,7 +421,6 @@
<string name="theme_dark">Tumma</string>
<string name="theme_wallpaper">Taustakuva</string>
<string name="theme_day_night">Päivä/Yö</string>
<string name="master_sync_warning">Android on estänyt automaattisen synkronoinnin</string>
<string name="settings_general">Yleinen</string>
<string name="language">Kieli</string>
<string name="restart_required">Tasks on uudelleen käynnistettävä muutoksien käyttöönottamiseksi</string>

@ -404,7 +404,6 @@
<string name="theme_dark">Noir</string>
<string name="theme_wallpaper">Fond d\'écran</string>
<string name="theme_day_night">Jour/Nuit</string>
<string name="master_sync_warning">La synchronisation automatique est actuellement déactivé par Android</string>
<string name="settings_general">Général</string>
<string name="language">Langage</string>
<string name="restart_required">Tasks doit être redémarrer pour que les changements prennent effets</string>

@ -342,7 +342,6 @@
<string name="theme_dark">Oscuro</string>
<string name="theme_wallpaper">Fondo de pantalla</string>
<string name="theme_day_night">Día/Noche</string>
<string name="master_sync_warning">La sincronización automática está actualmente deshabilitada por Android</string>
<string name="settings_general">Xeneral</string>
<string name="language">Idioma</string>
<string name="restart_required">Debe reiniciar Tasks para que los cambios tengan efecto</string>

@ -425,7 +425,6 @@
<string name="theme_dark">Sötét</string>
<string name="theme_wallpaper">Háttérkép</string>
<string name="theme_day_night">Nappal/Éjszaka</string>
<string name="master_sync_warning">Az Android pillanatnyilag letiltotta az automatikus szinkronizálást.</string>
<string name="settings_general">Általános</string>
<string name="language">Nyelv</string>
<string name="restart_required">A változás érvényesítéséhez újra kell indítani a Tasks appot</string>

@ -422,7 +422,6 @@
<string name="theme_dark">Scuro</string>
<string name="theme_wallpaper">Sfondo</string>
<string name="theme_day_night">Giorno/Notte</string>
<string name="master_sync_warning">La sincronizzazione automatica è disabilitata da Android</string>
<string name="settings_general">Generale</string>
<string name="language">Lingua</string>
<string name="restart_required">Per rendere attive le modifiche l\'attività va riavviata</string>

@ -373,7 +373,6 @@
<string name="theme_dark">כהה</string>
<string name="theme_wallpaper">תמונת רקע</string>
<string name="theme_day_night">יום / לילה</string>
<string name="master_sync_warning">סנכרון אוטומטי כרגע מושבת ע\"י אנדרואיד</string>
<string name="settings_general">כללי</string>
<string name="language">שפה</string>
<string name="restart_required">יש לאתחל את האפליקציה כדי לגרום לשינוי להשפיע</string>

@ -423,7 +423,6 @@
<string name="theme_dark">ダーク</string>
<string name="theme_wallpaper">壁紙</string>
<string name="theme_day_night">デイナイト</string>
<string name="master_sync_warning">自動同期は、現在 Android によって無効にされています</string>
<string name="settings_general">全般</string>
<string name="language">言語</string>
<string name="restart_required">Tasks は、変更を有効にするために再起動する必要があります</string>

@ -426,7 +426,6 @@
<string name="theme_dark">어둡게</string>
<string name="theme_wallpaper">바탕화면</string>
<string name="theme_day_night">주간/야간</string>
<string name="master_sync_warning">현재 자동 동기화는 안드로이드에 의해 사용이 불가합니다. </string>
<string name="settings_general">일반</string>
<string name="language">언어</string>
<string name="restart_required">이 변경을 적용하려면 Tasks 앱을 재시작해야 합니다. </string>

@ -421,7 +421,6 @@
<string name="theme_dark">Tamsi</string>
<string name="theme_wallpaper">Fono vaizdas</string>
<string name="theme_day_night">Diena/Naktis</string>
<string name="master_sync_warning">Automatinis sinchronizavimas šiuo metu yra išjungtas Android sistemos</string>
<string name="settings_general">Pagrindiniai</string>
<string name="language">Kalba</string>
<string name="restart_required">Kad nustatymai įsigaliotų, Tasks turi būti perkrauta</string>

@ -160,7 +160,6 @@
<string name="plugin_description">Tasks er et åpent kildekode-prosjekt vedlikeholdt av en utvikler. Enkelte funksjoner blir tilbudt som en betalt oppgradering i appen for å støtte videre utvikling.</string>
<string name="tasker_description">Kontekstbevisste listevarsler. Krever Tasker eller Locale</string>
<string name="opacity">Ugjennomsiktighet</string>
<string name="master_sync_warning">Automatisk synkronisering er for øyeblikket deaktivert av Android</string>
<string name="language">Språk</string>
<string name="restart_required">Tasks må startes om for at endringene skal ta effekt</string>
<string name="restart_now">Restart nå</string>

@ -413,7 +413,6 @@
<string name="theme_dark">Donker</string>
<string name="theme_wallpaper">Achtergrond</string>
<string name="theme_day_night">Dag/Nacht</string>
<string name="master_sync_warning">Automatische synchronisatie is momenteel uitgezet door Android</string>
<string name="settings_general">Globaal</string>
<string name="language">Taal</string>
<string name="restart_required">Tasks moet opnieuw gestart worden om de nieuwe wijzingen te activeren</string>

@ -399,7 +399,6 @@
<string name="theme_dark">Ciemny</string>
<string name="theme_wallpaper">Tapeta</string>
<string name="theme_day_night">Dzień/noc</string>
<string name="master_sync_warning">Automatyczna synchronizacja jest obecnie wyłączona przez Androida</string>
<string name="settings_general">Podstawowe</string>
<string name="language">Język</string>
<string name="restart_required">Tasks muszą zostać zrestartowane aby zmiany zostały wprowadzone</string>

@ -335,7 +335,6 @@
<string name="theme_dark">Escuro</string>
<string name="theme_wallpaper">Plano de fundo</string>
<string name="theme_day_night">Dia/Noite</string>
<string name="master_sync_warning">Sincronização automática está atualmente desabilitada pelo Android</string>
<string name="settings_general">Geral</string>
<string name="language">Idioma</string>
<string name="restart_required">Você deve reiniciar seu aplicativo para que as alterações sejam aplicadas.</string>

@ -405,7 +405,6 @@
<string name="theme_dark">Escuro</string>
<string name="theme_wallpaper">Fundo</string>
<string name="theme_day_night">Dia/Noite</string>
<string name="master_sync_warning">A sincronização automática está atualmente desabilitada pelo Android</string>
<string name="settings_general">Geral</string>
<string name="language">Linguagem</string>
<string name="restart_required">O Tasks precisa ser reiniciado para que a mudança entre em vigor</string>

@ -423,7 +423,6 @@
<string name="theme_dark">Тёмная</string>
<string name="theme_wallpaper">Как обои</string>
<string name="theme_day_night">День / ночь</string>
<string name="master_sync_warning">Автосинхронизация в Android выключена</string>
<string name="settings_general">Общие</string>
<string name="language">Язык</string>
<string name="restart_required">Для применения изменений программа должна быть перезапущена</string>

@ -422,7 +422,6 @@
<string name="theme_dark">Tmavá</string>
<string name="theme_wallpaper">Tapeta</string>
<string name="theme_day_night">Deň/Noc</string>
<string name="master_sync_warning">Automatická synchronizácia je pozastavená Androidom </string>
<string name="settings_general">Všeobecný</string>
<string name="language">Jazyk</string>
<string name="restart_required">Úlohy treba reštartovať, aby sa zmena prejavila</string>

@ -427,7 +427,6 @@
<string name="theme_dark">Koyu</string>
<string name="theme_wallpaper">Duvar kağıdı</string>
<string name="theme_day_night">Gündüz/Gece</string>
<string name="master_sync_warning">Kendiliğinden eşzamanlama şu anda Android tarafından devre dışı bırakıldı</string>
<string name="settings_general">Genel</string>
<string name="language">Dil</string>
<string name="restart_required">Değişikliklerin etkili olması için Tasks yeniden başlatılmalıdır</string>

@ -344,7 +344,6 @@
<string name="theme_dark">Темний</string>
<string name="theme_wallpaper">Шпалери</string>
<string name="theme_day_night">День/Ніч</string>
<string name="master_sync_warning">Автоматична синхронізація наразі недоступна в Android</string>
<string name="settings_general">Загальні</string>
<string name="language">Мова</string>
<string name="restart_required">Tasks має бути перезавантажено для вступу змін в дію.</string>

@ -422,7 +422,6 @@
<string name="theme_dark">暗色</string>
<string name="theme_wallpaper">壁纸</string>
<string name="theme_day_night">日/夜</string>
<string name="master_sync_warning">自动同步目前被Android禁用</string>
<string name="settings_general">一般</string>
<string name="language">语言</string>
<string name="restart_required">必须重新启动Tasks才能使更改生效</string>

@ -230,7 +230,6 @@
<string name="color">色彩</string>
<string name="accent">強調色</string>
<string name="themes">其他主題</string>
<string name="master_sync_warning">自動同步功能目前在 Android 上是無效的。</string>
<string name="settings_general">一般</string>
<string name="language">語言</string>
<string name="restart_required">Tasks 必須重新啟動以使變更生效</string>

@ -806,7 +806,6 @@ File %1$s contained %2$s.\n\n
<string name="theme_wallpaper">Wallpaper</string>
<string name="theme_day_night">Day/Night</string>
<string name="master_sync_warning">Automatic synchronization is currently disabled by Android</string>
<string name="settings_general">General</string>
<string name="language">Language</string>
<string name="restart_required">Tasks must be restarted for change to take effect</string>

Loading…
Cancel
Save