diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.kt b/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.kt index a7cf378e..f288e4dd 100644 --- a/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.kt +++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.kt @@ -20,6 +20,7 @@ import android.provider.ContactsContract.Groups import android.provider.ContactsContract.RawContacts import at.bitfire.vcard4android.* import com.etesync.syncadapter.App +import com.etesync.syncadapter.CachedCollection import com.etesync.syncadapter.log.Logger import com.etesync.syncadapter.model.CollectionInfo import com.etesync.syncadapter.model.JournalEntity @@ -70,6 +71,35 @@ class LocalAddressBook( return addressBook } + fun create(context: Context, provider: ContentProviderClient, mainAccount: Account, cachedCollection: CachedCollection): LocalAddressBook { + val col = cachedCollection.col + val accountManager = AccountManager.get(context) + + val account = Account(accountName(mainAccount, cachedCollection), App.addressBookAccountType) + val userData = initialUserData(mainAccount, col.uid) + Logger.log.log(Level.INFO, "Creating local address book $account", userData) + if (!accountManager.addAccountExplicitly(account, null, userData)) + throw IllegalStateException("Couldn't create address book account") + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + // Android < 7 seems to lose the initial user data sometimes, so set it a second time + // https://forums.bitfire.at/post/11644 + userData.keySet().forEach { key -> + accountManager.setUserData(account, key, userData.getString(key)) + } + } + + + val addressBook = LocalAddressBook(context, account, provider) + ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true) + + val values = ContentValues(2) + values.put(ContactsContract.Settings.SHOULD_SYNC, 1) + values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1) + addressBook.settings = values + + return addressBook + } fun find(context: Context, provider: ContentProviderClient?, mainAccount: Account?) = AccountManager.get(context) .getAccountsByType(App.addressBookAccountType) @@ -103,6 +133,19 @@ class LocalAddressBook( return sb.toString() } + fun accountName(mainAccount: Account, cachedCollection: CachedCollection): String { + val col = cachedCollection.col + val meta = cachedCollection.meta + val displayName = meta.name + val sb = StringBuilder(displayName) + sb.append(" (") + .append(mainAccount.name) + .append(" ") + .append(col.uid.substring(0, 4)) + .append(")") + return sb.toString() + } + fun initialUserData(mainAccount: Account, url: String): Bundle { val bundle = Bundle(3) bundle.putString(USER_DATA_MAIN_ACCOUNT_NAME, mainAccount.name) @@ -181,6 +224,37 @@ class LocalAddressBook( ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true) } + + fun update(cachedCollection: CachedCollection) { + val col = cachedCollection.col + val newAccountName = accountName(mainAccount, cachedCollection) + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + if (account.name != newAccountName && Build.VERSION.SDK_INT >= 21) { + val accountManager = AccountManager.get(context) + val future = accountManager.renameAccount(account, newAccountName, { + try { + // update raw contacts to new account name + if (provider != null) { + val values = ContentValues(1) + values.put(RawContacts.ACCOUNT_NAME, newAccountName) + provider.update(syncAdapterURI(RawContacts.CONTENT_URI), values, RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?", + arrayOf(account.name, account.type)) + } + } catch (e: RemoteException) { + Logger.log.log(Level.WARNING, "Couldn't re-assign contacts to new account name", e) + } + }, null) + account = future.result + } + + readOnly = col.accessLevel == "ro" + Logger.log.info("Address book write permission? = ${!readOnly}") + + // make sure it will still be synchronized when contacts are updated + ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true) + } + fun delete() { val accountManager = AccountManager.get(context) diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt index 9233eaab..2508a621 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt @@ -15,10 +15,7 @@ import android.content.* import android.os.Bundle import android.provider.ContactsContract import at.bitfire.vcard4android.ContactsStorageException -import com.etesync.syncadapter.AccountSettings -import com.etesync.syncadapter.App -import com.etesync.syncadapter.Constants -import com.etesync.syncadapter.R +import com.etesync.syncadapter.* import com.etesync.syncadapter.log.Logger import com.etesync.syncadapter.model.CollectionInfo import com.etesync.syncadapter.model.JournalEntity @@ -53,7 +50,11 @@ class AddressBooksSyncAdapterService : SyncAdapterService() { RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run() - updateLocalAddressBooks(contactsProvider, account) + if (settings.isLegacy) { + legacyUpdateLocalAddressBooks(contactsProvider, account) + } else { + updateLocalAddressBooks(contactsProvider, account, settings) + } contactsProvider.release() @@ -69,9 +70,52 @@ class AddressBooksSyncAdapterService : SyncAdapterService() { Logger.log.info("Address book sync complete") } + private fun updateLocalAddressBooks(provider: ContentProviderClient, account: Account, settings: AccountSettings) { + val remote = HashMap() + val etebaseLocalCache = EtebaseLocalCache.getInstance(context, account.name) + val collections: List + synchronized(etebaseLocalCache) { + val httpClient = HttpClient.Builder(context, settings).setForeground(false).build() + val etebase = EtebaseLocalCache.getEtebase(context, httpClient.okHttpClient, settings) + val colMgr = etebase.collectionManager + + collections = etebaseLocalCache.collectionList(colMgr).filter { it.meta.collectionType == Constants.ETEBASE_TYPE_ADDRESS_BOOK } + } + + for (collection in collections) { + remote[collection.col.uid] = collection + } + + val local = LocalAddressBook.find(context, provider, account) + + val updateColors = settings.manageCalendarColors + + // delete obsolete local calendar + for (addressBook in local) { + val url = addressBook.url + val collection = remote[url] + if (collection == null) { + Logger.log.fine("Deleting obsolete local addressBook $url") + addressBook.delete() + } else { + // remote CollectionInfo found for this local collection, update data + Logger.log.fine("Updating local addressBook $url") + addressBook.update(collection) + // we already have a local addressBook for this remote collection, don't take into consideration anymore + remote.remove(url) + } + } + + // create new local calendars + for (url in remote.keys) { + val cachedCollection = remote[url]!! + Logger.log.info("Adding local calendar list $cachedCollection") + LocalAddressBook.create(context, provider, account, cachedCollection) + } + } @Throws(ContactsStorageException::class, AuthenticatorException::class, OperationCanceledException::class, IOException::class) - private fun updateLocalAddressBooks(provider: ContentProviderClient, account: Account) { + private fun legacyUpdateLocalAddressBooks(provider: ContentProviderClient, account: Account) { val context = context val data = (getContext().applicationContext as App).data val service = JournalModel.Service.fetchOrCreate(data, account.name, CollectionInfo.Type.ADDRESS_BOOK)