Skip to content

Commit cae1ed5

Browse files
authored
More correct usage of expedited workers (#566)
* Add foreground notification type to expedited workers (required for Android 14) * Make SyncWorker a long-running worker * Don't use expedited SyncWorker for everything; handle foreground service launch restriction * AddressBookSyncer: only request expedited for sub-jobs when parent job is expedited, too * RefreshCollectionsWorker is not long-running -> no foreground service type needed * Fix tests * Don't use foreground service type in ForegroundInfo * Make SyncWorker not long-running
1 parent e5cf761 commit cae1ed5

File tree

7 files changed

+39
-14
lines changed

7 files changed

+39
-14
lines changed

app/src/androidTestOse/kotlin/at/bitfire/davdroid/syncadapter/SyncWorkerTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class SyncWorkerTest {
7272

7373
@Test
7474
fun testEnqueue_enqueuesWorker() {
75-
SyncWorker.enqueue(context, account, CalendarContract.AUTHORITY)
75+
SyncWorker.enqueue(context, account, CalendarContract.AUTHORITY, true)
7676
val workerName = SyncWorker.workerName(account, CalendarContract.AUTHORITY)
7777
assertTrue(workScheduledOrRunningOrSuccessful(context, workerName))
7878
}

app/src/main/kotlin/at/bitfire/davdroid/servicedetection/RefreshCollectionsWorker.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ import kotlin.collections.*
8484
* - adds resources if new ones are detected
8585
* - removes resources if not found 40x (delete locally)
8686
*
87+
* Expedited: yes (always initiated by user)
88+
*
89+
* Long-running: no
90+
*
8791
* @throws IllegalArgumentException when there's no service with the given service ID
8892
*/
8993
@HiltWorker
@@ -577,4 +581,4 @@ class RefreshCollectionsWorker @AssistedInject constructor(
577581
}
578582
}
579583

580-
}
584+
}

