Skip to content

Commit 51f01b2

Browse files
sunkuprfc2822
andauthored
Remove address books sync authority and content provider (#877)
* Drop address book provider * Drop address book sync adapter service * Replace address books authority with contacts authority * Update kdoc * Revert "Update kdoc" This reverts commit dfb14d4. * Revert "Replace address books authority with contacts authority" This reverts commit 0e15bf1. * Don't enable addressbook sync for main account * Acquire content provider in Syncer * Use contacts authority instead of address book authority when acquiring content provider * Set default sync interval for address book authority again * Minor re-ordering of lines * Add comment, rename variable * Move SyncAdapterServices out of adapter package --------- Co-authored-by: Ricki Hirner <[email protected]>
1 parent c02bf94 commit 51f01b2

File tree

9 files changed

+105
-137
lines changed

9 files changed

+105
-137
lines changed

app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ class SyncerTest {
3434

3535
/** use our WebDAV provider as a mock provider because it's our own and we don't need any permissions for it */
3636
private val mockAuthority = context.getString(R.string.webdav_authority)
37-
private val mockProvider = context.contentResolver!!.acquireContentProviderClient(mockAuthority)!!
3837

3938
val account = Account(javaClass.canonicalName, context.getString(R.string.account_type))
4039

@@ -47,7 +46,7 @@ class SyncerTest {
4746
@Test
4847
fun testOnPerformSync_runsSyncAndSetsClassLoader() {
4948
val syncer = TestSyncer(context, db)
50-
syncer.onPerformSync(account, arrayOf(), mockAuthority, mockProvider, SyncResult())
49+
syncer.onPerformSync(account, arrayOf(), mockAuthority, SyncResult())
5150

5251
// check whether onPerformSync() actually calls sync()
5352
assertEquals(1, syncer.syncCalled.get())

app/src/main/AndroidManifest.xml

+5-22
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
android:resource="@xml/account_authenticator"/>
178178
</service>
179179
<service
180-
android:name=".sync.adapter.CalendarsSyncAdapterService"
180+
android:name=".sync.CalendarsSyncAdapterService"
181181
android:exported="true"
182182
tools:ignore="ExportedService">
183183
<intent-filter>
@@ -188,7 +188,7 @@
188188
android:resource="@xml/sync_calendars"/>
189189
</service>
190190
<service
191-
android:name=".sync.adapter.JtxSyncAdapterService"
191+
android:name=".sync.JtxSyncAdapterService"
192192
android:exported="true"
193193
tools:ignore="ExportedService">
194194
<intent-filter>
@@ -199,7 +199,7 @@
199199
android:resource="@xml/sync_notes"/>
200200
</service>
201201
<service
202-
android:name=".sync.adapter.OpenTasksSyncAdapterService"
202+
android:name=".sync.OpenTasksSyncAdapterService"
203203
android:exported="true"
204204
tools:ignore="ExportedService">
205205
<intent-filter>
@@ -210,7 +210,7 @@
210210
android:resource="@xml/sync_opentasks"/>
211211
</service>
212212
<service
213-
android:name=".sync.adapter.TasksOrgSyncAdapterService"
213+
android:name=".sync.TasksOrgSyncAdapterService"
214214
android:exported="true"
215215
tools:ignore="ExportedService">
216216
<intent-filter>
@@ -244,25 +244,8 @@
244244
android:name="android.accounts.AccountAuthenticator"
245245
android:resource="@xml/account_authenticator_address_book"/>
246246
</service>
247-
<provider
248-
android:authorities="@string/address_books_authority"
249-
android:exported="false"
250-
android:label="@string/address_books_authority_title"
251-
android:name=".sync.adapter.AddressBookProvider" />
252-
<service
253-
android:name=".sync.adapter.AddressBooksSyncAdapterService"
254-
android:exported="true"
255-
tools:ignore="ExportedService">
256-
<intent-filter>
257-
<action android:name="android.content.SyncAdapter"/>
258-
</intent-filter>
259-
260-
<meta-data
261-
android:name="android.content.SyncAdapter"
262-
android:resource="@xml/sync_address_books"/>
263-
</service>
264247
<service
265-
android:name=".sync.adapter.ContactsSyncAdapterService"
248+
android:name=".sync.ContactsSyncAdapterService"
266249
android:exported="true"
267250
tools:ignore="ExportedService">
268251
<intent-filter>

app/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt

+6-10
Original file line numberDiff line numberDiff line change
@@ -90,26 +90,19 @@ class AccountRepository @Inject constructor(
9090
// insert CardDAV service
9191
val id = insertService(accountName, Service.TYPE_CARDDAV, config.cardDAV)
9292

93-
// initial CardDAV account settings
93+
// initial CardDAV account settings and sync intervals
9494
accountSettings.setGroupMethod(groupMethod)
95+
accountSettings.setSyncInterval(addrBookAuthority, defaultSyncInterval)
9596

9697
// start CardDAV service detection (refresh collections)
9798
RefreshCollectionsWorker.enqueue(context, id)
98-
99-
// set default sync interval and enable sync regardless of permissions
100-
ContentResolver.setIsSyncable(account, addrBookAuthority, 1)
101-
accountSettings.setSyncInterval(addrBookAuthority, defaultSyncInterval)
102-
} else
103-
ContentResolver.setIsSyncable(account, addrBookAuthority, 0)
99+
}
104100

105101
// Configure CalDAV service
106102
if (config.calDAV != null) {
107103
// insert CalDAV service
108104
val id = insertService(accountName, Service.TYPE_CALDAV, config.calDAV)
109105

110-
// start CalDAV service detection (refresh collections)
111-
RefreshCollectionsWorker.enqueue(context, id)
112-
113106
// set default sync interval and enable sync regardless of permissions
114107
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1)
115108
accountSettings.setSyncInterval(CalendarContract.AUTHORITY, defaultSyncInterval)
@@ -123,6 +116,9 @@ class AccountRepository @Inject constructor(
123116
Logger.log.info("Tasks provider ${taskProvider.authority} found. Tasks sync enabled.")
124117
} else
125118
Logger.log.info("No tasks provider found. Did not enable tasks sync.")
119+
120+
// start CalDAV service detection (refresh collections)
121+
RefreshCollectionsWorker.enqueue(context, id)
126122
} else
127123
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0)
128124

app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,13 @@ class AccountSettings(
217217
* @return sync interval in seconds; *[SYNC_INTERVAL_MANUALLY]* if manual sync; *null* if not set
218218
*/
219219
fun getSyncInterval(authority: String): Long? {
220-
if (ContentResolver.getIsSyncable(account, authority) <= 0)
220+
val addrBookAuthority = context.getString(R.string.address_books_authority)
221+
222+
if (ContentResolver.getIsSyncable(account, authority) <= 0 && authority != addrBookAuthority)
221223
return null
222224

223225
val key = when {
224-
authority == context.getString(R.string.address_books_authority) ->
226+
authority == addrBookAuthority ->
225227
KEY_SYNC_INTERVAL_ADDRESSBOOKS
226228
authority == CalendarContract.AUTHORITY ->
227229
KEY_SYNC_INTERVAL_CALENDARS

app/src/main/kotlin/at/bitfire/davdroid/sync/adapter/SyncAdapterServices.kt renamed to app/src/main/kotlin/at/bitfire/davdroid/sync/SyncAdapterServices.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
33
**************************************************************************************************/
44

5-
package at.bitfire.davdroid.sync.adapter
5+
package at.bitfire.davdroid.sync
66

77
import android.accounts.Account
88
import android.app.Service
@@ -134,7 +134,6 @@ abstract class SyncAdapterService: Service() {
134134
}
135135

136136
// exported sync adapter services; we need a separate class for each authority
137-
class AddressBooksSyncAdapterService: SyncAdapterService()
138137
class CalendarsSyncAdapterService: SyncAdapterService()
139138
class ContactsSyncAdapterService: SyncAdapterService()
140139
class JtxSyncAdapterService: SyncAdapterService()

app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt

+32-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import android.content.ContentProviderClient
99
import android.content.Context
1010
import android.content.SyncResult
1111
import android.os.DeadObjectException
12+
import android.provider.ContactsContract
1213
import at.bitfire.davdroid.InvalidAccountException
14+
import at.bitfire.davdroid.R
1315
import at.bitfire.davdroid.db.AppDatabase
1416
import at.bitfire.davdroid.log.Logger
1517
import at.bitfire.davdroid.network.HttpClient
@@ -59,19 +61,44 @@ abstract class Syncer(
5961
account: Account,
6062
extras: Array<String>,
6163
authority: String,
62-
provider: ContentProviderClient,
6364
syncResult: SyncResult
6465
) {
6566
Logger.log.log(Level.INFO, "$authority sync of $account initiated", extras.joinToString(", "))
6667

68+
// use contacts provider for address books
69+
val contentAuthority =
70+
if (authority == context.getString(R.string.address_books_authority))
71+
ContactsContract.AUTHORITY
72+
else
73+
authority
74+
6775
val accountSettings by lazy { AccountSettings(context, account) }
6876
val httpClient = lazy { HttpClient.Builder(context, accountSettings).build() }
6977

78+
// acquire ContentProviderClient of authority to be synced
79+
val provider = try {
80+
context.contentResolver.acquireContentProviderClient(contentAuthority)
81+
} catch (e: SecurityException) {
82+
Logger.log.log(Level.WARNING, "Missing permissions for authority $contentAuthority", e)
83+
null
84+
}
85+
86+
if (provider == null) {
87+
/* Can happen if
88+
- we're not allowed to access the content provider, or
89+
- the content provider is not available at all, for instance because the respective
90+
system app, like "contacts storage" is disabled */
91+
Logger.log.warning("Couldn't connect to content provider of authority $contentAuthority")
92+
syncResult.stats.numParseExceptions++ // hard sync error
93+
return
94+
}
95+
96+
// run sync
7097
try {
7198
val runSync = /* ose */ true
7299

73100
if (runSync)
74-
sync(account, extras, authority, httpClient, provider, syncResult)
101+
sync(account, extras, contentAuthority, httpClient, provider, syncResult)
75102

76103
} catch (e: DeadObjectException) {
77104
/* May happen when the remote process dies or (since Android 14) when IPC (for instance with the calendar provider)
@@ -83,15 +110,15 @@ abstract class Syncer(
83110
Logger.log.log(Level.WARNING, "Account was removed during synchronization", e)
84111

85112
} catch (e: Exception) {
86-
Logger.log.log(Level.SEVERE, "Couldn't sync $authority", e)
87-
syncResult.stats.numParseExceptions++
113+
Logger.log.log(Level.SEVERE, "Couldn't sync $contentAuthority", e)
114+
syncResult.stats.numParseExceptions++ // Hard sync error
88115

89116
} finally {
90117
if (httpClient.isInitialized())
91118
httpClient.value.close()
92119
Logger.log.log(
93120
Level.INFO,
94-
"$authority sync of $account finished",
121+
"$contentAuthority sync of $account finished",
95122
extras.joinToString(", "))
96123
}
97124
}

app/src/main/kotlin/at/bitfire/davdroid/sync/adapter/AddressBookProvider.kt

-20
This file was deleted.

app/src/main/kotlin/at/bitfire/davdroid/sync/worker/BaseSyncWorker.kt

+56-69
Original file line numberDiff line numberDiff line change
@@ -290,81 +290,68 @@ abstract class BaseSyncWorker(
290290
// Comes in through SyncAdapterService and is used only by ContactsSyncManager for an Android 7 workaround.
291291
extras.add(ContentResolver.SYNC_EXTRAS_UPLOAD)
292292

293-
// acquire ContentProviderClient of authority to be synced
294-
try {
295-
applicationContext.contentResolver.acquireContentProviderClient(authority)
296-
} catch (e: SecurityException) {
297-
Logger.log.log(Level.WARNING, "Missing permissions to acquire ContentProviderClient for $authority", e)
298-
null
299-
}.use { provider ->
300-
if (provider == null) {
301-
Logger.log.warning("Couldn't acquire ContentProviderClient for $authority")
302-
return@withContext Result.failure()
303-
}
304-
305-
val result = SyncResult()
306-
// Start syncing. We still use the sync adapter framework's SyncResult to pass the sync results, but this
307-
// is only for legacy reasons and can be replaced by an own result class in the future.
308-
runInterruptible {
309-
syncer.onPerformSync(account, extras.toTypedArray(), authority, provider, result)
310-
}
293+
val result = SyncResult()
294+
// Start syncing. We still use the sync adapter framework's SyncResult to pass the sync results, but this
295+
// is only for legacy reasons and can be replaced by an own result class in the future.
296+
runInterruptible {
297+
syncer.onPerformSync(account, extras.toTypedArray(), authority, result)
298+
}
311299

312-
// Check for errors
313-
if (result.hasError()) {
314-
val syncResult = Data.Builder()
315-
.putString("syncresult", result.toString())
316-
.putString("syncResultStats", result.stats.toString())
317-
.build()
318-
319-
val softErrorNotificationTag = account.type + "-" + account.name + "-" + authority
320-
321-
// On soft errors the sync is retried a few times before considered failed
322-
if (result.hasSoftError()) {
323-
Logger.log.warning("Soft error while syncing: result=$result, stats=${result.stats}")
324-
if (runAttemptCount < MAX_RUN_ATTEMPTS) {
325-
val blockDuration = result.delayUntil - System.currentTimeMillis() / 1000
326-
Logger.log.warning("Waiting for $blockDuration seconds, before retrying ...")
327-
328-
// We block the SyncWorker here so that it won't be started by the sync framework immediately again.
329-
// This should be replaced by proper work scheduling as soon as we don't depend on the sync framework anymore.
330-
if (blockDuration > 0)
331-
delay(blockDuration * 1000)
332-
333-
Logger.log.warning("Retrying on soft error (attempt $runAttemptCount of $MAX_RUN_ATTEMPTS)")
334-
return@withContext Result.retry()
335-
}
336-
337-
Logger.log.warning("Max retries on soft errors reached ($runAttemptCount of $MAX_RUN_ATTEMPTS). Treating as failed")
338-
339-
notificationManager.notifyIfPossible(
340-
softErrorNotificationTag,
341-
NotificationUtils.NOTIFY_SYNC_ERROR,
342-
NotificationUtils.newBuilder(applicationContext, NotificationUtils.CHANNEL_SYNC_IO_ERRORS)
343-
.setSmallIcon(R.drawable.ic_sync_problem_notify)
344-
.setContentTitle(account.name)
345-
.setContentText(applicationContext.getString(R.string.sync_error_retry_limit_reached))
346-
.setSubText(account.name)
347-
.setOnlyAlertOnce(true)
348-
.setPriority(NotificationCompat.PRIORITY_MIN)
349-
.setCategory(NotificationCompat.CATEGORY_ERROR)
350-
.build()
351-
)
352-
353-
return@withContext Result.failure(syncResult)
300+
// Check for errors
301+
if (result.hasError()) {
302+
val syncResult = Data.Builder()
303+
.putString("syncresult", result.toString())
304+
.putString("syncResultStats", result.stats.toString())
305+
.build()
306+
307+
val softErrorNotificationTag = account.type + "-" + account.name + "-" + authority
308+
309+
// On soft errors the sync is retried a few times before considered failed
310+
if (result.hasSoftError()) {
311+
Logger.log.warning("Soft error while syncing: result=$result, stats=${result.stats}")
312+
if (runAttemptCount < MAX_RUN_ATTEMPTS) {
313+
val blockDuration = result.delayUntil - System.currentTimeMillis() / 1000
314+
Logger.log.warning("Waiting for $blockDuration seconds, before retrying ...")
315+
316+
// We block the SyncWorker here so that it won't be started by the sync framework immediately again.
317+
// This should be replaced by proper work scheduling as soon as we don't depend on the sync framework anymore.
318+
if (blockDuration > 0)
319+
delay(blockDuration * 1000)
320+
321+
Logger.log.warning("Retrying on soft error (attempt $runAttemptCount of $MAX_RUN_ATTEMPTS)")
322+
return@withContext Result.retry()
354323
}
355324

356-
// If no soft error found, dismiss sync error notification
357-
notificationManager.cancel(
325+
Logger.log.warning("Max retries on soft errors reached ($runAttemptCount of $MAX_RUN_ATTEMPTS). Treating as failed")
326+
327+
notificationManager.notifyIfPossible(
358328
softErrorNotificationTag,
359-
NotificationUtils.NOTIFY_SYNC_ERROR
329+
NotificationUtils.NOTIFY_SYNC_ERROR,
330+
NotificationUtils.newBuilder(applicationContext, NotificationUtils.CHANNEL_SYNC_IO_ERRORS)
331+
.setSmallIcon(R.drawable.ic_sync_problem_notify)
332+
.setContentTitle(account.name)
333+
.setContentText(applicationContext.getString(R.string.sync_error_retry_limit_reached))
334+
.setSubText(account.name)
335+
.setOnlyAlertOnce(true)
336+
.setPriority(NotificationCompat.PRIORITY_MIN)
337+
.setCategory(NotificationCompat.CATEGORY_ERROR)
338+
.build()
360339
)
361340

362-
// On a hard error - fail with an error message
363-
// Note: SyncManager should have notified the user
364-
if (result.hasHardError()) {
365-
Logger.log.warning("Hard error while syncing: result=$result, stats=${result.stats}")
366-
return@withContext Result.failure(syncResult)
367-
}
341+
return@withContext Result.failure(syncResult)
342+
}
343+
344+
// If no soft error found, dismiss sync error notification
345+
notificationManager.cancel(
346+
softErrorNotificationTag,
347+
NotificationUtils.NOTIFY_SYNC_ERROR
348+
)
349+
350+
// On a hard error - fail with an error message
351+
// Note: SyncManager should have notified the user
352+
if (result.hasHardError()) {
353+
Logger.log.warning("Hard error while syncing: result=$result, stats=${result.stats}")
354+
return@withContext Result.failure(syncResult)
368355
}
369356
}
370357

app/src/main/res/xml/sync_address_books.xml

-5
This file was deleted.

0 commit comments

Comments
 (0)