Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit fac68680 authored by Sunik Kupfer's avatar Sunik Kupfer Committed by Ricki Hirner
Browse files

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: default avatarRicki Hirner <hirner@bitfire.at>
parent 46488f16
Loading
Loading
Loading
Loading
+51 −4
Original line number Diff line number Diff line
@@ -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) {
+6 −3
Original line number Diff line number Diff line
@@ -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
        }

        /**