app/src/main/kotlin/at/bitfire/davdroid/syncadapter/AddressBookSyncer.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import java.util.logging.Level
3030
/**
3131
* Sync logic for address books
3232
*/
33-
class AddressBookSyncer(context: Context): Syncer(context) {
33+
class AddressBookSyncer(
34+
context: Context,
35+
private val expedited: Boolean
36+
) : Syncer(context) {
3437

3538
@EntryPoint
3639
@InstallIn(SingletonComponent::class)
@@ -53,7 +56,7 @@ class AddressBookSyncer(context: Context): Syncer(context) {
5356
if (updateLocalAddressBooks(account, syncResult))
5457
for (addressBookAccount in LocalAddressBook.findAll(context, null, account).map { it.account }) {
5558
Logger.log.log(Level.INFO, "Running sync for address book", addressBookAccount)
56-
SyncWorker.enqueue(context, addressBookAccount, ContactsContract.AUTHORITY)
59+
SyncWorker.enqueue(context, addressBookAccount, ContactsContract.AUTHORITY, expedited = expedited)
5760
}
5861
} catch (e: Exception) {
5962
Logger.log.log(Level.SEVERE, "Couldn't sync address books", e)

app/src/main/kotlin/at/bitfire/davdroid/syncadapter/PeriodicSyncWorker.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ import java.util.concurrent.TimeUnit
3232
* The different periodic sync workers each carry a unique work name composed of the account and
3333
* authority which they are responsible for. For each account there will be multiple dedicated periodic
3434
* sync workers for each authority. See [PeriodicSyncWorker.workerName] for more information.
35+
*
36+
* Deferrable: yes (PeriodicWorkRequest)
37+
*
38+
* Long-running: no
3539
*/
3640
@HiltWorker
3741
class PeriodicSyncWorker @AssistedInject constructor(
@@ -121,7 +125,7 @@ class PeriodicSyncWorker @AssistedInject constructor(
121125

122126
// Just request immediate sync
123127
Logger.log.info("Requesting immediate sync")
124-
SyncWorker.enqueue(applicationContext, account, authority)
128+
SyncWorker.enqueue(applicationContext, account, authority, expedited = false)
125129
return Result.success()
126130
}
127131
}

app/src/main/kotlin/at/bitfire/davdroid/syncadapter/SyncAdapterServices.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ abstract class SyncAdapterService: Service() {
7979
}
8080

8181
Logger.log.fine("Sync framework now starting SyncWorker")
82-
val workerName = SyncWorker.enqueue(context, account, authority, upload = upload)
82+
val workerName = SyncWorker.enqueue(context, account, authority, expedited = true, upload = upload)
8383

8484
// Block the onPerformSync method to simulate an ongoing sync
8585
Logger.log.fine("Blocking sync framework until SyncWorker finishes")

app/src/main/kotlin/at/bitfire/davdroid/syncadapter/SyncWorker.kt

+21-7
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,17 @@ import java.util.logging.Level
6363
*
6464
* By enqueuing this worker ([SyncWorker.enqueue]) a sync will be started immediately (as soon as
6565
* possible). Currently, there are three scenarios starting a sync:
66+
*
6667
* 1) *manual sync*: User presses an in-app sync button and enqueues this worker directly.
6768
* 2) *periodic sync*: User defines time interval to sync in app settings. The [PeriodicSyncWorker] runs
6869
* in the background and enqueues this worker when due.
6970
* 3) *content-triggered sync*: User changes a calendar event, task or contact, or presses a sync
7071
* button in one of the responsible apps. The [SyncAdapterService] is notified of this and enqueues
7172
* this worker.
7273
*
74+
* Expedited: when run manually
75+
*
76+
* Long-running: no
7377
*/
7478
@HiltWorker
7579
class SyncWorker @AssistedInject constructor(
@@ -84,6 +88,9 @@ class SyncWorker @AssistedInject constructor(
8488
internal const val ARG_ACCOUNT_TYPE = "accountType"
8589
internal const val ARG_AUTHORITY = "authority"
8690

91+
/** Boolean. Set to `true` when the job was requested as expedited job. */
92+
private const val ARG_EXPEDITED = "expedited"
93+
8794
private const val ARG_UPLOAD = "upload"
8895

8996
private const val ARG_RESYNC = "resync"
@@ -129,7 +136,7 @@ class SyncWorker @AssistedInject constructor(
129136
upload: Boolean = false
130137
) {
131138
for (authority in SyncUtils.syncAuthorities(context))
132-
enqueue(context, account, authority, resync, upload)
139+
enqueue(context, account, authority, expedited = true, resync = resync, upload = upload)
133140
}
134141

135142
/**
@@ -146,6 +153,7 @@ class SyncWorker @AssistedInject constructor(
146153
context: Context,
147154
account: Account,
148155
authority: String,
156+
expedited: Boolean,
149157
@ArgResync resync: Int = NO_RESYNC,
150158
upload: Boolean = false
151159
): String {
@@ -154,6 +162,8 @@ class SyncWorker @AssistedInject constructor(
154162
.putString(ARG_AUTHORITY, authority)
155163
.putString(ARG_ACCOUNT_NAME, account.name)
156164
.putString(ARG_ACCOUNT_TYPE, account.type)
165+
if (expedited)
166+
argumentsBuilder.putBoolean(ARG_EXPEDITED, true)
157167
if (resync != NO_RESYNC)
158168
argumentsBuilder.putInt(ARG_RESYNC, resync)
159169
argumentsBuilder.putBoolean(ARG_UPLOAD, upload)
@@ -165,7 +175,6 @@ class SyncWorker @AssistedInject constructor(
165175
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
166176
.addTag(workerName(account, authority))
167177
.setInputData(argumentsBuilder.build())
168-
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
169178
.setBackoffCriteria(
170179
BackoffPolicy.EXPONENTIAL,
171180
WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS, // 30 sec
@@ -179,17 +188,20 @@ class SyncWorker @AssistedInject constructor(
179188
addTag(workerName(mainAccount, authority))
180189
}
181190
}
182-
.build()
191+
192+
if (expedited)
193+
workRequest.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
183194

184195
// enqueue and start syncing
185196
val name = workerName(account, authority)
186-
Logger.log.log(Level.INFO, "Enqueueing unique worker: $name, with tags: ${workRequest.tags}")
197+
val request = workRequest.build()
198+
Logger.log.log(Level.INFO, "Enqueueing unique worker: $name, expedited = $expedited, tags = ${request.tags}")
187199
WorkManager.getInstance(context).enqueueUniqueWork(
188200
name,
189201
ExistingWorkPolicy.KEEP, // If sync is already running, just continue.
190202
// Existing retried work will not be replaced (for instance when
191203
// PeriodicSyncWorker enqueues another scheduled sync).
192-
workRequest
204+
request
193205
)
194206
return name
195207
}
@@ -305,8 +317,9 @@ class SyncWorker @AssistedInject constructor(
305317
inputData.getString(ARG_ACCOUNT_TYPE) ?: throw IllegalArgumentException("$ARG_ACCOUNT_TYPE required")
306318
)
307319
val authority = inputData.getString(ARG_AUTHORITY) ?: throw IllegalArgumentException("$ARG_AUTHORITY required")
320+
val expedited = inputData.getBoolean(ARG_EXPEDITED, false)
308321

309-
// Check internet connection
322+
// check internet connection
310323
val ignoreVpns = AccountSettings(applicationContext, account).getIgnoreVpns()
311324
val connectivityManager = applicationContext.getSystemService<ConnectivityManager>()!!
312325
if (!internetAvailable(connectivityManager, ignoreVpns)) {
@@ -319,7 +332,7 @@ class SyncWorker @AssistedInject constructor(
319332
// What are we going to sync? Select syncer based on authority
320333
val syncer: Syncer = when (authority) {
321334
applicationContext.getString(R.string.address_books_authority) ->
322-
AddressBookSyncer(applicationContext)
335+
AddressBookSyncer(applicationContext, expedited)
323336
CalendarContract.AUTHORITY ->
324337
CalendarSyncer(applicationContext)
325338
ContactsContract.AUTHORITY ->
@@ -438,6 +451,7 @@ class SyncWorker @AssistedInject constructor(
438451
.setCategory(NotificationCompat.CATEGORY_STATUS)
439452
.setOngoing(true)
440453
.setPriority(NotificationCompat.PRIORITY_LOW)
454+
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
441455
.build()
442456
return ForegroundInfo(NotificationUtils.NOTIFY_SYNC_EXPEDITED, notification)
443457
}

app/src/main/kotlin/at/bitfire/davdroid/ui/account/SettingsActivity.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ class SettingsActivity: AppCompatActivity() {
603603
*/
604604
private fun resync(authority: String, fullResync: Boolean) {
605605
val resync = if (fullResync) SyncWorker.FULL_RESYNC else SyncWorker.RESYNC
606-
SyncWorker.enqueue(getApplication(), account, authority, resync)
606+
SyncWorker.enqueue(getApplication(), account, authority, expedited = true, resync = resync)
607607
}
608608

609609
}

0 commit comments

Comments
 (0)