mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-07-23 11:39:15 +00:00
Include address book account syncs, when querying sync status (bitfireAT/davx5#378)
* Use tags instead of uniqueWorkNames for work queries. * Also include address book accounts, when querying sync status. Give address book account sync workers their parent (main account) sync workers tag too, such that they will be included at the query for sync status of their parent account. --------- Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
parent
6db1473e00
commit
6bbdcb332f
|
@ -19,10 +19,11 @@ import java.util.concurrent.TimeUnit
|
|||
/**
|
||||
* Handles scheduled sync requests.
|
||||
*
|
||||
* Enqueues immediate [SyncWorker] syncs at the appropriate moment. This will prevent the actual
|
||||
* sync code from running twice simultaneously (for manual and scheduled sync).
|
||||
* Enqueues immediate [SyncWorker] syncs at the appropriate moment.
|
||||
*
|
||||
* For each account there will be multiple dedicated workers running for each authority.
|
||||
* The different periodic sync workers each carry a unique work name composed of the account and
|
||||
* authority which they are responsible for. For each account there will be multiple dedicated periodic
|
||||
* sync workers for each authority. See [PeriodicSyncWorker.workerName] for more information.
|
||||
*/
|
||||
@HiltWorker
|
||||
class PeriodicSyncWorker @AssistedInject constructor(
|
||||
|
@ -31,21 +32,22 @@ class PeriodicSyncWorker @AssistedInject constructor(
|
|||
) : Worker(appContext, workerParams) {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val WORKER_TAG = "periodic-sync"
|
||||
|
||||
// Worker input parameters
|
||||
internal const val ARG_ACCOUNT_NAME = "accountName"
|
||||
internal const val ARG_ACCOUNT_TYPE = "accountType"
|
||||
internal const val ARG_AUTHORITY = "authority"
|
||||
|
||||
/**
|
||||
* Name of this worker.
|
||||
* Used to distinguish between other work processes. A worker names are unique. There can
|
||||
* never be two running workers with the same name.
|
||||
* Unique work name of this worker. Can also be used as tag.
|
||||
*
|
||||
* Mainly used to query [WorkManager] for work state (by unique work name or tag).
|
||||
*
|
||||
* @param account the account this worker is running for
|
||||
* @param authority the authority this worker is running for
|
||||
* @return Name of this worker composed as "sync $authority ${account.type}/${account.name}"
|
||||
*/
|
||||
fun workerName(account: Account, authority: String): String =
|
||||
"$WORKER_TAG $authority ${account.type}/${account.name}"
|
||||
"periodic-sync $authority ${account.type}/${account.name}"
|
||||
|
||||
/**
|
||||
* Activate scheduled synchronization of an account with a specific authority.
|
||||
|
@ -69,7 +71,6 @@ class PeriodicSyncWorker @AssistedInject constructor(
|
|||
NetworkType.CONNECTED
|
||||
).build()
|
||||
val workRequest = PeriodicWorkRequestBuilder<PeriodicSyncWorker>(interval, TimeUnit.SECONDS)
|
||||
.addTag(WORKER_TAG)
|
||||
.setInputData(arguments)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
@ -104,7 +105,7 @@ class PeriodicSyncWorker @AssistedInject constructor(
|
|||
WorkManager.getInstance(context)
|
||||
.getWorkInfos(
|
||||
WorkQuery.Builder
|
||||
.fromUniqueWorkNames(listOf(workerName(account, authority)))
|
||||
.fromTags(listOf(workerName(account, authority)))
|
||||
.addStates(listOf(WorkInfo.State.ENQUEUED, WorkInfo.State.RUNNING))
|
||||
.build()
|
||||
).get()
|
||||
|
|
|
@ -15,6 +15,7 @@ import android.graphics.drawable.BitmapDrawable
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.CalendarContract
|
||||
import android.provider.ContactsContract
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
@ -89,22 +90,26 @@ object SyncUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all available sync authorities for main accounts (!= address book accounts):
|
||||
* Returns a list of all available sync authorities:
|
||||
*
|
||||
* 1. address books authority (not [ContactsContract.AUTHORITY], but the one which manages address book accounts)
|
||||
* 1. calendar authority
|
||||
* 1. tasks authority (if available)
|
||||
* 2. contacts authority (only if [withContacts] is *true* - mostly we don't want it included)
|
||||
* 3. address books authority
|
||||
* 4. tasks authority/ies (if available, when tasks managing app(s) installed)
|
||||
*
|
||||
* Checking the availability of authorities may be relatively expensive, so the
|
||||
* result should be cached for the current operation.
|
||||
*
|
||||
* @param withContacts whether to add contacts authority
|
||||
* @return list of available sync authorities for main accounts
|
||||
*/
|
||||
fun syncAuthorities(context: Context): List<String> {
|
||||
fun syncAuthorities(context: Context, withContacts: Boolean = false): List<String> {
|
||||
val result = mutableListOf(
|
||||
context.getString(R.string.address_books_authority),
|
||||
CalendarContract.AUTHORITY
|
||||
CalendarContract.AUTHORITY,
|
||||
context.getString(R.string.address_books_authority)
|
||||
)
|
||||
if (withContacts)
|
||||
result.add(ContactsContract.AUTHORITY)
|
||||
TaskUtils.currentProvider(context)?.let { taskProvider ->
|
||||
result += taskProvider.authority
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import at.bitfire.davdroid.R
|
|||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.network.ConnectionUtils.internetAvailable
|
||||
import at.bitfire.davdroid.network.ConnectionUtils.wifiAvailable
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.ui.NotificationUtils
|
||||
import at.bitfire.davdroid.ui.NotificationUtils.notifyIfPossible
|
||||
|
@ -55,7 +56,21 @@ import java.util.concurrent.TimeUnit
|
|||
import java.util.logging.Level
|
||||
|
||||
/**
|
||||
* Handles immediate sync requests, status queries and cancellation for one or multiple authorities
|
||||
* Handles immediate sync requests and cancellations of accounts and respective content authorities,
|
||||
* by creating appropriate workers.
|
||||
*
|
||||
* The different sync workers each carry a unique work name composed of the account and authority they
|
||||
* are syncing. See [SyncWorker.workerName] for more information.
|
||||
*
|
||||
* By enqueuing this worker ([SyncWorker.enqueue]) a sync will be started immediately (as soon as
|
||||
* possible). Currently, there are three scenarios starting a sync:
|
||||
* 1) *manual sync*: User presses an in-app sync button and enqueues this worker directly.
|
||||
* 2) *periodic sync*: User defines time interval to sync in app settings. The [PeriodicSyncWorker] runs
|
||||
* in the background and enqueues this worker when due.
|
||||
* 3) *content-triggered sync*: User changes a calendar event, task or contact, or presses a sync
|
||||
* button in one of the responsible apps. The [SyncAdapterService] is notified of this and enqueues
|
||||
* this worker.
|
||||
*
|
||||
*/
|
||||
@HiltWorker
|
||||
class SyncWorker @AssistedInject constructor(
|
||||
|
@ -79,9 +94,6 @@ class SyncWorker @AssistedInject constructor(
|
|||
const val RESYNC = 1
|
||||
const val FULL_RESYNC = 2
|
||||
|
||||
// This SyncWorker's tag
|
||||
const val TAG_SYNC = "sync"
|
||||
|
||||
/**
|
||||
* How often this work will be retried to run after soft (network) errors.
|
||||
*
|
||||
|
@ -90,11 +102,20 @@ class SyncWorker @AssistedInject constructor(
|
|||
internal const val MAX_RUN_ATTEMPTS = 5
|
||||
|
||||
/**
|
||||
* Name of this worker.
|
||||
* Used to distinguish between other work processes. There must only ever be one worker with the exact same name.
|
||||
* Unique work name of this worker. Can also be used as tag.
|
||||
*
|
||||
* Mainly used to query [WorkManager] for work state (by unique work name or tag).
|
||||
*
|
||||
* *NOTE:* SyncWorkers for address book accounts bear the unique worker name of their parent
|
||||
* account (main account) as tag. This makes it easier to query the overall sync status of a
|
||||
* main account.
|
||||
*
|
||||
* @param account the account this worker is running for
|
||||
* @param authority the authority this worker is running for
|
||||
* @return Name of this worker composed as "sync $authority ${account.type}/${account.name}"
|
||||
*/
|
||||
fun workerName(account: Account, authority: String) =
|
||||
"$TAG_SYNC $authority ${account.type}/${account.name}"
|
||||
"sync $authority ${account.type}/${account.name}"
|
||||
|
||||
/**
|
||||
* Requests immediate synchronization of an account with all applicable
|
||||
|
@ -143,7 +164,6 @@ class SyncWorker @AssistedInject constructor(
|
|||
.setRequiredNetworkType(NetworkType.CONNECTED) // require a network connection
|
||||
.build()
|
||||
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||
.addTag(TAG_SYNC)
|
||||
.setInputData(argumentsBuilder.build())
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.setBackoffCriteria(
|
||||
|
@ -152,11 +172,18 @@ class SyncWorker @AssistedInject constructor(
|
|||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
.setConstraints(constraints)
|
||||
.apply {
|
||||
// If this is a sub sync worker (address book sync), add the main account tag as well
|
||||
if (account.type == context.getString(R.string.account_type_address_book)) {
|
||||
val mainAccount = LocalAddressBook.mainAccount(context, account)
|
||||
addTag(workerName(mainAccount, authority))
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
// enqueue and start syncing
|
||||
val name = workerName(account, authority)
|
||||
Logger.log.log(Level.INFO, "Enqueueing unique worker: $name")
|
||||
Logger.log.log(Level.INFO, "Enqueueing unique worker: $name, with tags: ${workRequest.tags}")
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
name,
|
||||
ExistingWorkPolicy.KEEP, // If sync is already running, just continue.
|
||||
|
@ -191,10 +218,9 @@ class SyncWorker @AssistedInject constructor(
|
|||
authorities: List<String>? = null
|
||||
): LiveData<Boolean> {
|
||||
val workQuery = WorkQuery.Builder
|
||||
.fromTags(listOf(TAG_SYNC))
|
||||
.addStates(workStates)
|
||||
.fromStates(workStates)
|
||||
if (account != null && authorities != null)
|
||||
workQuery.addUniqueWorkNames(
|
||||
workQuery.addTags(
|
||||
authorities.map { authority -> workerName(account, authority) }
|
||||
)
|
||||
return WorkManager.getInstance(context)
|
||||
|
|
|
@ -40,7 +40,6 @@ import androidx.work.WorkQuery
|
|||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.databinding.AccountListBinding
|
||||
import at.bitfire.davdroid.databinding.AccountListItemBinding
|
||||
import at.bitfire.davdroid.syncadapter.SyncUtils
|
||||
import at.bitfire.davdroid.syncadapter.SyncUtils.syncAuthorities
|
||||
import at.bitfire.davdroid.syncadapter.SyncWorker
|
||||
import at.bitfire.davdroid.ui.account.AccountActivity
|
||||
|
@ -261,7 +260,7 @@ class AccountListFragment: Fragment() {
|
|||
val accountsWithInfo = sortedAccounts.map { account ->
|
||||
AccountInfo(
|
||||
account,
|
||||
SyncStatus.fromAccount(context, account, syncAuthorities)
|
||||
SyncStatus.fromAccount(context, account)
|
||||
)
|
||||
}
|
||||
value = accountsWithInfo
|
||||
|
@ -269,7 +268,6 @@ class AccountListFragment: Fragment() {
|
|||
}
|
||||
|
||||
private val accountManager = AccountManager.get(application)!!
|
||||
private val syncAuthorities by lazy { SyncUtils.syncAuthorities(application) }
|
||||
|
||||
init {
|
||||
// watch accounts
|
||||
|
@ -297,17 +295,16 @@ class AccountListFragment: Fragment() {
|
|||
* sub-accounts (address book accounts).
|
||||
*
|
||||
* @param account account to check
|
||||
* @param authorities sync authorities to check (usually taken from [syncAuthorities])
|
||||
*
|
||||
* @return sync status of the given account
|
||||
*/
|
||||
fun fromAccount(context: Context, account: Account, authorities: List<String>): SyncStatus {
|
||||
val workerNames = authorities.map { authority ->
|
||||
fun fromAccount(context: Context, account: Account): SyncStatus {
|
||||
// Add contacts authority, so sync status of address-book-accounts is also checked
|
||||
val workerNames = syncAuthorities(context, true).map { authority ->
|
||||
SyncWorker.workerName(account, authority)
|
||||
}
|
||||
val workQuery = WorkQuery.Builder
|
||||
.fromTags(listOf(SyncWorker.TAG_SYNC))
|
||||
.addUniqueWorkNames(workerNames)
|
||||
.fromTags(workerNames)
|
||||
.addStates(listOf(WorkInfo.State.RUNNING, WorkInfo.State.ENQUEUED))
|
||||
.build()
|
||||
|
||||
|
|
Loading…
Reference in a new issue