1
0
mirror of https://github.com/etesync/android synced 2024-12-23 15:18:14 +00:00

AddressBooksSyncAdapter: implement syncing etebase address books

This commit is contained in:
Tom Hacohen 2020-08-26 08:02:44 +03:00
parent d6a0958d16
commit deb1bb831b
2 changed files with 124 additions and 6 deletions

View File

@ -20,6 +20,7 @@ import android.provider.ContactsContract.Groups
import android.provider.ContactsContract.RawContacts import android.provider.ContactsContract.RawContacts
import at.bitfire.vcard4android.* import at.bitfire.vcard4android.*
import com.etesync.syncadapter.App import com.etesync.syncadapter.App
import com.etesync.syncadapter.CachedCollection
import com.etesync.syncadapter.log.Logger import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.CollectionInfo import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity import com.etesync.syncadapter.model.JournalEntity
@ -70,6 +71,35 @@ class LocalAddressBook(
return addressBook 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) fun find(context: Context, provider: ContentProviderClient?, mainAccount: Account?) = AccountManager.get(context)
.getAccountsByType(App.addressBookAccountType) .getAccountsByType(App.addressBookAccountType)
@ -103,6 +133,19 @@ class LocalAddressBook(
return sb.toString() 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 { fun initialUserData(mainAccount: Account, url: String): Bundle {
val bundle = Bundle(3) val bundle = Bundle(3)
bundle.putString(USER_DATA_MAIN_ACCOUNT_NAME, mainAccount.name) bundle.putString(USER_DATA_MAIN_ACCOUNT_NAME, mainAccount.name)
@ -181,6 +224,37 @@ class LocalAddressBook(
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true) 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() { fun delete() {
val accountManager = AccountManager.get(context) val accountManager = AccountManager.get(context)

View File

@ -15,10 +15,7 @@ import android.content.*
import android.os.Bundle import android.os.Bundle
import android.provider.ContactsContract import android.provider.ContactsContract
import at.bitfire.vcard4android.ContactsStorageException import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.AccountSettings import com.etesync.syncadapter.*
import com.etesync.syncadapter.App
import com.etesync.syncadapter.Constants
import com.etesync.syncadapter.R
import com.etesync.syncadapter.log.Logger import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.CollectionInfo import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity import com.etesync.syncadapter.model.JournalEntity
@ -53,7 +50,11 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run() RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run()
updateLocalAddressBooks(contactsProvider, account) if (settings.isLegacy) {
legacyUpdateLocalAddressBooks(contactsProvider, account)
} else {
updateLocalAddressBooks(contactsProvider, account, settings)
}
contactsProvider.release() contactsProvider.release()
@ -69,9 +70,52 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
Logger.log.info("Address book sync complete") Logger.log.info("Address book sync complete")
} }
private fun updateLocalAddressBooks(provider: ContentProviderClient, account: Account, settings: AccountSettings) {
val remote = HashMap<String, CachedCollection>()
val etebaseLocalCache = EtebaseLocalCache.getInstance(context, account.name)
val collections: List<CachedCollection>
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) @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 context = context
val data = (getContext().applicationContext as App).data val data = (getContext().applicationContext as App).data
val service = JournalModel.Service.fetchOrCreate(data, account.name, CollectionInfo.Type.ADDRESS_BOOK) val service = JournalModel.Service.fetchOrCreate(data, account.name, CollectionInfo.Type.ADDRESS_BOOK)