mirror of
https://github.com/etesync/android
synced 2024-11-22 07:58:09 +00:00
Bring back and adjust the tasks sync manager.
This commit is contained in:
parent
e9b09453f8
commit
23ab80acc0
@ -106,6 +106,19 @@
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_calendars" />
|
||||
</service>
|
||||
<service
|
||||
android:name=".syncadapter.TasksSyncAdapterService"
|
||||
android:exported="true"
|
||||
android:process=":sync"
|
||||
tools:ignore="ExportedService">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_tasks"/>
|
||||
</service>
|
||||
|
||||
<!-- Address book account -->
|
||||
<service
|
||||
|
@ -8,14 +8,20 @@
|
||||
|
||||
package com.etesync.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.etesync.syncadapter.resource.LocalTaskList;
|
||||
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
|
||||
public class PackageChangedReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
@ -31,7 +37,17 @@ public class PackageChangedReceiver extends BroadcastReceiver {
|
||||
App.log.info("Package (un)installed; OpenTasks provider now available = " + tasksInstalled);
|
||||
|
||||
// check all accounts and (de)activate OpenTasks if a CalDAV service is defined
|
||||
// FIXME: Do something if we ever bring back tasks.
|
||||
AccountManager am = AccountManager.get(context);
|
||||
for (Account account : am.getAccountsByType(App.getAccountType())) {
|
||||
if (tasksInstalled) {
|
||||
if (ContentResolver.getIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority) <= 0) {
|
||||
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
|
||||
ContentResolver.setSyncAutomatically(account, TaskProvider.ProviderName.OpenTasks.authority, true);
|
||||
ContentResolver.addPeriodicSync(account, TaskProvider.ProviderName.OpenTasks.authority, new Bundle(), Constants.DEFAULT_SYNC_INTERVAL);
|
||||
}
|
||||
} else {
|
||||
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ public class CollectionInfo implements Serializable {
|
||||
|
||||
public enum Type {
|
||||
ADDRESS_BOOK,
|
||||
CALENDAR
|
||||
CALENDAR,
|
||||
TASK_LIST,
|
||||
}
|
||||
|
||||
// FIXME: Shouldn't be exposed, as it's already saved in the journal. We just expose it for when we save for db.
|
||||
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package com.etesync.syncadapter.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.etesync.syncadapter.AccountSettings;
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.Constants;
|
||||
import com.etesync.syncadapter.NotificationHelper;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.journalmanager.Exceptions;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.model.JournalEntity;
|
||||
import com.etesync.syncadapter.model.JournalModel;
|
||||
import com.etesync.syncadapter.model.ServiceEntity;
|
||||
import com.etesync.syncadapter.resource.LocalTaskList;
|
||||
import com.etesync.syncadapter.ui.DebugInfoActivity;
|
||||
|
||||
import org.dmfs.provider.tasks.TaskContract;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.sql.EntityDataStore;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
|
||||
|
||||
/**
|
||||
* Synchronization manager for CalDAV collections; handles tasks ({@code VTODO}).
|
||||
*/
|
||||
public class TasksSyncAdapterService extends SyncAdapterService {
|
||||
|
||||
@Override
|
||||
protected AbstractThreadedSyncAdapter syncAdapter() {
|
||||
return new SyncAdapter(this);
|
||||
}
|
||||
|
||||
|
||||
private static class SyncAdapter extends SyncAdapterService.SyncAdapter {
|
||||
|
||||
public SyncAdapter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient providerClient, SyncResult syncResult) {
|
||||
super.onPerformSync(account, extras, authority, providerClient, syncResult);
|
||||
|
||||
NotificationHelper notificationManager = new NotificationHelper(getContext(), "journals-tasks", Constants.NOTIFICATION_CALENDAR_SYNC);
|
||||
notificationManager.cancel();
|
||||
|
||||
try {
|
||||
@Cleanup TaskProvider provider = TaskProvider.acquire(getContext().getContentResolver(), TaskProvider.ProviderName.OpenTasks);
|
||||
if (provider == null)
|
||||
throw new CalendarStorageException("Couldn't access OpenTasks provider");
|
||||
|
||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
||||
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
|
||||
return;
|
||||
|
||||
new RefreshCollections(account, CollectionInfo.Type.CALENDAR).run();
|
||||
|
||||
updateLocalTaskLists(provider, account, settings);
|
||||
|
||||
HttpUrl principal = HttpUrl.get(settings.getUri());
|
||||
|
||||
for (LocalTaskList taskList : (LocalTaskList[]) LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, TaskContract.TaskLists.SYNC_ENABLED + "!=0", null)) {
|
||||
App.log.info("Synchronizing task list #" + taskList.getId() + " [" + taskList.getSyncId() + "]");
|
||||
TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, taskList, principal);
|
||||
syncManager.performSync();
|
||||
}
|
||||
} catch (Exceptions.ServiceUnavailableException e) {
|
||||
syncResult.stats.numIoExceptions++;
|
||||
syncResult.delayUntil = (e.retryAfter > 0) ? e.retryAfter : Constants.DEFAULT_RETRY_DELAY;
|
||||
} catch (Exception | OutOfMemoryError e) {
|
||||
if (e instanceof CalendarStorageException || e instanceof SQLiteException) {
|
||||
App.log.log(Level.SEVERE, "Couldn't prepare local calendars", e);
|
||||
syncResult.databaseError = true;
|
||||
}
|
||||
|
||||
int syncPhase = R.string.sync_phase_journals;
|
||||
String title = getContext().getString(R.string.sync_error_calendar, account.name);
|
||||
|
||||
notificationManager.setThrowable(e);
|
||||
|
||||
final Intent detailsIntent = notificationManager.getDetailsIntent();
|
||||
detailsIntent.putExtra(KEY_ACCOUNT, account);
|
||||
if (!(e instanceof Exceptions.UnauthorizedException)) {
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
|
||||
}
|
||||
|
||||
notificationManager.notify(title, getContext().getString(syncPhase));
|
||||
}
|
||||
|
||||
App.log.info("Task sync complete");
|
||||
}
|
||||
|
||||
private void updateLocalTaskLists(TaskProvider provider, Account account, AccountSettings settings) throws CalendarStorageException {
|
||||
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
|
||||
ServiceEntity service = JournalModel.Service.fetch(data, account.name, CollectionInfo.Type.TASK_LIST);
|
||||
|
||||
Map<String, JournalEntity> remote = new HashMap<>();
|
||||
List<JournalEntity> remoteJournals = JournalEntity.getJournals(data, service);
|
||||
for (JournalEntity journalEntity : remoteJournals) {
|
||||
remote.put(journalEntity.getUid(), journalEntity);
|
||||
}
|
||||
|
||||
LocalTaskList[] local = (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null);
|
||||
|
||||
boolean updateColors = settings.getManageCalendarColors();
|
||||
|
||||
// delete obsolete local task lists
|
||||
for (LocalTaskList list : local) {
|
||||
String url = list.getSyncId();
|
||||
if (!remote.containsKey(url)) {
|
||||
App.log.fine("Deleting obsolete local task list" + url);
|
||||
list.delete();
|
||||
} else {
|
||||
// remote CollectionInfo found for this local collection, update data
|
||||
JournalEntity journalEntity = remote.get(url);
|
||||
App.log.fine("Updating local task list " + url + " with " + journalEntity);
|
||||
list.update(journalEntity, updateColors);
|
||||
// we already have a local task list for this remote collection, don't take into consideration anymore
|
||||
remote.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
// create new local task lists
|
||||
for (String url : remote.keySet()) {
|
||||
JournalEntity journalEntity = remote.get(url);
|
||||
App.log.info("Adding local task list " + journalEntity);
|
||||
LocalTaskList.create(account, provider, journalEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package com.etesync.syncadapter.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.etesync.syncadapter.AccountSettings;
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.Constants;
|
||||
import com.etesync.syncadapter.InvalidAccountException;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.journalmanager.Exceptions;
|
||||
import com.etesync.syncadapter.journalmanager.JournalEntryManager;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.model.SyncEntry;
|
||||
import com.etesync.syncadapter.resource.LocalResource;
|
||||
import com.etesync.syncadapter.resource.LocalTask;
|
||||
import com.etesync.syncadapter.resource.LocalTaskList;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.InvalidCalendarException;
|
||||
import at.bitfire.ical4android.Task;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class TasksSyncManager extends SyncManager {
|
||||
final private HttpUrl remote;
|
||||
|
||||
protected static final int MAX_MULTIGET = 30;
|
||||
|
||||
final protected TaskProvider provider;
|
||||
|
||||
|
||||
public TasksSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, TaskProvider provider, SyncResult result, LocalTaskList taskList, HttpUrl remote) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException {
|
||||
super(context, account, settings, extras, authority, result, taskList.getName(), CollectionInfo.Type.TASK_LIST, account.name);
|
||||
this.provider = provider;
|
||||
localCollection = taskList;
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int notificationId() {
|
||||
return Constants.NOTIFICATION_TASK_SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSyncErrorTitle() {
|
||||
return context.getString(R.string.sync_error_tasks, account.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSyncSuccessfullyTitle() {
|
||||
return context.getString(R.string.sync_successfully_tasks, info.displayName,
|
||||
account.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepare() throws CalendarStorageException, ContactsStorageException {
|
||||
if (!super.prepare())
|
||||
return false;
|
||||
|
||||
journal = new JournalEntryManager(httpClient, remote, localTaskList().getName());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareDirty() throws CalendarStorageException, ContactsStorageException {
|
||||
super.prepareDirty();
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private LocalTaskList localTaskList() { return ((LocalTaskList)localCollection); }
|
||||
|
||||
protected void processSyncEntry(SyncEntry cEntry) throws IOException, ContactsStorageException, CalendarStorageException, InvalidCalendarException {
|
||||
InputStream is = new ByteArrayInputStream(cEntry.getContent().getBytes(Charsets.UTF_8));
|
||||
|
||||
Task[] tasks = Task.fromStream(is, Charsets.UTF_8);
|
||||
if (tasks.length == 0) {
|
||||
App.log.warning("Received VCard without data, ignoring");
|
||||
return;
|
||||
} else if (tasks.length > 1)
|
||||
App.log.warning("Received multiple VCALs, using first one");
|
||||
|
||||
Task task = tasks[0];
|
||||
LocalTask local = (LocalTask) localCollection.getByUid(task.uid);
|
||||
|
||||
if (cEntry.isAction(SyncEntry.Actions.ADD) || cEntry.isAction(SyncEntry.Actions.CHANGE)) {
|
||||
processTask(task, local);
|
||||
} else {
|
||||
App.log.info("Removing local record #" + local.getId() + " which has been deleted on the server");
|
||||
local.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private LocalResource processTask(final Task newData, LocalTask localTask) throws IOException, ContactsStorageException, CalendarStorageException {
|
||||
// delete local event, if it exists
|
||||
if (localTask != null) {
|
||||
App.log.info("Updating " + newData.uid + " in local calendar");
|
||||
localTask.setETag(newData.uid);
|
||||
localTask.update(newData);
|
||||
syncResult.stats.numUpdates++;
|
||||
} else {
|
||||
App.log.info("Adding " + newData.uid + " to local calendar");
|
||||
localTask = new LocalTask(localTaskList(), newData, newData.uid, newData.uid);
|
||||
localTask.add();
|
||||
syncResult.stats.numInserts++;
|
||||
}
|
||||
|
||||
return localTask;
|
||||
}
|
||||
}
|
@ -34,6 +34,8 @@ import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.ui.setup.LoginCredentials;
|
||||
import com.etesync.syncadapter.ui.setup.LoginCredentialsChangeFragment;
|
||||
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
|
||||
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
|
||||
|
||||
public class AccountSettingsActivity extends BaseActivity {
|
||||
@ -150,6 +152,27 @@ public class AccountSettingsActivity extends BaseActivity {
|
||||
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available);
|
||||
}
|
||||
|
||||
final ListPreference prefSyncTasks = (ListPreference)findPreference("sync_interval_tasks");
|
||||
final Long syncIntervalTasks = settings.getSyncInterval(TaskProvider.ProviderName.OpenTasks.authority);
|
||||
if (syncIntervalTasks != null) {
|
||||
prefSyncTasks.setValue(syncIntervalTasks.toString());
|
||||
if (syncIntervalTasks == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
||||
prefSyncTasks.setSummary(R.string.settings_sync_summary_manually);
|
||||
else
|
||||
prefSyncTasks.setSummary(getString(R.string.settings_sync_summary_periodically, prefSyncTasks.getEntry()));
|
||||
prefSyncTasks.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String)newValue));
|
||||
getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
prefSyncTasks.setEnabled(false);
|
||||
prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available);
|
||||
}
|
||||
|
||||
final SwitchPreferenceCompat prefWifiOnly = (SwitchPreferenceCompat)findPreference("sync_wifi_only");
|
||||
prefWifiOnly.setChecked(settings.getSyncWifiOnly());
|
||||
prefWifiOnly.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
|
@ -224,6 +224,7 @@
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">%s and immediately on local changes</string>
|
||||
<string name="settings_sync_summary_not_available">Not available</string>
|
||||
<string name="settings_sync_interval_calendars">Calendars sync. interval</string>
|
||||
<string name="settings_sync_interval_tasks">Tasks sync. interval</string>
|
||||
<string-array name="settings_sync_interval_seconds" translateable="false">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
@ -291,6 +292,7 @@
|
||||
<string name="sync_error_permissions_text">Additional permissions required</string>
|
||||
<string name="sync_error_calendar">Calendar sync failed (%s)</string>
|
||||
<string name="sync_error_contacts">Contacts sync failed (%s)</string>
|
||||
<string name="sync_error_tasks">Tasks sync failed (%s)</string>
|
||||
<string name="sync_error">Error while %s</string>
|
||||
<string name="sync_error_integrity">Integrity error while %s</string>
|
||||
<string name="sync_error_http_dav">Server error while %s</string>
|
||||
@ -308,6 +310,7 @@
|
||||
<string name="sync_phase_post_processing">post processing</string>
|
||||
<string name="sync_error_unauthorized">Authentication failed</string>
|
||||
<string name="sync_error_user_inactive">User is inactive</string>
|
||||
<string name="sync_successfully_tasks" formatted="false">Task list \"%s\" modified (%s)</string>
|
||||
<string name="sync_successfully_calendar" formatted="false">Calendar \"%s\" modified (%s)</string>
|
||||
<string name="sync_successfully_contacts" formatted="false">Contacts modified (%s)</string>
|
||||
<string name="sync_successfully_modified" formatted="false">%s modified.</string>
|
||||
|
@ -50,6 +50,13 @@
|
||||
android:entries="@array/settings_sync_interval_names"
|
||||
android:entryValues="@array/settings_sync_interval_seconds" />
|
||||
|
||||
<ListPreference
|
||||
android:key="sync_interval_tasks"
|
||||
android:persistent="false"
|
||||
android:title="@string/settings_sync_interval_tasks"
|
||||
android:entries="@array/settings_sync_interval_names"
|
||||
android:entryValues="@array/settings_sync_interval_seconds" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="sync_wifi_only"
|
||||
android:persistent="false"
|
||||
|
15
app/src/main/res/xml/sync_tasks.xml
Normal file
15
app/src/main/res/xml/sync_tasks.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountType="com.etesync.syncadapter"
|
||||
android:contentAuthority="org.dmfs.tasks"
|
||||
android:allowParallelSyncs="true"
|
||||
android:supportsUploading="true"
|
||||
android:isAlwaysSyncable="true"
|
||||
android:userVisible="true" />
|
Loading…
Reference in New Issue
Block a user