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