Block sync adapter's onPerformSync until SyncWorker finishes (bitfireAT/davx5#278)

* Block sync framework until SyncWorker finishes

* Bump version code for 4.3.3 (previous version code was never released publicly)

* Fetch translations from Transifex

* Release internal version automatically [skip ci]

* Update periodic sync workers when "Sync only on WiFi" flag is changed (#282)

* Update periodic sync workers when "sync only on WiFi" flag is changed
* Remove BootCompletedReceiver which was only needed to repair sync intervals (not required with WorkManager anymore)

* Bump version code to 403030006 (stays 4.3.3)

* Use unique worker name, Java notify/wait and observeForever

* Remove observer when sync finished

* Catch and ignore, but log interruption exceptions

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
Sunik Kupfer 2023-07-03 12:04:34 +02:00 committed by Ricki Hirner
parent 46488f1618
commit fac68680ee
No known key found for this signature in database
GPG key ID: 79A019FCAAEDD3AA
2 changed files with 57 additions and 7 deletions

View file

@ -13,9 +13,14 @@ import android.content.Context
import android.content.Intent
import android.content.SyncResult
import android.os.Bundle
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
import androidx.work.WorkManager
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.settings.AccountSettings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import java.util.logging.Level
abstract class SyncAdapterService: Service() {
@ -44,7 +49,7 @@ abstract class SyncAdapterService: Service() {
override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
// We seem to have to pass this old SyncFramework extra for an Android 7 workaround
val upload = extras.containsKey(ContentResolver.SYNC_EXTRAS_UPLOAD)
Logger.log.info("Sync request via sync adapter (upload=$upload)")
Logger.log.info("Sync request via sync framework (upload=$upload)")
val accountSettings = try {
AccountSettings(context, account)
@ -55,12 +60,54 @@ abstract class SyncAdapterService: Service() {
// Should we run the sync at all?
if (!SyncWorker.wifiConditionsMet(context, accountSettings)) {
Logger.log.info("Sync conditions not met. Aborting sync adapter")
Logger.log.info("Sync conditions not met. Aborting sync framework initiated sync")
return
}
Logger.log.fine("Sync adapter now handing over to SyncWorker")
SyncWorker.enqueue(context, account, authority, upload = upload)
Logger.log.fine("Sync framework now starting SyncWorker")
val workerName = SyncWorker.enqueue(context, account, authority, upload = upload)
// Block the onPerformSync method to simulate an ongoing sync
Logger.log.fine("Blocking sync framework until SyncWorker finishes")
// Because we are not allowed to observe worker state on a background thread, we can not
// use it to block the sync adapter. Instead we check periodically whether the sync has
// finished, putting the thread to sleep in between checks.
val workManager = WorkManager.getInstance(context)
val status = workManager.getWorkInfosForUniqueWorkLiveData(workerName)
var finished = false
val lock = Object()
val observer = Observer<List<WorkInfo>> { workInfoList ->
for (workInfo in workInfoList) {
if (workInfo.state.isFinished) {
synchronized(lock) {
finished = true
lock.notify()
}
}
}
}
runBlocking(Dispatchers.Main) { // observeForever not allowed in background thread
status.observeForever(observer)
}
synchronized(lock) {
try {
if (!finished)
lock.wait(10*60*1000) // wait max 10 minutes
} catch (e: InterruptedException) {
Logger.log.info("Interrupted while blocking sync framework. Sync may still be running")
}
}
runBlocking(Dispatchers.Main) {
status.removeObserver(observer)
}
Logger.log.info("Returning to sync framework")
}
override fun onSecurityException(account: Account, extras: Bundle, authority: String, syncResult: SyncResult) {

View file

@ -118,6 +118,7 @@ class SyncWorker @AssistedInject constructor(
* @param resync whether to request (full) re-synchronization or not
* @param upload see [ContentResolver.SYNC_EXTRAS_UPLOAD] used only for contacts sync
* and android 7 workaround
* @return existing or newly created worker name
*/
fun enqueue(
context: Context,
@ -125,7 +126,7 @@ class SyncWorker @AssistedInject constructor(
authority: String,
@ArgResync resync: Int = NO_RESYNC,
upload: Boolean = false
) {
): String {
// Worker arguments
val argumentsBuilder = Data.Builder()
.putString(ARG_AUTHORITY, authority)
@ -152,14 +153,16 @@ class SyncWorker @AssistedInject constructor(
.build()
// enqueue and start syncing
Logger.log.log(Level.INFO, "Enqueueing unique worker: ${workerName(account, authority)}")
val name = workerName(account, authority)
Logger.log.log(Level.INFO, "Enqueueing unique worker: $name")
WorkManager.getInstance(context).enqueueUniqueWork(
workerName(account, authority),
name,
ExistingWorkPolicy.KEEP, // If sync is already running, just continue.
// Existing retried work will not be replaced (for instance when
// PeriodicSyncWorker enqueues another scheduled sync).
workRequest
)
return name
}
/**