Convert WorkManager to Kotlin

pull/1012/head
Alex Baker 4 years ago
parent 7ca58c413c
commit 1b891f2d7e

@ -1,281 +0,0 @@
package org.tasks.jobs;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo;
import static com.todoroo.andlib.utility.DateUtilities.now;
import static io.reactivex.Single.just;
import static io.reactivex.Single.zip;
import static org.tasks.date.DateTimeUtils.midnight;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.db.DbUtils.batch;
import static org.tasks.jobs.ReverseGeocodeWork.PLACE_ID;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import static org.tasks.time.DateTimeUtils.printDuration;
import static org.tasks.time.DateTimeUtils.printTimestamp;
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.annotation.Nullable;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OneTimeWorkRequest.Builder;
import androidx.work.PeriodicWorkRequest;
import androidx.work.Worker;
import com.google.common.primitives.Longs;
import com.todoroo.astrid.data.Task;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskListDao;
import org.tasks.data.Place;
import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
import timber.log.Timber;
@ApplicationScope
public class WorkManager {
public static final long REMOTE_CONFIG_INTERVAL_HOURS = BuildConfig.DEBUG ? 1 : 12;
private static final int MAX_CLEANUP_LENGTH = 500;
private static final String TAG_BACKUP = "tag_backup";
private static final String TAG_REFRESH = "tag_refresh";
private static final String TAG_MIDNIGHT_REFRESH = "tag_midnight_refresh";
private static final String TAG_SYNC = "tag_sync";
private static final String TAG_BACKGROUND_SYNC = "tag_background_sync";
private static final String TAG_REMOTE_CONFIG = "tag_remote_config";
private final Context context;
private final Preferences preferences;
private final GoogleTaskListDao googleTaskListDao;
private final CaldavDao caldavDao;
private final AlarmManager alarmManager;
private final androidx.work.WorkManager workManager;
@Inject
public WorkManager(
@ForApplication Context context,
Preferences preferences,
GoogleTaskListDao googleTaskListDao,
CaldavDao caldavDao) {
this.context = context;
this.preferences = preferences;
this.googleTaskListDao = googleTaskListDao;
this.caldavDao = caldavDao;
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
workManager = androidx.work.WorkManager.getInstance(context);
}
public void afterSave(Task current, Task original) {
workManager.enqueue(
new OneTimeWorkRequest.Builder(AfterSaveWork.class)
.setInputData(AfterSaveWork.getInputData(current, original))
.build());
}
public void cleanup(Iterable<Long> ids) {
batch(
ids,
MAX_CLEANUP_LENGTH,
b ->
workManager.enqueue(
new Builder(CleanupWork.class)
.setInputData(
new Data.Builder()
.putLongArray(CleanupWork.EXTRA_TASK_IDS, Longs.toArray(b))
.build())
.build()));
}
public void sync(boolean immediate) {
Constraints constraints =
new Constraints.Builder()
.setRequiredNetworkType(
!immediate
&& preferences.getBoolean(R.string.p_background_sync_unmetered_only, false)
? NetworkType.UNMETERED
: NetworkType.CONNECTED)
.build();
Builder builder =
new Builder(SyncWork.class)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(constraints);
if (!immediate) {
builder.setInitialDelay(1, TimeUnit.MINUTES);
}
OneTimeWorkRequest request = builder.build();
workManager.beginUniqueWork(TAG_SYNC, ExistingWorkPolicy.REPLACE, request).enqueue();
}
public void reverseGeocode(Place place) {
if (BuildConfig.DEBUG && place.getId() == 0) {
throw new RuntimeException("Missing id");
}
workManager.enqueue(
new Builder(ReverseGeocodeWork.class)
.setInputData(new Data.Builder().putLong(PLACE_ID, place.getId()).build())
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(
new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build());
}
public void updateBackgroundSync() {
updateBackgroundSync(null, null, null);
}
@SuppressLint("CheckResult")
public void updateBackgroundSync(
@Nullable Boolean forceAccountPresent,
@Nullable Boolean forceBackgroundEnabled,
@Nullable Boolean forceOnlyOnUnmetered) {
boolean backgroundEnabled =
forceBackgroundEnabled == null
? preferences.getBoolean(R.string.p_background_sync, true)
: forceBackgroundEnabled;
boolean onlyOnWifi =
forceOnlyOnUnmetered == null
? preferences.getBoolean(R.string.p_background_sync_unmetered_only, false)
: forceOnlyOnUnmetered;
//noinspection ResultOfMethodCallIgnored
(forceAccountPresent == null
? zip(
googleTaskListDao.accountCount(),
Single.fromCallable(caldavDao::accountCount),
(googleCount, caldavCount) -> googleCount > 0 || caldavCount > 0)
: just(forceAccountPresent))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
accountsPresent ->
scheduleBackgroundSync(backgroundEnabled && accountsPresent, onlyOnWifi));
}
private void scheduleBackgroundSync(boolean enabled, boolean onlyOnUnmetered) {
Timber.d("background sync enabled: %s, onlyOnUnmetered: %s", enabled, onlyOnUnmetered);
if (enabled) {
workManager.enqueueUniquePeriodicWork(
TAG_BACKGROUND_SYNC,
ExistingPeriodicWorkPolicy.KEEP,
new PeriodicWorkRequest.Builder(SyncWork.class, 1, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(getNetworkConstraints(onlyOnUnmetered))
.build());
} else {
workManager.cancelUniqueWork(TAG_BACKGROUND_SYNC);
}
}
public void scheduleRefresh(long time) {
enqueueUnique(TAG_REFRESH, RefreshWork.class, time);
}
public void scheduleMidnightRefresh() {
enqueueUnique(TAG_MIDNIGHT_REFRESH, MidnightRefreshWork.class, midnight());
}
@SuppressWarnings("WeakerAccess")
public void scheduleNotification(long time) {
time = Math.max(now(), time);
if (time < currentTimeMillis()) {
Intent intent = getNotificationIntent();
if (atLeastOreo()) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
} else {
PendingIntent pendingIntent = getNotificationPendingIntent();
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pendingIntent);
}
}
public void scheduleBackup() {
long lastBackup = preferences.getLong(R.string.p_last_backup, 0L);
enqueueUnique(
TAG_BACKUP,
BackupWork.class,
Math.min(newDateTime(lastBackup).plusDays(1).getMillis(), midnight()));
}
public void scheduleConfigRefresh() {
workManager.enqueueUniquePeriodicWork(
TAG_REMOTE_CONFIG,
ExistingPeriodicWorkPolicy.KEEP,
new PeriodicWorkRequest.Builder(
RemoteConfigWork.class, REMOTE_CONFIG_INTERVAL_HOURS, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(
new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build());
}
public void scheduleDriveUpload(Uri uri, boolean purge) {
if (!preferences.getBoolean(R.string.p_google_drive_backup, false)) {
return;
}
Builder builder =
new Builder(DriveUploader.class)
.setInputData(DriveUploader.getInputData(uri, purge))
.setConstraints(getNetworkConstraints());
if (purge) {
builder.setInitialDelay(new Random().nextInt(3600), TimeUnit.SECONDS);
}
workManager.enqueue(builder.build());
}
private Constraints getNetworkConstraints() {
return getNetworkConstraints(
preferences.getBoolean(R.string.p_background_sync_unmetered_only, false));
}
private Constraints getNetworkConstraints(boolean unmeteredOnly) {
return new Constraints.Builder()
.setRequiredNetworkType(unmeteredOnly ? NetworkType.UNMETERED : NetworkType.CONNECTED)
.build();
}
private void enqueueUnique(String key, Class<? extends Worker> c, long time) {
long delay = time - now();
OneTimeWorkRequest.Builder builder = new Builder(c);
if (delay > 0) {
builder.setInitialDelay(delay, TimeUnit.MILLISECONDS);
}
Timber.d("%s: %s (%s)", key, printTimestamp(time), printDuration(delay));
workManager.beginUniqueWork(key, ExistingWorkPolicy.REPLACE, builder.build()).enqueue();
}
@SuppressWarnings("WeakerAccess")
public void cancelNotifications() {
Timber.d("cancelNotifications");
alarmManager.cancel(getNotificationPendingIntent());
}
private Intent getNotificationIntent() {
return new Intent(context, NotificationService.class);
}
private PendingIntent getNotificationPendingIntent() {
Intent intent = getNotificationIntent();
return atLeastOreo()
? PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
: PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}

@ -0,0 +1,228 @@
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 androidx.work.WorkManager
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.Place
import org.tasks.date.DateTimeUtils.midnight
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.injection.ApplicationScope
import org.tasks.injection.ForApplication
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils
import timber.log.Timber
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.max
@ApplicationScope
class WorkManager @Inject constructor(
@param:ForApplication private val context: Context,
private val preferences: Preferences,
private val googleTaskListDao: GoogleTaskListDao,
private val caldavDao: CaldavDao) {
private val alarmManager: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
private val workManager: WorkManager = WorkManager.getInstance(context)
fun afterSave(current: Task, original: Task?) =
workManager.enqueue(
OneTimeWorkRequest.Builder(AfterSaveWork::class.java)
.setInputData(AfterSaveWork.getInputData(current, original))
.build())
fun cleanup(ids: Iterable<Long>) = ids.chunked(MAX_CLEANUP_LENGTH) {
workManager.enqueue(
OneTimeWorkRequest.Builder(CleanupWork::class.java)
.setInputData(
Data.Builder()
.putLongArray(CleanupWork.EXTRA_TASK_IDS, it.toLongArray())
.build())
.build())
}
fun sync(immediate: Boolean) {
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(SyncWork::class.java)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(constraints)
if (!immediate) {
builder.setInitialDelay(1, TimeUnit.MINUTES)
}
val request = builder.build()
workManager.beginUniqueWork(TAG_SYNC, ExistingWorkPolicy.REPLACE, request).enqueue()
}
fun reverseGeocode(place: Place) {
if (BuildConfig.DEBUG && place.id == 0L) {
throw RuntimeException("Missing id")
}
workManager.enqueue(
OneTimeWorkRequest.Builder(ReverseGeocodeWork::class.java)
.setInputData(Data.Builder().putLong(ReverseGeocodeWork.PLACE_ID, place.id).build())
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build())
}
fun updateBackgroundSync() = updateBackgroundSync(null, null, null)
@SuppressLint("CheckResult")
fun updateBackgroundSync(
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)
(if (forceAccountPresent == null) Single.zip(
googleTaskListDao.accountCount(),
Single.fromCallable { caldavDao.accountCount() },
BiFunction { googleCount: Int, caldavCount: Int -> googleCount > 0 || caldavCount > 0 }) else Single.just(forceAccountPresent))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { accountsPresent: Boolean -> scheduleBackgroundSync(backgroundEnabled && accountsPresent, onlyOnWifi) }
}
private fun scheduleBackgroundSync(enabled: Boolean, onlyOnUnmetered: Boolean) {
Timber.d("background sync enabled: %s, onlyOnUnmetered: %s", enabled, onlyOnUnmetered)
if (enabled) {
workManager.enqueueUniquePeriodicWork(
TAG_BACKGROUND_SYNC,
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequest.Builder(SyncWork::class.java, 1, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(getNetworkConstraints(onlyOnUnmetered))
.build())
} else {
workManager.cancelUniqueWork(TAG_BACKGROUND_SYNC)
}
}
fun scheduleRefresh(time: Long) = enqueueUnique(TAG_REFRESH, RefreshWork::class.java, time)
fun scheduleMidnightRefresh() =
enqueueUnique(TAG_MIDNIGHT_REFRESH, MidnightRefreshWork::class.java, midnight())
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)
}
}
fun scheduleBackup() {
enqueueUnique(
TAG_BACKUP,
BackupWork::class.java,
newDateTime(preferences.getLong(R.string.p_last_backup, 0L))
.plusDays(1)
.millis
.coerceAtMost(midnight()))
}
fun scheduleConfigRefresh() =
workManager.enqueueUniquePeriodicWork(
TAG_REMOTE_CONFIG,
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequest.Builder(
RemoteConfigWork::class.java, REMOTE_CONFIG_INTERVAL_HOURS, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build())
fun scheduleDriveUpload(uri: Uri?, purge: Boolean) {
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)
}
workManager.enqueue(builder.build())
}
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()
private fun enqueueUnique(key: String, c: Class<out Worker?>, time: Long) {
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)})")
workManager.beginUniqueWork(key, ExistingWorkPolicy.REPLACE, builder.build()).enqueue()
}
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)
}
}
companion object {
val REMOTE_CONFIG_INTERVAL_HOURS = if (BuildConfig.DEBUG) 1 else 12.toLong()
private const val MAX_CLEANUP_LENGTH = 500
private const val TAG_BACKUP = "tag_backup"
private const val TAG_REFRESH = "tag_refresh"
private const val TAG_MIDNIGHT_REFRESH = "tag_midnight_refresh"
private const val TAG_SYNC = "tag_sync"
private const val TAG_BACKGROUND_SYNC = "tag_background_sync"
private const val TAG_REMOTE_CONFIG = "tag_remote_config"
}
}
Loading…
Cancel
Save