You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/org/tasks/jobs/WorkManagerImpl.kt

253 lines
11 KiB
Kotlin

4 years ago
package org.tasks.jobs
import android.annotation.SuppressLint
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.work.*
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task
import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDao
4 years ago
import org.tasks.data.Place
import org.tasks.date.DateTimeUtils.midnight
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.db.SuspendDbUtils.eachChunk
4 years ago
import org.tasks.jobs.WorkManager.Companion.MAX_CLEANUP_LENGTH
import org.tasks.jobs.WorkManager.Companion.REMOTE_CONFIG_INTERVAL_HOURS
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_CALDAV
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_ETESYNC
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_GOOGLE_TASKS
4 years ago
import org.tasks.jobs.WorkManager.Companion.TAG_BACKUP
import org.tasks.jobs.WorkManager.Companion.TAG_MIDNIGHT_REFRESH
import org.tasks.jobs.WorkManager.Companion.TAG_REFRESH
import org.tasks.jobs.WorkManager.Companion.TAG_REMOTE_CONFIG
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_CALDAV
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_ETESYNC
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_GOOGLE_TASKS
import org.tasks.notifications.Throttle
4 years ago
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils
import timber.log.Timber
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.max
class WorkManagerImpl constructor(
private val context: Context,
private val preferences: Preferences,
private val googleTaskListDao: GoogleTaskListDao,
private val caldavDao: CaldavDao): WorkManager {
private val throttle = Throttle(200, 60000, "WORK")
4 years ago
private val alarmManager: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
private val workManager = androidx.work.WorkManager.getInstance(context)
private suspend fun enqueue(builder: WorkRequest.Builder<*, *>) {
throttle.run {
workManager.enqueue(builder.build())
}
}
private suspend fun enqueue(continuation: WorkContinuation) {
throttle.run {
continuation.enqueue()
}
}
override suspend fun afterComplete(task: Task) {
enqueue(
4 years ago
OneTimeWorkRequest.Builder(AfterSaveWork::class.java)
.setInputData(Data.Builder()
.putLong(AfterSaveWork.EXTRA_ID, task.id)
.build()))
4 years ago
}
override suspend fun cleanup(ids: Iterable<Long>) {
ids.eachChunk(MAX_CLEANUP_LENGTH) {
enqueue(
4 years ago
OneTimeWorkRequest.Builder(CleanupWork::class.java)
.setInputData(
Data.Builder()
.putLongArray(CleanupWork.EXTRA_TASK_IDS, it.toLongArray())
.build()))
4 years ago
}
}
override suspend fun googleTaskSync(immediate: Boolean) =
sync(immediate, TAG_SYNC_GOOGLE_TASKS, SyncGoogleTasksWork::class.java)
override suspend fun caldavSync(immediate: Boolean) =
sync(immediate, TAG_SYNC_CALDAV, SyncCaldavWork::class.java)
override suspend fun eteSync(immediate: Boolean) =
sync(immediate, TAG_SYNC_ETESYNC, SyncEteSyncWork::class.java)
private suspend fun sync(immediate: Boolean, tag: String, c: Class<out SyncWork>) {
4 years ago
val constraints = Constraints.Builder()
.setRequiredNetworkType(
if (!immediate && preferences.getBoolean(R.string.p_background_sync_unmetered_only, false)) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
})
.build()
val builder = OneTimeWorkRequest.Builder(c).setConstraints(constraints)
4 years ago
if (!immediate) {
builder.setInitialDelay(1, TimeUnit.MINUTES)
}
throttle.run {
workManager
.beginUniqueWork(tag, ExistingWorkPolicy.REPLACE, builder.build())
.enqueue()
}
4 years ago
}
override suspend fun reverseGeocode(place: Place) {
4 years ago
if (BuildConfig.DEBUG && place.id == 0L) {
throw RuntimeException("Missing id")
}
enqueue(
4 years ago
OneTimeWorkRequest.Builder(ReverseGeocodeWork::class.java)
.setInputData(Data.Builder().putLong(ReverseGeocodeWork.PLACE_ID, place.id).build())
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()))
4 years ago
}
override suspend fun updateBackgroundSync() {
updateBackgroundSync(null, null, null)
}
4 years ago
@SuppressLint("CheckResult")
override suspend fun updateBackgroundSync(
4 years ago
forceAccountPresent: Boolean?,
forceBackgroundEnabled: Boolean?,
forceOnlyOnUnmetered: Boolean?) {
val backgroundEnabled = forceBackgroundEnabled
?: preferences.getBoolean(R.string.p_background_sync, true)
val onlyOnWifi = forceOnlyOnUnmetered
?: preferences.getBoolean(R.string.p_background_sync_unmetered_only, false)
val accountsPresent = forceAccountPresent == true
|| googleTaskListDao.accountCount() > 0
|| caldavDao.accountCount() > 0
scheduleBackgroundSync(backgroundEnabled && accountsPresent, onlyOnWifi)
4 years ago
}
private suspend fun scheduleBackgroundSync(enabled: Boolean, onlyOnUnmetered: Boolean) {
4 years ago
Timber.d("background sync enabled: %s, onlyOnUnmetered: %s", enabled, onlyOnUnmetered)
scheduleBackgroundSync(enabled, onlyOnUnmetered, TAG_BACKGROUND_SYNC_GOOGLE_TASKS, SyncGoogleTasksWork::class.java)
scheduleBackgroundSync(enabled, onlyOnUnmetered, TAG_BACKGROUND_SYNC_CALDAV, SyncCaldavWork::class.java)
scheduleBackgroundSync(enabled, onlyOnUnmetered, TAG_BACKGROUND_SYNC_ETESYNC, SyncEteSyncWork::class.java)
}
private suspend fun scheduleBackgroundSync(enabled: Boolean, onlyOnUnmetered: Boolean, tag: String, c: Class<out SyncWork>) {
throttle.run {
if (enabled) {
workManager.enqueueUniquePeriodicWork(
tag,
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequest.Builder(c, 1, TimeUnit.HOURS)
.setConstraints(getNetworkConstraints(onlyOnUnmetered))
.build())
} else {
workManager.cancelUniqueWork(tag)
}
4 years ago
}
}
override suspend fun scheduleRefresh(time: Long) = enqueueUnique(TAG_REFRESH, RefreshWork::class.java, time)
4 years ago
override suspend fun scheduleMidnightRefresh() =
4 years ago
enqueueUnique(TAG_MIDNIGHT_REFRESH, MidnightRefreshWork::class.java, midnight())
override fun scheduleNotification(scheduledTime: Long) {
val time = max(DateUtilities.now(), scheduledTime)
if (time < DateTimeUtils.currentTimeMillis()) {
val intent = notificationIntent
if (AndroidUtilities.atLeastOreo()) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
} else {
val pendingIntent = notificationPendingIntent
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pendingIntent)
}
}
override suspend fun scheduleBackup() =
enqueueUnique(
TAG_BACKUP,
BackupWork::class.java,
newDateTime(preferences.getLong(R.string.p_last_backup, 0L))
.plusDays(1)
.millis
.coerceAtMost(midnight()))
override suspend fun scheduleConfigRefresh() {
throttle.run {
workManager.enqueueUniquePeriodicWork(
TAG_REMOTE_CONFIG,
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequest.Builder(
RemoteConfigWork::class.java, REMOTE_CONFIG_INTERVAL_HOURS, TimeUnit.HOURS)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build())
}
4 years ago
}
override suspend fun scheduleDriveUpload(uri: Uri, purge: Boolean) {
4 years ago
if (!preferences.getBoolean(R.string.p_google_drive_backup, false)) {
return
}
val builder = OneTimeWorkRequest.Builder(DriveUploader::class.java)
.setInputData(DriveUploader.getInputData(uri, purge))
.setConstraints(networkConstraints)
if (purge) {
builder.setInitialDelay(Random().nextInt(3600).toLong(), TimeUnit.SECONDS)
}
enqueue(builder)
4 years ago
}
private val networkConstraints: Constraints
get() = getNetworkConstraints(
preferences.getBoolean(R.string.p_background_sync_unmetered_only, false))
private fun getNetworkConstraints(unmeteredOnly: Boolean) =
Constraints.Builder()
.setRequiredNetworkType(if (unmeteredOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)
.build()
@SuppressLint("EnqueueWork")
private suspend fun enqueueUnique(key: String, c: Class<out Worker?>, time: Long) {
4 years ago
val delay = time - DateUtilities.now()
val builder = OneTimeWorkRequest.Builder(c)
if (delay > 0) {
builder.setInitialDelay(delay, TimeUnit.MILLISECONDS)
}
Timber.d("$key: ${DateTimeUtils.printTimestamp(time)} (${DateTimeUtils.printDuration(delay)})")
enqueue(workManager.beginUniqueWork(key, ExistingWorkPolicy.REPLACE, builder.build()))
4 years ago
}
override fun cancelNotifications() {
Timber.d("cancelNotifications")
alarmManager.cancel(notificationPendingIntent)
}
private val notificationIntent: Intent
get() = Intent(context, NotificationService::class.java)
private val notificationPendingIntent: PendingIntent
get() {
return if (AndroidUtilities.atLeastOreo()) {
PendingIntent.getForegroundService(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
} else {
PendingIntent.getService(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
}
}