From 589f81c50d47804c5685217a9aee9f3c792d2194 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Fri, 21 Apr 2017 15:59:22 +0100 Subject: [PATCH] Add multiple address books support Android allows only having one address book per account, so until now users of EteSync were only able to have one address book. This was always an annoying limitation, but even more so now that journal sharing is implemented. Luckily, DAVdroid recently implemented multiple account support by creating sub-accounts for address books. This patch is an import of the DAVdroid changes, with adjustments to work with EteSync, and a few changes that did not make sense for EteSync. The original commits' split didn't provide any value over this squash, and the amount of adjustments and addition needed to be done to apply them, made me decide to squash this change together. This commit is mostly based on: dfec72ce6b8ff5e0780e9ac4418c81d080f4b60b 9817594da14ad8dffae18de386e14aeaf41312b9 --- app/src/main/AndroidManifest.xml | 61 +++++-- .../etesync/syncadapter/AccountSettings.java | 91 +++++++++- .../syncadapter/AccountUpdateService.java | 26 ++- .../java/com/etesync/syncadapter/App.java | 20 ++- .../com/etesync/syncadapter/HttpClient.java | 8 +- .../resource/LocalAddressBook.java | 148 ++++++++++++---- .../syncadapter/AddressBookProvider.java | 53 ++++++ .../AddressBooksSyncAdapterService.java | 161 ++++++++++++++++++ .../syncadapter/CalendarSyncManager.java | 2 +- .../ContactsSyncAdapterService.java | 31 ++-- .../syncadapter/ContactsSyncManager.java | 11 +- .../syncadapter/NullAuthenticatorService.java | 83 +++++++++ .../syncadapter/SyncAdapterService.java | 6 +- .../syncadapter/syncadapter/SyncManager.java | 6 +- .../syncadapter/ui/AccountActivity.java | 16 +- .../ui/AccountSettingsActivity.java | 4 +- .../syncadapter/ui/AddMemberFragment.java | 2 +- .../ui/CollectionMembersListFragment.java | 2 +- .../ui/CreateCollectionFragment.java | 4 +- .../ui/DeleteCollectionFragment.java | 2 +- .../syncadapter/ui/RemoveMemberFragment.java | 2 +- .../ui/ViewCollectionActivity.java | 2 +- .../ui/importlocal/ImportFragment.java | 2 +- .../LocalContactImportFragment.java | 4 +- .../ui/setup/SetupEncryptionFragment.java | 4 +- .../ui/setup/SetupUserInfoFragment.java | 2 +- app/src/main/res/values/strings.xml | 5 + .../account_authenticator_address_book.xml | 14 ++ app/src/main/res/xml/sync_address_books.xml | 13 ++ app/src/main/res/xml/sync_contacts.xml | 6 +- ical4android | 2 +- 31 files changed, 668 insertions(+), 125 deletions(-) create mode 100644 app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBookProvider.java create mode 100644 app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.java create mode 100644 app/src/main/java/com/etesync/syncadapter/syncadapter/NullAuthenticatorService.java create mode 100644 app/src/main/res/xml/account_authenticator_address_book.xml create mode 100644 app/src/main/res/xml/sync_address_books.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e7c9dc60..130c25e0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -91,6 +91,54 @@ android:name="android.accounts.AccountAuthenticator" android:resource="@xml/account_authenticator"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - sqlAccountNames = new LinkedList<>(); + List accountNames = new LinkedList<>(); AccountManager am = AccountManager.get(this); - for (Account account : am.getAccountsByType(Constants.ACCOUNT_TYPE)) - sqlAccountNames.add(account.name); + for (Account account : am.getAccountsByType(App.getAccountType())) { + accountNames.add(account.name); + } EntityDataStore data = ((App) getApplication()).getData(); - if (sqlAccountNames.isEmpty()) { + // delete orphaned address book accounts + for (Account addrBookAccount : am.getAccountsByType(App.getAddressBookAccountType())) { + LocalAddressBook addressBook = new LocalAddressBook(this, addrBookAccount, null); + try { + if (!accountNames.contains(addressBook.getMainAccount().name)) + addressBook.delete(); + } catch(ContactsStorageException e) { + App.log.log(Level.SEVERE, "Couldn't get address book main account", e); + } + } + + + if (accountNames.isEmpty()) { data.delete(ServiceEntity.class).get().value(); } else { - data.delete(ServiceEntity.class).where(ServiceEntity.ACCOUNT.notIn(sqlAccountNames)).get().value(); + data.delete(ServiceEntity.class).where(ServiceEntity.ACCOUNT.notIn(accountNames)).get().value(); } } } diff --git a/app/src/main/java/com/etesync/syncadapter/App.java b/app/src/main/java/com/etesync/syncadapter/App.java index 7b95c3c3..3d759535 100644 --- a/app/src/main/java/com/etesync/syncadapter/App.java +++ b/app/src/main/java/com/etesync/syncadapter/App.java @@ -107,6 +107,13 @@ public class App extends Application { at.bitfire.cert4android.Constants.log = Logger.getLogger("syncadapter.cert4android"); } + @Getter + private static String accountType; + @Getter + private static String addressBookAccountType; + @Getter + private static String addressBooksAuthority; + @Override @SuppressLint("HardwareIds") public void onCreate() { @@ -117,6 +124,10 @@ public class App extends Application { initPrefVersion(); uidGenerator = new UidGenerator(null, android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID)); + + accountType = getString(R.string.account_type); + addressBookAccountType = getString(R.string.account_type_address_book); + addressBooksAuthority = getString(R.string.address_books_authority); } public void reinitCertManager() { @@ -301,16 +312,21 @@ public class App extends Application { AccountManager am = AccountManager.get(this); for (Account account : am.getAccountsByType(Constants.ACCOUNT_TYPE)) { try { + // Generate account settings to make sure account is migrated. + new AccountSettings(this, account); + LocalCalendar calendars[] = (LocalCalendar[]) LocalCalendar.find(account, this.getContentResolver().acquireContentProviderClient(CalendarContract.CONTENT_URI), LocalCalendar.Factory.INSTANCE, null, null); for (LocalCalendar calendar : calendars) { calendar.fixEtags(); } - } catch (CalendarStorageException e) { + } catch (CalendarStorageException|InvalidAccountException e) { e.printStackTrace(); } + } - LocalAddressBook addressBook = new LocalAddressBook(account, this.getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI)); + for (Account account : am.getAccountsByType(App.getAddressBookAccountType())) { + LocalAddressBook addressBook = new LocalAddressBook(this, account, this.getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI)); try { addressBook.fixEtags(); } catch (ContactsStorageException e) { diff --git a/app/src/main/java/com/etesync/syncadapter/HttpClient.java b/app/src/main/java/com/etesync/syncadapter/HttpClient.java index 5fd080a8..d6454974 100644 --- a/app/src/main/java/com/etesync/syncadapter/HttpClient.java +++ b/app/src/main/java/com/etesync/syncadapter/HttpClient.java @@ -56,9 +56,7 @@ public class HttpClient { return builder.build(); } - public static OkHttpClient create(@Nullable Context context, @NonNull Account account, @NonNull final Logger logger) throws InvalidAccountException { - // use account settings for authentication - AccountSettings settings = new AccountSettings(context, account); + public static OkHttpClient create(@Nullable Context context, @NonNull AccountSettings settings, @NonNull final Logger logger) throws InvalidAccountException { return create(context, logger, Constants.serviceUrl.getHost(), settings.getAuthToken()); } @@ -66,8 +64,8 @@ public class HttpClient { return defaultBuilder(context, logger).build(); } - public static OkHttpClient create(@NonNull Context context, @NonNull Account account) throws InvalidAccountException { - return create(context, account, App.log); + public static OkHttpClient create(@NonNull Context context, @NonNull AccountSettings settings) throws InvalidAccountException { + return create(context, settings, App.log); } public static OkHttpClient create(@Nullable Context context) { diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.java b/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.java index d6b8e8ca..b0aaf092 100644 --- a/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.java +++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.java @@ -8,25 +8,35 @@ package com.etesync.syncadapter.resource; import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; +import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Parcel; import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.RawContacts; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.os.OperationCanceledException; import com.etesync.syncadapter.App; +import com.etesync.syncadapter.model.CollectionInfo; +import com.etesync.syncadapter.utils.Base64; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; @@ -45,9 +55,11 @@ import lombok.Cleanup; public class LocalAddressBook extends AndroidAddressBook implements LocalCollection { protected static final String - SYNC_STATE_CTAG = "ctag", - SYNC_STATE_URL = "url"; + USER_DATA_MAIN_ACCOUNT_TYPE = "real_account_type", + USER_DATA_MAIN_ACCOUNT_NAME = "real_account_name", + USER_DATA_URL = "url"; + protected final Context context; private final Bundle syncState = new Bundle(); /** @@ -58,8 +70,70 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect public boolean includeGroups = true; - public LocalAddressBook(Account account, ContentProviderClient provider) { + public static LocalAddressBook[] find(@NonNull Context context, @NonNull ContentProviderClient provider, @Nullable Account mainAccount) throws ContactsStorageException { + AccountManager accountManager = AccountManager.get(context); + + List result = new LinkedList<>(); + for (Account account : accountManager.getAccountsByType(App.getAddressBookAccountType())) { + LocalAddressBook addressBook = new LocalAddressBook(context, account, provider); + if (mainAccount == null || addressBook.getMainAccount().equals(mainAccount)) + result.add(addressBook); + } + + return result.toArray(new LocalAddressBook[result.size()]); + } + + public static LocalAddressBook create(@NonNull Context context, @NonNull ContentProviderClient provider, @NonNull Account mainAccount, @NonNull CollectionInfo info) throws ContactsStorageException { + AccountManager accountManager = AccountManager.get(context); + + Account account = new Account(accountName(mainAccount, info), App.getAddressBookAccountType()); + if (!accountManager.addAccountExplicitly(account, null, initialUserData(mainAccount, info.uid))) + throw new ContactsStorageException("Couldn't create address book account"); + + LocalAddressBook addressBook = new LocalAddressBook(context, account, provider); + addressBook.setMainAccount(mainAccount); + addressBook.setURL(info.uid); + + ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); + + return addressBook; + } + + public void update(@NonNull CollectionInfo info) throws AuthenticatorException, OperationCanceledException, IOException, ContactsStorageException, android.accounts.OperationCanceledException { + final String newAccountName = accountName(getMainAccount(), info); + if (!account.name.equals(newAccountName) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + final AccountManager accountManager = AccountManager.get(context); + AccountManagerFuture future = accountManager.renameAccount(account, newAccountName, new AccountManagerCallback() { + @Override + public void run(AccountManagerFuture future) { + try { + // update raw contacts to new account name + if (provider != null) { + ContentValues values = new ContentValues(1); + values.put(RawContacts.ACCOUNT_NAME, newAccountName); + provider.update(syncAdapterURI(RawContacts.CONTENT_URI), values, RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?", + new String[] { account.name, account.type }); + } + } catch(RemoteException e) { + App.log.log(Level.WARNING, "Couldn't re-assign contacts to new account name", e); + } + } + }, null); + account = future.getResult(); + } + + // make sure it will still be synchronized when contacts are updated + ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); + } + + public void delete() { + AccountManager accountManager = AccountManager.get(context); + accountManager.removeAccount(account, null, null); + } + + public LocalAddressBook(Context context, Account account, ContentProviderClient provider) { super(account, provider, LocalGroup.Factory.INSTANCE, LocalContact.Factory.INSTANCE); + this.context = context; } @NonNull @@ -268,51 +342,53 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect } - // SYNC STATE + // SETTINGS - @SuppressWarnings("ParcelClassLoader,Recycle") - protected void readSyncState() throws ContactsStorageException { - @Cleanup("recycle") Parcel parcel = Parcel.obtain(); - byte[] raw = getSyncState(); - syncState.clear(); - if (raw != null) { - parcel.unmarshall(raw, 0, raw.length); - parcel.setDataPosition(0); - syncState.putAll(parcel.readBundle()); - } + public static Bundle initialUserData(@NonNull Account mainAccount, @NonNull String url) { + Bundle bundle = new Bundle(3); + bundle.putString(USER_DATA_MAIN_ACCOUNT_NAME, mainAccount.name); + bundle.putString(USER_DATA_MAIN_ACCOUNT_TYPE, mainAccount.type); + bundle.putString(USER_DATA_URL, url); + return bundle; } - @SuppressWarnings("Recycle") - protected void writeSyncState() throws ContactsStorageException { - @Cleanup("recycle") Parcel parcel = Parcel.obtain(); - parcel.writeBundle(syncState); - setSyncState(parcel.marshall()); + public Account getMainAccount() throws ContactsStorageException { + AccountManager accountManager = AccountManager.get(context); + String name = accountManager.getUserData(account, USER_DATA_MAIN_ACCOUNT_NAME), + type = accountManager.getUserData(account, USER_DATA_MAIN_ACCOUNT_TYPE); + if (name != null && type != null) + return new Account(name, type); + else + throw new ContactsStorageException("Address book doesn't exist anymore"); + } + + public void setMainAccount(@NonNull Account mainAccount) throws ContactsStorageException { + AccountManager accountManager = AccountManager.get(context); + accountManager.setUserData(account, USER_DATA_MAIN_ACCOUNT_NAME, mainAccount.name); + accountManager.setUserData(account, USER_DATA_MAIN_ACCOUNT_TYPE, mainAccount.type); } public String getURL() throws ContactsStorageException { - synchronized (syncState) { - readSyncState(); - return syncState.getString(SYNC_STATE_URL); - } + AccountManager accountManager = AccountManager.get(context); + return accountManager.getUserData(account, USER_DATA_URL); } public void setURL(String url) throws ContactsStorageException { - synchronized (syncState) { - readSyncState(); - syncState.putString(SYNC_STATE_URL, url); - writeSyncState(); - } + AccountManager accountManager = AccountManager.get(context); + accountManager.setUserData(account, USER_DATA_URL, url); } // HELPERS - public static void onRenameAccount(@NonNull ContentResolver resolver, @NonNull String oldName, @NonNull String newName) throws RemoteException { - @Cleanup("release") ContentProviderClient client = resolver.acquireContentProviderClient(ContactsContract.AUTHORITY); - if (client != null) { - ContentValues values = new ContentValues(1); - values.put(RawContacts.ACCOUNT_NAME, newName); - client.update(RawContacts.CONTENT_URI, values, RawContacts.ACCOUNT_NAME + "=?", new String[]{oldName}); - } + public static String accountName(@NonNull Account mainAccount, @NonNull CollectionInfo info) { + String displayName = (info.displayName != null) ? info.displayName : info.uid; + StringBuilder sb = new StringBuilder(displayName); + sb .append(" (") + .append(mainAccount.name) + .append(" ") + .append(info.uid.substring(0, 4)) + .append(")"); + return sb.toString(); } /** Fix all of the etags of all of the non-dirty contacts to be non-null. diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBookProvider.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBookProvider.java new file mode 100644 index 00000000..14bdf90d --- /dev/null +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBookProvider.java @@ -0,0 +1,53 @@ +/* + * Copyright © 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.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class AddressBookProvider extends ContentProvider { + + @Override + public boolean onCreate() { + return false; + } + + @Nullable + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { + return null; + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + return null; + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return null; + } + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } + + @Override + public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } + +} diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.java new file mode 100644 index 00000000..ef5ed8ea --- /dev/null +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.java @@ -0,0 +1,161 @@ +/* + * 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.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +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 android.provider.ContactsContract; + +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.LocalAddressBook; +import com.etesync.syncadapter.ui.DebugInfoActivity; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +import at.bitfire.vcard4android.ContactsStorageException; +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; +import lombok.Cleanup; + +import static com.etesync.syncadapter.Constants.KEY_ACCOUNT; + +public class AddressBooksSyncAdapterService extends SyncAdapterService { + + @Override + protected AbstractThreadedSyncAdapter syncAdapter() { + return new AddressBooksSyncAdapter(this); + } + + + private static class AddressBooksSyncAdapter extends SyncAdapter { + + public AddressBooksSyncAdapter(Context context) { + super(context); + } + + @Override + public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { + super.onPerformSync(account, extras, authority, provider, syncResult); + + NotificationHelper notificationManager = new NotificationHelper(getContext(), "journals-contacts", Constants.NOTIFICATION_CONTACTS_SYNC); + notificationManager.cancel(); + + try { + @Cleanup("release") ContentProviderClient contactsProvider = getContext().getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY); + if (contactsProvider == null) { + App.log.severe("Couldn't access contacts provider"); + syncResult.databaseError = true; + return; + } + + AccountSettings settings = new AccountSettings(getContext(), account); + if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) + return; + + new RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run(); + + updateLocalAddressBooks(contactsProvider, account); + + AccountManager accountManager = AccountManager.get(getContext()); + for (Account addressBookAccount : accountManager.getAccountsByType(getContext().getString(R.string.account_type_address_book))) { + App.log.log(Level.INFO, "Running sync for address book", addressBookAccount); + Bundle syncExtras = new Bundle(extras); + syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); + syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); + ContentResolver.requestSync(addressBookAccount, ContactsContract.AUTHORITY, syncExtras); + } + } 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 ContactsStorageException || e instanceof SQLiteException) { + App.log.log(Level.SEVERE, "Couldn't prepare local address books", e); + syncResult.databaseError = true; + } + + int syncPhase = R.string.sync_phase_journals; + String title = getContext().getString(R.string.sync_error_contacts, 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("Address book sync complete"); + } + + + private void updateLocalAddressBooks(ContentProviderClient provider, Account account) throws ContactsStorageException, AuthenticatorException, OperationCanceledException, IOException { + final Context context = getContext(); + EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); + ServiceEntity service = JournalModel.Service.fetch(data, account.name, CollectionInfo.Type.ADDRESS_BOOK); + + Map remote = new HashMap<>(); + List remoteJournals = JournalEntity.getJournals(data, service); + for (JournalEntity journalEntity : remoteJournals) { + remote.put(journalEntity.getUid(), journalEntity); + } + + LocalAddressBook[] local = LocalAddressBook.find(context, provider, account); + + // delete obsolete local address books + for (LocalAddressBook addressBook : local) { + String url = addressBook.getURL(); + if (!remote.containsKey(url)) { + App.log.fine("Deleting obsolete local address book " + url); + addressBook.delete(); + } else { + // remote CollectionInfo found for this local collection, update data + JournalEntity journalEntity = remote.get(url); + App.log.fine("Updating local address book " + url + " with " + journalEntity); + addressBook.update(journalEntity); + // we already have a local collection for this remote collection, don't take into consideration anymore + remote.remove(url); + } + } + + // create new local address books + for (String url : remote.keySet()) { + JournalEntity journalEntity = remote.get(url); + App.log.info("Adding local address book " + journalEntity); + LocalAddressBook.create(context, provider, account, journalEntity); + } + } + } + +} diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java index de871b88..58712d84 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java @@ -46,7 +46,7 @@ public class CalendarSyncManager extends SyncManager { final private HttpUrl remote; public CalendarSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult result, LocalCalendar calendar, HttpUrl remote) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException { - super(context, account, settings, extras, authority, result, calendar.getName(), CollectionInfo.Type.CALENDAR); + super(context, account, settings, extras, authority, result, calendar.getName(), CollectionInfo.Type.CALENDAR, account.name); localCollection = calendar; this.remote = remote; } diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java index 9c63c8a2..9ce8dc4e 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java @@ -28,6 +28,7 @@ import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.JournalModel; import com.etesync.syncadapter.model.ServiceDB; import com.etesync.syncadapter.model.ServiceEntity; +import com.etesync.syncadapter.resource.LocalAddressBook; import com.etesync.syncadapter.ui.DebugInfoActivity; import java.util.logging.Level; @@ -59,30 +60,18 @@ public class ContactsSyncAdapterService extends SyncAdapterService { notificationManager.cancel(); try { - AccountSettings settings = new AccountSettings(getContext(), account); + LocalAddressBook addressBook = new LocalAddressBook(getContext(), account, provider); + + AccountSettings settings = new AccountSettings(getContext(), addressBook.getMainAccount()); if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) return; - new RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run(); + App.log.info("Synchronizing address book: " + addressBook.getURL()); + App.log.info("Taking settings from: " + addressBook.getMainAccount()); - EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); - - ServiceEntity service = JournalModel.Service.fetch(data, account.name, CollectionInfo.Type.ADDRESS_BOOK); - - if (service != null) { - HttpUrl principal = HttpUrl.get(settings.getUri()); - CollectionInfo info = JournalEntity.getCollections(data, service).get(0); - try { - ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, principal, info); - syncManager.performSync(); - } catch (InvalidAccountException e) { - App.log.log(Level.SEVERE, "Couldn't get account settings", e); - } - } else - App.log.info("No CardDAV service found in DB"); - } catch (Exceptions.ServiceUnavailableException e) { - syncResult.stats.numIoExceptions++; - syncResult.delayUntil = (e.retryAfter > 0) ? e.retryAfter : Constants.DEFAULT_RETRY_DELAY; + HttpUrl principal = HttpUrl.get(settings.getUri()); + ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, addressBook, principal); + syncManager.performSync(); } catch (Exception | OutOfMemoryError e) { int syncPhase = R.string.sync_phase_journals; String title = getContext().getString(R.string.sync_error_contacts, account.name); @@ -98,7 +87,7 @@ public class ContactsSyncAdapterService extends SyncAdapterService { notificationManager.notify(title, getContext().getString(syncPhase)); } - App.log.info("Address book sync complete"); + App.log.info("Contacts sync complete"); } } diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.java index 2b6be832..57ad8e9b 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.java @@ -60,10 +60,12 @@ public class ContactsSyncManager extends SyncManager { final private ContentProviderClient provider; final private HttpUrl remote; - public ContactsSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, ContentProviderClient provider, SyncResult result, HttpUrl principal, CollectionInfo info) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException { - super(context, account, settings, extras, authority, result, info.uid, CollectionInfo.Type.ADDRESS_BOOK); + public ContactsSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, ContentProviderClient provider, SyncResult result, LocalAddressBook localAddressBook, HttpUrl principal) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException, ContactsStorageException { + super(context, account, settings, extras, authority, result, localAddressBook.getURL(), CollectionInfo.Type.ADDRESS_BOOK, localAddressBook.getMainAccount().name); this.provider = provider; this.remote = principal; + + localCollection = localAddressBook; } @Override @@ -80,10 +82,7 @@ public class ContactsSyncManager extends SyncManager { protected boolean prepare() throws ContactsStorageException, CalendarStorageException { if (!super.prepare()) return false; - // prepare local address book - localCollection = new LocalAddressBook(account, provider); LocalAddressBook localAddressBook = localAddressBook(); - localAddressBook.setURL(info.uid); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // workaround for Android 7 which sets DIRTY flag when only meta-data is changed @@ -101,7 +100,7 @@ public class ContactsSyncManager extends SyncManager { values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1); localAddressBook.updateSettings(values); - journal = new JournalEntryManager(httpClient, remote, info.uid); + journal = new JournalEntryManager(httpClient, remote, localAddressBook.getURL()); return true; } diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/NullAuthenticatorService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/NullAuthenticatorService.java new file mode 100644 index 00000000..e85852cc --- /dev/null +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/NullAuthenticatorService.java @@ -0,0 +1,83 @@ +/* + * Copyright © 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.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.NetworkErrorException; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +public class NullAuthenticatorService extends Service { + + private AccountAuthenticator accountAuthenticator; + + @Override + public void onCreate() { + accountAuthenticator = new NullAuthenticatorService.AccountAuthenticator(this); + } + + @Override + public IBinder onBind(Intent intent) { + if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) + return accountAuthenticator.getIBinder(); + return null; + } + + + private static class AccountAuthenticator extends AbstractAccountAuthenticator { + final Context context; + + public AccountAuthenticator(Context context) { + super(context); + this.context = context; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { + return null; + } + + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { + return null; + } + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { + return null; + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { + return null; + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + return null; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { + return null; + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java index 06946fc0..9b5a92f3 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java @@ -76,7 +76,7 @@ public abstract class SyncAdapterService extends Service { @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { - App.log.log(Level.INFO, "Sync for " + authority + " has been initiated.", extras.keySet().toArray()); + App.log.log(Level.INFO, authority + " sync of " + account + " has been initiated.", extras.keySet().toArray()); // required for dav4android (ServiceLoader) Thread.currentThread().setContextClassLoader(getContext().getClassLoader()); @@ -143,9 +143,9 @@ public abstract class SyncAdapterService extends Service { void run() throws Exceptions.HttpException, Exceptions.IntegrityException, InvalidAccountException, Exceptions.GenericCryptoException { App.log.info("Refreshing " + serviceType + " collections of service #" + serviceType.toString()); - OkHttpClient httpClient = HttpClient.create(context, account); - AccountSettings settings = new AccountSettings(context, account); + OkHttpClient httpClient = HttpClient.create(context, settings); + JournalManager journalsManager = new JournalManager(httpClient, HttpUrl.get(settings.getUri())); List> journals = new LinkedList<>(); diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java index 45531db8..25067e1f 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java @@ -97,7 +97,7 @@ abstract public class SyncManager { private List localDeleted; private LocalResource[] localDirty; - public SyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult syncResult, String journalUid, CollectionInfo.Type serviceType) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException { + public SyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult syncResult, String journalUid, CollectionInfo.Type serviceType, String accountName) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException { this.context = context; this.account = account; this.settings = settings; @@ -107,10 +107,10 @@ abstract public class SyncManager { this.serviceType = serviceType; // create HttpClient with given logger - httpClient = HttpClient.create(context, account); + httpClient = HttpClient.create(context, settings); data = ((App) context.getApplicationContext()).getData(); - ServiceEntity serviceEntity = JournalModel.Service.fetch(data, account.name, serviceType); + ServiceEntity serviceEntity = JournalModel.Service.fetch(data, accountName, serviceType); info = JournalEntity.fetch(data, serviceEntity, journalUid).getInfo(); // dismiss previous error notifications diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java index ad091144..7c1ef284 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java @@ -58,6 +58,7 @@ import com.etesync.syncadapter.journalmanager.Crypto; import com.etesync.syncadapter.model.CollectionInfo; import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.ServiceEntity; +import com.etesync.syncadapter.resource.LocalAddressBook; import com.etesync.syncadapter.resource.LocalCalendar; import com.etesync.syncadapter.ui.setup.SetupUserInfoFragment; import com.etesync.syncadapter.utils.HintManager; @@ -69,6 +70,7 @@ import java.util.logging.Level; import at.bitfire.cert4android.CustomCertManager; import at.bitfire.ical4android.TaskProvider; +import at.bitfire.vcard4android.ContactsStorageException; import io.requery.Persistable; import io.requery.sql.EntityDataStore; import tourguide.tourguide.ToolTip; @@ -357,8 +359,18 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu if (service.equals(CollectionInfo.Type.ADDRESS_BOOK)) { info.carddav = new AccountInfo.ServiceInfo(); info.carddav.id = id; - info.carddav.refreshing = (davService != null && davService.isRefreshing(id)) || ContentResolver.isSyncActive(account, ContactsContract.AUTHORITY); + info.carddav.refreshing = (davService != null && davService.isRefreshing(id)) || ContentResolver.isSyncActive(account, App.getAddressBooksAuthority()); info.carddav.journals = JournalEntity.getJournals(data, serviceEntity); + + AccountManager accountManager = AccountManager.get(getContext()); + for (Account addrBookAccount : accountManager.getAccountsByType(App.getAddressBookAccountType())) { + LocalAddressBook addressBook = new LocalAddressBook(getContext(), addrBookAccount, null); + try { + if (account.equals(addressBook.getMainAccount())) + info.carddav.refreshing |= ContentResolver.isSyncActive(addrBookAccount, ContactsContract.AUTHORITY); + } catch(ContactsStorageException e) { + } + } } else if (service.equals(CollectionInfo.Type.CALENDAR)) { info.caldav = new AccountInfo.ServiceInfo(); info.caldav.id = id; @@ -457,7 +469,7 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu protected static void requestSync(Account account) { String authorities[] = { - ContactsContract.AUTHORITY, + App.getAddressBooksAuthority(), CalendarContract.AUTHORITY, TaskProvider.ProviderName.OpenTasks.authority }; diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.java index d60377ad..ddc6e6f3 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.java @@ -111,7 +111,7 @@ public class AccountSettingsActivity extends AppCompatActivity { // category: synchronization final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts"); - final Long syncIntervalContacts = settings.getSyncInterval(ContactsContract.AUTHORITY); + final Long syncIntervalContacts = settings.getSyncInterval(App.getAddressBooksAuthority()); if (syncIntervalContacts != null) { prefSyncContacts.setValue(syncIntervalContacts.toString()); if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY) @@ -121,7 +121,7 @@ public class AccountSettingsActivity extends AppCompatActivity { prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String)newValue)); + settings.setSyncInterval(App.getAddressBooksAuthority(), Long.parseLong((String)newValue)); getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this); return false; } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.java index dae2ba6c..d3bbe91d 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.java @@ -55,7 +55,7 @@ public class AddMemberFragment extends DialogFragment { memberEmail = getArguments().getString(KEY_MEMBER); try { settings = new AccountSettings(getContext(), account); - httpClient = HttpClient.create(getContext(), account); + httpClient = HttpClient.create(getContext(), settings); } catch (InvalidAccountException e) { e.printStackTrace(); } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.java index 73559d7b..b49aad06 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.java @@ -130,8 +130,8 @@ public class CollectionMembersListFragment extends ListFragment implements Adapt @Override protected MembersResult doInBackground(Void... voids) { try { - OkHttpClient httpClient = HttpClient.create(getContext(), account); AccountSettings settings = new AccountSettings(getContext(), account); + OkHttpClient httpClient = HttpClient.create(getContext(), settings); JournalManager journalsManager = new JournalManager(httpClient, HttpUrl.get(settings.getUri())); JournalManager.Journal journal = JournalManager.Journal.fakeWithUid(journalEntity.getUid()); diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java index 5adb88aa..d23fc146 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java @@ -130,7 +130,7 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa // 1. find service ID if (info.type == CollectionInfo.Type.ADDRESS_BOOK) { - authority = ContactsContract.AUTHORITY; + authority = App.getAddressBooksAuthority(); } else if (info.type == CollectionInfo.Type.CALENDAR) { authority = CalendarContract.AUTHORITY; } else { @@ -142,7 +142,7 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa AccountSettings settings = new AccountSettings(getContext(), account); HttpUrl principal = HttpUrl.get(settings.getUri()); - JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal); + JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), settings), principal); if (info.uid == null) { info.uid = JournalManager.Journal.genUid(); Crypto.CryptoManager crypto = new Crypto.CryptoManager(info.version, settings.password(), info.uid); diff --git a/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java index cf5bed55..20606422 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java @@ -119,7 +119,7 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa AccountSettings settings = new AccountSettings(getContext(), account); HttpUrl principal = HttpUrl.get(settings.getUri()); - JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal); + JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), settings), principal); Crypto.CryptoManager crypto = new Crypto.CryptoManager(collectionInfo.version, settings.password(), collectionInfo.uid); journalManager.delete(new JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.uid)); diff --git a/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.java index a8191238..e61799ee 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.java @@ -52,7 +52,7 @@ public class RemoveMemberFragment extends DialogFragment { memberEmail = getArguments().getString(KEY_MEMBER); try { settings = new AccountSettings(getContext(), account); - httpClient = HttpClient.create(getContext(), account); + httpClient = HttpClient.create(getContext(), settings); } catch (InvalidAccountException e) { e.printStackTrace(); } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java index 1a117511..783344a8 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java @@ -256,7 +256,7 @@ public class ViewCollectionActivity extends AppCompatActivity implements Refresh } } else { try { - LocalAddressBook resource = new LocalAddressBook(account, getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI)); + LocalAddressBook resource = new LocalAddressBook(ViewCollectionActivity.this, account, getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI)); count = resource.count(); } catch (ContactsStorageException e) { e.printStackTrace(); diff --git a/app/src/main/java/com/etesync/syncadapter/ui/importlocal/ImportFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/importlocal/ImportFragment.java index 6b06b39f..c52f9f70 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/importlocal/ImportFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/importlocal/ImportFragment.java @@ -307,7 +307,7 @@ public class ImportFragment extends DialogFragment { finishParsingFile(contacts.length); ContentProviderClient provider = getContext().getContentResolver().acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI); - LocalAddressBook localAddressBook = new LocalAddressBook(account, provider); + LocalAddressBook localAddressBook = new LocalAddressBook(getContext(), account, provider); for (Contact contact : contacts) { try { diff --git a/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.java index dfa42309..74332951 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.java @@ -104,7 +104,7 @@ public class LocalContactImportFragment extends Fragment { String accountType = cursor.getString(accountTypeIndex); if (account == null || (!account.name.equals(accountName) || !account.type.equals(accountType))) { account = new Account(accountName, accountType); - localAddressBooks.add(new LocalAddressBook(account, provider)); + localAddressBooks.add(new LocalAddressBook(getContext(), account, provider)); } } @@ -152,7 +152,7 @@ public class LocalContactImportFragment extends Fragment { private ResultFragment.ImportResult importContacts(LocalAddressBook localAddressBook) { ResultFragment.ImportResult result = new ResultFragment.ImportResult(); try { - LocalAddressBook addressBook = new LocalAddressBook(account, + LocalAddressBook addressBook = new LocalAddressBook(getContext(), account, getContext().getContentResolver().acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI)); LocalContact[] localContacts = localAddressBook.getAll(); int total = localContacts.length; diff --git a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java index 163b0682..e49fde38 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java @@ -171,9 +171,9 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan insertService(accountName, CollectionInfo.Type.ADDRESS_BOOK, config.cardDAV); // contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml - settings.setSyncInterval(ContactsContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL); + settings.setSyncInterval(App.getAddressBooksAuthority(), Constants.DEFAULT_SYNC_INTERVAL); } else { - ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0); + ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 0); } if (config.calDAV != null) { diff --git a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.java index 997f810c..c8953e9f 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.java @@ -90,7 +90,7 @@ public class SetupUserInfoFragment extends DialogFragment { protected SetupUserInfo.SetupUserInfoResult doInBackground(Account... accounts) { try { Crypto.CryptoManager cryptoManager; - OkHttpClient httpClient = HttpClient.create(getContext(), account); + OkHttpClient httpClient = HttpClient.create(getContext(), settings); UserInfoManager userInfoManager = new UserInfoManager(httpClient, HttpUrl.get(settings.getUri())); UserInfoManager.UserInfo userInfo = userInfoManager.get(account.name); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a061b759..4739e77f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,11 @@ EteSync + com.etesync.syncadapter + com.etesync.syncadapter.address_book + EteSync Address book + com.etesync.syncadapter.addressbooks + Address books Help Manage accounts Please wait … diff --git a/app/src/main/res/xml/account_authenticator_address_book.xml b/app/src/main/res/xml/account_authenticator_address_book.xml new file mode 100644 index 00000000..48979c31 --- /dev/null +++ b/app/src/main/res/xml/account_authenticator_address_book.xml @@ -0,0 +1,14 @@ + + + diff --git a/app/src/main/res/xml/sync_address_books.xml b/app/src/main/res/xml/sync_address_books.xml new file mode 100644 index 00000000..fea4886d --- /dev/null +++ b/app/src/main/res/xml/sync_address_books.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/xml/sync_contacts.xml b/app/src/main/res/xml/sync_contacts.xml index 5d0e19fe..02f747e4 100644 --- a/app/src/main/res/xml/sync_contacts.xml +++ b/app/src/main/res/xml/sync_contacts.xml @@ -7,9 +7,9 @@ --> + android:userVisible="false" + android:isAlwaysSyncable="true" /> diff --git a/ical4android b/ical4android index 2f6a94e8..49fbb684 160000 --- a/ical4android +++ b/ical4android @@ -1 +1 @@ -Subproject commit 2f6a94e85b9c0a375af8bdb08c8af82287c5fdc4 +Subproject commit 49fbb6846153fee5e5cbc71b77e94bef7629936d