diff --git a/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt b/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt index 6e7c6b556..ddf0e95f7 100644 --- a/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt +++ b/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt @@ -5,13 +5,11 @@ package at.bitfire.davdroid.sync import android.accounts.Account -import android.content.ContentProviderClient import android.content.Context import android.content.SyncResult import androidx.test.platform.app.InstrumentationRegistry import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase -import at.bitfire.davdroid.network.HttpClient import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Assert.assertEquals @@ -67,7 +65,7 @@ class SyncerTest { val syncCalled = AtomicInteger() - override fun sync(provider: ContentProviderClient) { + override fun sync() { Thread.sleep(1000) syncCalled.incrementAndGet() } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt index 56573d3fa..914714ac9 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt @@ -53,7 +53,28 @@ class AddressBookSyncer @AssistedInject constructor( const val PREVIOUS_GROUP_METHOD = "previous_group_method" } - override fun sync(provider: ContentProviderClient) { + override fun sync() { + + // use contacts provider for address books (not address book authority) + val contactsAuthority = ContactsContract.AUTHORITY + + // acquire ContentProviderClient + val provider = try { + context.contentResolver.acquireContentProviderClient(contactsAuthority) + } catch (e: SecurityException) { + Logger.log.log(Level.WARNING, "Missing permissions for authority $contactsAuthority", e) + null + } + + if (provider == null) { + /* Can happen if + - we're not allowed to access the content provider, or + - the content provider is not available at all, for instance because the respective + system app, like "contacts storage" is disabled */ + Logger.log.warning("Couldn't connect to content provider of authority $contactsAuthority") + syncResult.stats.numParseExceptions++ // hard sync error + return + } // 1. find address book collections to be synced val remoteAddressBooks = mutableMapOf() diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt index 3003fc6b7..a6f4a8a55 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt @@ -42,9 +42,29 @@ class CalendarSyncer @AssistedInject constructor( fun create(account: Account, extras: Array, authority: String, syncResult: SyncResult): CalendarSyncer } - override fun sync(provider: ContentProviderClient) { + override fun sync() { // 0. preparations + + // acquire ContentProviderClient + val provider = try { + context.contentResolver.acquireContentProviderClient(authority) + } catch (e: SecurityException) { + Logger.log.log(Level.WARNING, "Missing permissions for authority $authority", e) + null + } + + if (provider == null) { + /* Can happen if + - we're not allowed to access the content provider, or + - the content provider is not available at all, for instance because the respective + system app, like "calendar storage" is disabled */ + Logger.log.warning("Couldn't connect to content provider of authority $authority") + syncResult.stats.numParseExceptions++ // hard sync error + return + } + + // Update colors val accountSettings = AccountSettings(context, account) if (accountSettings.getEventColors()) AndroidCalendar.insertColors(provider, account) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt index 28f39f9cf..34e3f85b4 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt @@ -45,10 +45,28 @@ class JtxSyncer @AssistedInject constructor( fun create(account: Account, extras: Array, authority: String, syncResult: SyncResult): JtxSyncer } - override fun sync(provider: ContentProviderClient) { + override fun sync() { // 0. preparations + // acquire ContentProviderClient + val provider = try { + context.contentResolver.acquireContentProviderClient(authority) + } catch (e: SecurityException) { + Logger.log.log(Level.WARNING, "Missing permissions for authority $authority", e) + null + } + + if (provider == null) { + /* Can happen if + - we're not allowed to access the content provider, or + - the content provider is not available at all, for instance because the respective + system app, like "calendar storage" is disabled */ + Logger.log.warning("Couldn't connect to content provider of authority $authority") + syncResult.stats.numParseExceptions++ // hard sync error + return + } + // check whether jtx Board is new enough try { TaskProvider.checkVersion(context, TaskProvider.ProviderName.JtxBoard) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt index 03fe2a5ec..eb7cdf3a2 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt @@ -66,41 +66,17 @@ abstract class Syncer( * with remote information. Then syncs the actual entries (events, tasks, contacts, etc) of all * collections. */ - abstract fun sync(provider: ContentProviderClient) + abstract fun sync() fun onPerformSync() { Logger.log.log(Level.INFO, "$authority sync of $account initiated", extras.joinToString(", ")) - // use contacts provider for address books - val contentAuthority = - if (authority == context.getString(R.string.address_books_authority)) - ContactsContract.AUTHORITY - else - authority - - // acquire ContentProviderClient of authority to be synced - val provider = try { - context.contentResolver.acquireContentProviderClient(contentAuthority) - } catch (e: SecurityException) { - Logger.log.log(Level.WARNING, "Missing permissions for authority $contentAuthority", e) - null - } - - if (provider == null) { - /* Can happen if - - we're not allowed to access the content provider, or - - the content provider is not available at all, for instance because the respective - system app, like "contacts storage" is disabled */ - Logger.log.warning("Couldn't connect to content provider of authority $contentAuthority") - syncResult.stats.numParseExceptions++ // hard sync error - return - } // run sync try { val runSync = /* ose */ true if (runSync) - sync(provider) + sync() } catch (e: DeadObjectException) { /* May happen when the remote process dies or (since Android 14) when IPC (for instance with the calendar provider) @@ -112,16 +88,15 @@ abstract class Syncer( Logger.log.log(Level.WARNING, "Account was removed during synchronization", e) } catch (e: Exception) { - Logger.log.log(Level.SEVERE, "Couldn't sync $contentAuthority", e) + Logger.log.log(Level.SEVERE, "Couldn't sync $authority", e) syncResult.stats.numParseExceptions++ // Hard sync error } finally { if (httpClient.isInitialized()) httpClient.value.close() - provider.close() Logger.log.log( Level.INFO, - "$contentAuthority sync of $account finished", + "$authority sync of $account finished", extras.joinToString(", ")) } } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt index f1851a2e3..de25bef34 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt @@ -6,7 +6,6 @@ package at.bitfire.davdroid.sync import android.accounts.Account import android.accounts.AccountManager -import android.content.ContentProviderClient import android.content.Context import android.content.SyncResult import android.os.Build @@ -46,10 +45,28 @@ class TaskSyncer @AssistedInject constructor( fun create(account: Account, extras: Array, authority: String, syncResult: SyncResult): TaskSyncer } - override fun sync(provider: ContentProviderClient) { + override fun sync() { // 0. preparations + // acquire ContentProviderClient + val provider = try { + context.contentResolver.acquireContentProviderClient(authority) + } catch (e: SecurityException) { + Logger.log.log(Level.WARNING, "Missing permissions for authority $authority", e) + null + } + + if (provider == null) { + /* Can happen if + - we're not allowed to access the content provider, or + - the content provider is not available at all, for instance because the respective + system app, like "calendar storage" is disabled */ + Logger.log.warning("Couldn't connect to content provider of authority $authority") + syncResult.stats.numParseExceptions++ // hard sync error + return + } + // Acquire task provider val providerName = TaskProvider.ProviderName.fromAuthority(authority) val taskProvider = try {