Always use provider.use to automatically close content provider clients (#726)

This commit is contained in:
Ricki Hirner 2024-04-13 22:03:42 +02:00 committed by GitHub
parent f6d71ffb98
commit 0a58f0a269
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 63 additions and 78 deletions

View file

@ -285,13 +285,11 @@ class AccountSettingsMigrations(
provider.client.update(tasksUri, emptyETag, "${TaskContract.Tasks._DIRTY}=0 AND ${TaskContract.Tasks._DELETED}=0", null)
}
@SuppressLint("Recycle")
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED)
context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)?.let { provider ->
context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)?.use { provider ->
provider.update(
CalendarContract.Calendars.CONTENT_URI.asSyncAdapter(account),
AndroidCalendar.calendarBaseValues, null, null)
provider.close()
}
}
@ -355,9 +353,9 @@ class AccountSettingsMigrations(
}
@Suppress("unused")
@SuppressLint("Recycle", "ParcelClassLoader")
@SuppressLint("ParcelClassLoader")
private fun update_5_6() {
context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.let { provider ->
context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.use { provider ->
val parcel = Parcel.obtain()
try {
// don't run syncs during the migration
@ -408,7 +406,6 @@ class AccountSettingsMigrations(
throw ContactsStorageException("Couldn't migrate contacts to new address book", e)
} finally {
parcel.recycle()
provider.close()
}
}

View file

@ -97,8 +97,7 @@ class AddressBookSyncer(
return false
}
val contactsProvider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)
try {
context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY).use { contactsProvider ->
if (contactsProvider == null) {
Logger.log.severe("Couldn't access contacts provider")
syncResult.databaseError = true
@ -132,8 +131,6 @@ class AddressBookSyncer(
Logger.log.log(Level.INFO, "Adding local address book", info)
LocalAddressBook.create(context, contactsProvider, account, info, forceAllReadOnly)
}
} finally {
contactsProvider?.close()
}
return true

View file

@ -5,7 +5,6 @@
package at.bitfire.davdroid.syncadapter
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
@ -269,82 +268,80 @@ abstract class BaseSyncWorker(
extras.add(ContentResolver.SYNC_EXTRAS_UPLOAD)
// acquire ContentProviderClient of authority to be synced
val provider: ContentProviderClient? =
try {
applicationContext.contentResolver.acquireContentProviderClient(authority)
} catch (e: SecurityException) {
Logger.log.log(Level.WARNING, "Missing permissions to acquire ContentProviderClient for $authority", e)
null
try {
applicationContext.contentResolver.acquireContentProviderClient(authority)
} catch (e: SecurityException) {
Logger.log.log(Level.WARNING, "Missing permissions to acquire ContentProviderClient for $authority", e)
null
}.use { provider ->
if (provider == null) {
Logger.log.warning("Couldn't acquire ContentProviderClient for $authority")
return@withContext Result.failure()
}
if (provider == null) {
Logger.log.warning("Couldn't acquire ContentProviderClient for $authority")
return@withContext Result.failure()
}
val result = SyncResult()
provider.use {
val result = SyncResult()
// Start syncing. We still use the sync adapter framework's SyncResult to pass the sync results, but this
// is only for legacy reasons and can be replaced by an own result class in the future.
runInterruptible {
syncer.onPerformSync(account, extras.toTypedArray(), authority, provider, result)
}
}
// Check for errors
if (result.hasError()) {
val syncResult = Data.Builder()
.putString("syncresult", result.toString())
.putString("syncResultStats", result.stats.toString())
.build()
// Check for errors
if (result.hasError()) {
val syncResult = Data.Builder()
.putString("syncresult", result.toString())
.putString("syncResultStats", result.stats.toString())
.build()
val softErrorNotificationTag = account.type + "-" + account.name + "-" + authority
val softErrorNotificationTag = account.type + "-" + account.name + "-" + authority
// On soft errors the sync is retried a few times before considered failed
if (result.hasSoftError()) {
Logger.log.warning("Soft error while syncing: result=$result, stats=${result.stats}")
if (runAttemptCount < MAX_RUN_ATTEMPTS) {
val blockDuration = result.delayUntil - System.currentTimeMillis()/1000
Logger.log.warning("Waiting for $blockDuration seconds, before retrying ...")
// On soft errors the sync is retried a few times before considered failed
if (result.hasSoftError()) {
Logger.log.warning("Soft error while syncing: result=$result, stats=${result.stats}")
if (runAttemptCount < MAX_RUN_ATTEMPTS) {
val blockDuration = result.delayUntil - System.currentTimeMillis() / 1000
Logger.log.warning("Waiting for $blockDuration seconds, before retrying ...")
// We block the SyncWorker here so that it won't be started by the sync framework immediately again.
// This should be replaced by proper work scheduling as soon as we don't depend on the sync framework anymore.
if (blockDuration > 0)
delay(blockDuration*1000)
// We block the SyncWorker here so that it won't be started by the sync framework immediately again.
// This should be replaced by proper work scheduling as soon as we don't depend on the sync framework anymore.
if (blockDuration > 0)
delay(blockDuration * 1000)
Logger.log.warning("Retrying on soft error (attempt $runAttemptCount of $MAX_RUN_ATTEMPTS)")
return@withContext Result.retry()
Logger.log.warning("Retrying on soft error (attempt $runAttemptCount of $MAX_RUN_ATTEMPTS)")
return@withContext Result.retry()
}
Logger.log.warning("Max retries on soft errors reached ($runAttemptCount of $MAX_RUN_ATTEMPTS). Treating as failed")
notificationManager.notifyIfPossible(
softErrorNotificationTag,
NotificationUtils.NOTIFY_SYNC_ERROR,
NotificationUtils.newBuilder(applicationContext, NotificationUtils.CHANNEL_SYNC_IO_ERRORS)
.setSmallIcon(R.drawable.ic_sync_problem_notify)
.setContentTitle(account.name)
.setContentText(applicationContext.getString(R.string.sync_error_retry_limit_reached))
.setSubText(account.name)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_ERROR)
.build()
)
return@withContext Result.failure(syncResult)
}
Logger.log.warning("Max retries on soft errors reached ($runAttemptCount of $MAX_RUN_ATTEMPTS). Treating as failed")
notificationManager.notifyIfPossible(
// If no soft error found, dismiss sync error notification
notificationManager.cancel(
softErrorNotificationTag,
NotificationUtils.NOTIFY_SYNC_ERROR,
NotificationUtils.newBuilder(applicationContext, NotificationUtils.CHANNEL_SYNC_IO_ERRORS)
.setSmallIcon(R.drawable.ic_sync_problem_notify)
.setContentTitle(account.name)
.setContentText(applicationContext.getString(R.string.sync_error_retry_limit_reached))
.setSubText(account.name)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_ERROR)
.build()
NotificationUtils.NOTIFY_SYNC_ERROR
)
return@withContext Result.failure(syncResult)
}
// If no soft error found, dismiss sync error notification
notificationManager.cancel(
softErrorNotificationTag,
NotificationUtils.NOTIFY_SYNC_ERROR
)
// On a hard error - fail with an error message
// Note: SyncManager should have notified the user
if (result.hasHardError()) {
Logger.log.warning("Hard error while syncing: result=$result, stats=${result.stats}")
return@withContext Result.failure(syncResult)
// On a hard error - fail with an error message
// Note: SyncManager should have notified the user
if (result.hasHardError()) {
Logger.log.warning("Hard error while syncing: result=$result, stats=${result.stats}")
return@withContext Result.failure(syncResult)
}
}
}

View file

@ -7,7 +7,6 @@ import android.accounts.Account
import android.accounts.AccountManager
import android.app.Application
import android.app.usage.UsageStatsManager
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
@ -834,20 +833,15 @@ class DebugInfoActivity : AppCompatActivity() {
val table = TextTable("Authority", "isSyncable", "syncAutomatically", "Interval", "Entries")
for (info in infos) {
var nrEntries = ""
var client: ContentProviderClient? = null
if (info.countUri != null)
try {
client = context.contentResolver.acquireContentProviderClient(info.authority)
if (client != null)
context.contentResolver.acquireContentProviderClient(info.authority)?.use { client ->
client.query(info.countUri, null, null, null, null)?.use { cursor ->
nrEntries = "${cursor.count} ${info.countStr}"
}
else
nrEntries = "n/a"
}
} catch (e: Exception) {
nrEntries = e.toString()
} finally {
client?.close()
}
val accountSettings = AccountSettings(context, account)
table.addLine(