diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6f6b4c4583..1ac1f64cc5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -151,7 +151,9 @@ dependencies { implementation("org.jsoup:jsoup:1.13.1") // Job scheduling - implementation("com.evernote:android-job:1.4.2") + val workManagerVersion = "2.3.3" + implementation("android.arch.work:work-runtime:$workManagerVersion") + implementation("android.arch.work:work-runtime-ktx:$workManagerVersion") implementation("com.google.android.gms:play-services-gcm:17.0.0") // Changelog diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 16ce8042cf..69434a0f50 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -8,14 +8,9 @@ import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.ProcessLifecycleOwner import androidx.multidex.MultiDex -import com.evernote.android.job.JobManager -import eu.kanade.tachiyomi.data.backup.BackupCreatorJob -import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.updater.UpdaterJob -import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.LocaleHelper import org.acra.ACRA @@ -43,7 +38,6 @@ open class App : Application(), LifecycleObserver { Injekt.importModule(AppModule(this)) setupAcra() - setupJobManager() setupNotificationChannels() LocaleHelper.updateConfiguration(this, resources.configuration) @@ -73,22 +67,6 @@ open class App : Application(), LifecycleObserver { ACRA.init(this) } - protected open fun setupJobManager() { - try { - JobManager.create(this).addJobCreator { tag -> - when (tag) { - LibraryUpdateJob.TAG -> LibraryUpdateJob() - UpdaterJob.TAG -> UpdaterJob() - BackupCreatorJob.TAG -> BackupCreatorJob() - ExtensionUpdateJob.TAG -> ExtensionUpdateJob() - else -> null - } - } - } catch (e: Exception) { - Timber.w("Can't initialize job manager") - } - } - protected open fun setupNotificationChannels() { Notifications.createChannels(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 6bb2fca7ca..f7e1ac4c99 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -1,10 +1,12 @@ package eu.kanade.tachiyomi +import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.updater.UpdaterJob +import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.ui.library.LibraryPresenter import java.io.File @@ -64,8 +66,16 @@ object Migrations { } if (oldVersion < 54) DownloadProvider(context).renameChaapters() - if (oldVersion < 62) + if (oldVersion < 62) { LibraryPresenter.updateDB() + // Restore jobs after migrating from Evernote's job scheduler to WorkManager. + if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) { + UpdaterJob.setupTask() + } + LibraryUpdateJob.setupTask() + BackupCreatorJob.setupTask() + ExtensionUpdateJob.setupTask() + } return true } return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt index f18340ac52..f10b64ed3a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt @@ -1,44 +1,47 @@ package eu.kanade.tachiyomi.data.backup +import android.content.Context import android.net.Uri -import com.evernote.android.job.Job -import com.evernote.android.job.JobManager -import com.evernote.android.job.JobRequest +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.Worker +import androidx.work.WorkerParameters import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.TimeUnit -class BackupCreatorJob : Job() { +class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) : + Worker(context, workerParams) { - override fun onRunJob(params: Params): Result { + override fun doWork(): Result { val preferences = Injekt.get() val backupManager = BackupManager(context) val uri = Uri.parse(preferences.backupsDirectory().getOrDefault()) val flags = BackupCreateService.BACKUP_ALL backupManager.createBackup(uri, flags, true) - return Result.SUCCESS + return Result.success() } companion object { - const val TAG = "BackupCreator" + private const val TAG = "BackupCreator" fun setupTask(prefInterval: Int? = null) { val preferences = Injekt.get() - val interval = (prefInterval ?: preferences.backupInterval().getOrDefault()).toLong() + val interval = prefInterval ?: preferences.backupInterval().getOrDefault() if (interval > 0) { - JobRequest.Builder(TAG) - .setPeriodic(TimeUnit.HOURS.toMillis(interval), TimeUnit.MINUTES.toMillis - (10)) - .setUpdateCurrent(true) + val request = PeriodicWorkRequestBuilder( + interval.toLong(), TimeUnit.HOURS, + 10, TimeUnit.MINUTES) + .addTag(TAG) .build() - .schedule() - } - } - fun cancelTask() { - JobManager.instance().cancelAllForTag(TAG) + WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) + } else { + WorkManager.getInstance().cancelAllWorkByTag(TAG) + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index 01698a5ce0..3401262ecf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -1,51 +1,57 @@ package eu.kanade.tachiyomi.data.library -import com.evernote.android.job.Job -import com.evernote.android.job.JobManager -import com.evernote.android.job.JobRequest +import android.content.Context +import androidx.work.Constraints +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.Worker +import androidx.work.WorkerParameters import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.TimeUnit -class LibraryUpdateJob : Job() { +class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) : + Worker(context, workerParams) { - override fun onRunJob(params: Params): Result { + override fun doWork(): Result { LibraryUpdateService.start(context) - return Job.Result.SUCCESS + return Result.success() } companion object { - const val TAG = "LibraryUpdate" + private const val TAG = "LibraryUpdate" fun setupTask(prefInterval: Int? = null) { val preferences = Injekt.get() - val interval = (prefInterval ?: preferences.libraryUpdateInterval().getOrDefault()) - .toLong() + val interval = prefInterval ?: preferences.libraryUpdateInterval().getOrDefault() if (interval > 0) { val restrictions = preferences.libraryUpdateRestriction()!! val acRestriction = "ac" in restrictions val wifiRestriction = if ("wifi" in restrictions) - JobRequest.NetworkType.UNMETERED + NetworkType.UNMETERED else - JobRequest.NetworkType.CONNECTED + NetworkType.CONNECTED - JobRequest.Builder(TAG) - .setPeriodic( - TimeUnit.HOURS.toMillis(interval), TimeUnit.MINUTES.toMillis - (10)) + val constraints = Constraints.Builder() .setRequiredNetworkType(wifiRestriction) .setRequiresCharging(acRestriction) - .setRequirementsEnforced(true) - .setUpdateCurrent(true) .build() - .schedule() - } - } - fun cancelTask() { - JobManager.instance().cancelAllForTag(TAG) + val request = PeriodicWorkRequestBuilder( + interval.toLong(), TimeUnit.HOURS, + 10, TimeUnit.MINUTES) + .addTag(TAG) + .setConstraints(constraints) + .build() + + WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) + } else { + WorkManager.getInstance().cancelAllWorkByTag(TAG) + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt index b0093843ec..a0c89b83bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt @@ -1,53 +1,55 @@ package eu.kanade.tachiyomi.data.updater import android.app.PendingIntent +import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat -import com.evernote.android.job.Job -import com.evernote.android.job.JobManager -import com.evernote.android.job.JobRequest +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.system.notificationManager -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.coroutineScope import java.util.concurrent.TimeUnit -class UpdaterJob : Job() { +class UpdaterJob(private val context: Context, workerParams: WorkerParameters) : + CoroutineWorker(context, workerParams) { - override fun onRunJob(params: Params): Result { - GlobalScope.launch(Dispatchers.IO) { - val result = try { UpdateChecker.getUpdateChecker().checkForUpdate() } catch (e: Exception) { return@launch } - if (result is UpdateResult.NewUpdate<*>) { - val url = result.release.downloadLink - - val intent = Intent(context, UpdaterService::class.java).apply { - putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url) - } - - NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update { - setContentTitle(context.getString(R.string.app_name)) - setContentText(context.getString(R.string.update_available)) - setSmallIcon(android.R.drawable.stat_sys_download_done) - color = ContextCompat.getColor(context, R.color.colorAccent) - // Download action - addAction( - android.R.drawable.stat_sys_download_done, - context.getString(R.string.download), - PendingIntent.getService( - context, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT - ) - ) - } - } - Result.SUCCESS + override suspend fun doWork(): Result = coroutineScope { + val result = try { + UpdateChecker.getUpdateChecker().checkForUpdate() + } catch (e: Exception) { + Result.failure() } - return Result.SUCCESS + if (result is UpdateResult.NewUpdate<*>) { + val url = result.release.downloadLink + + val intent = Intent(context, UpdaterService::class.java).apply { + putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url) + } + + NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update { + setContentTitle(context.getString(R.string.app_name)) + setContentText(context.getString(R.string.update_available)) + setSmallIcon(android.R.drawable.stat_sys_download_done) + color = ContextCompat.getColor(context, R.color.colorAccent) + // Download action + addAction( + android.R.drawable.stat_sys_download_done, + context.getString(R.string.download), + PendingIntent.getService( + context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + } + } + Result.success() } fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) { @@ -56,20 +58,25 @@ class UpdaterJob : Job() { } companion object { - const val TAG = "UpdateChecker" + private const val TAG = "UpdateChecker" fun setupTask() { - JobRequest.Builder(TAG) - .setPeriodic(TimeUnit.DAYS.toMillis(1), TimeUnit.HOURS.toMillis(1)) - .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED) - .setRequirementsEnforced(true) - .setUpdateCurrent(true) + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) .build() - .schedule() + + val request = PeriodicWorkRequestBuilder( + 1, TimeUnit.DAYS, + 1, TimeUnit.HOURS) + .addTag(TAG) + .setConstraints(constraints) + .build() + + WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) } fun cancelTask() { - JobManager.instance().cancelAllForTag(TAG) + WorkManager.getInstance().cancelAllWorkByTag(TAG) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt index 53202e4614..bd0647fe8d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt @@ -1,73 +1,95 @@ package eu.kanade.tachiyomi.extension +import android.content.Context import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat -import com.evernote.android.job.Job -import com.evernote.android.job.JobManager -import com.evernote.android.job.JobRequest +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.util.system.notification -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.coroutineScope +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.util.concurrent.TimeUnit -class ExtensionUpdateJob : Job() { +class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) : + CoroutineWorker(context, workerParams) { - override fun onRunJob(params: Params): Result { - GlobalScope.launch(Dispatchers.IO) { - val pendingUpdates = ExtensionGithubApi().checkForUpdates(context) - if (pendingUpdates.isNotEmpty()) { - val names = pendingUpdates.map { it.name } - val preferences: PreferencesHelper by injectLazy() - preferences.extensionUpdatesCount().set(pendingUpdates.size) - NotificationManagerCompat.from(context).apply { - notify(Notifications.ID_UPDATES_TO_EXTS, - context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) { - setContentTitle( - context.resources.getQuantityString( - R.plurals.extension_updates_available, names - .size, names.size - ) - ) - val extNames = names.joinToString(", ") - setContentText(extNames) - setStyle(NotificationCompat.BigTextStyle().bigText(extNames)) - setSmallIcon(R.drawable.ic_extension_update) - color = ContextCompat.getColor(context, R.color.colorAccent) - setContentIntent( - NotificationReceiver.openExtensionsPendingActivity( - context - ) - ) - setAutoCancel(true) - }) - } - } + override suspend fun doWork(): Result = coroutineScope { + val pendingUpdates = try { + ExtensionGithubApi().checkForUpdates(context) + } catch (e: Exception) { + return@coroutineScope Result.failure() + } + + if (pendingUpdates.isNotEmpty()) { + createUpdateNotification(pendingUpdates.map { it.name }) + } + + Result.success() + } + + private fun createUpdateNotification(names: List) { + val preferences: PreferencesHelper by injectLazy() + preferences.extensionUpdatesCount().set(names.size) + NotificationManagerCompat.from(context).apply { + notify(Notifications.ID_UPDATES_TO_EXTS, + context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) { + setContentTitle( + context.resources.getQuantityString( + R.plurals.extension_updates_available, names + .size, names.size + ) + ) + val extNames = names.joinToString(", ") + setContentText(extNames) + setStyle(NotificationCompat.BigTextStyle().bigText(extNames)) + setSmallIcon(R.drawable.ic_extension_update) + color = ContextCompat.getColor(context, R.color.colorAccent) + setContentIntent( + NotificationReceiver.openExtensionsPendingActivity( + context + ) + ) + setAutoCancel(true) + }) } - return Result.SUCCESS } companion object { - const val TAG = "ExtensionUpdate" + private const val TAG = "ExtensionUpdate" - fun setupTask() { - JobRequest.Builder(TAG).setPeriodic(TimeUnit.HOURS.toMillis(12), - TimeUnit.HOURS.toMillis(2)) - .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED) - .setRequirementsEnforced(true) - .setUpdateCurrent(true) - .build().schedule() - } + fun setupTask(forceAutoUpdateJob: Boolean? = null) { + val preferences = Injekt.get() + val autoUpdateJob = forceAutoUpdateJob ?: preferences.automaticExtUpdates().getOrDefault() + if (autoUpdateJob) { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() - fun cancelTask() { - JobManager.instance().cancelAllForTag(TAG) + val request = PeriodicWorkRequestBuilder( + 12, TimeUnit.HOURS, + 1, TimeUnit.HOURS) + .addTag(TAG) + .setConstraints(constraints) + .build() + + WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) + } else { + WorkManager.getInstance().cancelAllWorkByTag(TAG) + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/ManageCategoryDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/ManageCategoryDialog.kt index 36171fa5f5..e93ce31d3e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/ManageCategoryDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/ManageCategoryDialog.kt @@ -71,7 +71,7 @@ class ManageCategoryDialog(bundle: Bundle? = null) : if (preferences.libraryUpdateInterval().getOrDefault() > 0 && !updatePref(preferences.libraryUpdateCategories(), view.include_global)) { preferences.libraryUpdateInterval().set(0) - LibraryUpdateJob.cancelTask() + LibraryUpdateJob.setupTask(0) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index f861fd5e60..3ccea49652 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt @@ -94,7 +94,7 @@ class SettingsBackupController : SettingsController() { onChange { newValue -> // Always cancel the previous task, it seems that sometimes they are not updated - BackupCreatorJob.cancelTask() + BackupCreatorJob.setupTask(0) val interval = newValue as Int if (interval > 0) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index 9a7a840e8d..d18a7e0549 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt @@ -81,7 +81,7 @@ class SettingsLibraryController : SettingsController() { onChange { newValue -> // Always cancel the previous task, it seems that sometimes they are not updated. - LibraryUpdateJob.cancelTask() + LibraryUpdateJob.setupTask(0) val interval = newValue as Int if (interval > 0) {