diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0f0917a051..0fd4cd1fad 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -196,7 +196,6 @@ dependencies {
// RxJava
implementation(libs.rxjava)
- implementation(libs.flowreactivenetwork)
// Networking
implementation(libs.bundles.okhttp)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5c903960f1..9eddc43a28 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,6 +21,8 @@
+
+
-
-
@@ -154,6 +152,11 @@
android:value="true" />
+
+
= Build.VERSION_CODES.Q) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ } else {
+ 0
+ },
+ )
+ }
+
+ override suspend fun doWork(): Result {
+ try {
+ setForeground(getForegroundInfo())
+ } catch (e: IllegalStateException) {
+ logcat(LogPriority.ERROR, e) { "Not allowed to set foreground job" }
+ }
+
+ var networkCheck = checkConnectivity()
+ var active = networkCheck
+ downloadManager.downloaderStart()
+
+ // Keep the worker running when needed
+ while (active) {
+ delay(100)
+ networkCheck = checkConnectivity()
+ active = !isStopped && networkCheck && downloadManager.isRunning
+ }
+
+ return Result.success()
+ }
+
+ private fun checkConnectivity(): Boolean {
+ return with(applicationContext) {
+ if (isOnline()) {
+ val noWifi = downloadPreferences.downloadOnlyOverWifi().get() && !isConnectedToWifi()
+ if (noWifi) {
+ downloadManager.downloaderStop(
+ applicationContext.getString(R.string.download_notifier_text_only_wifi),
+ )
+ }
+ !noWifi
+ } else {
+ downloadManager.downloaderStop(applicationContext.getString(R.string.download_notifier_no_network))
+ false
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "Downloader"
+
+ fun start(context: Context) {
+ val request = OneTimeWorkRequestBuilder()
+ .addTag(TAG)
+ .build()
+ WorkManager.getInstance(context)
+ .enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
+ }
+
+ fun stop(context: Context) {
+ WorkManager.getInstance(context)
+ .cancelUniqueWork(TAG)
+ }
+
+ fun isRunning(context: Context): Boolean {
+ return WorkManager.getInstance(context)
+ .getWorkInfosForUniqueWork(TAG)
+ .get()
+ .let { list -> list.count { it.state == WorkInfo.State.RUNNING } == 1 }
+ }
+
+ fun isRunningFlow(context: Context): Flow {
+ return WorkManager.getInstance(context)
+ .getWorkInfosForUniqueWorkLiveData(TAG)
+ .asFlow()
+ .map { list -> list.count { it.state == WorkInfo.State.RUNNING } == 1 }
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
index ba5c4d81a9..dca4fac237 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
@@ -46,6 +46,9 @@ class DownloadManager(
*/
private val downloader = Downloader(context, provider, cache)
+ val isRunning: Boolean
+ get() = downloader.isRunning
+
/**
* Queue to delay the deletion of a list of chapters until triggered.
*/
@@ -59,13 +62,13 @@ class DownloadManager(
fun downloaderStop(reason: String? = null) = downloader.stop(reason)
val isDownloaderRunning
- get() = DownloadService.isRunning
+ get() = DownloadJob.isRunningFlow(context)
/**
* Tells the downloader to begin downloads.
*/
fun startDownloads() {
- DownloadService.start(context)
+ DownloadJob.start(context)
}
/**
@@ -104,10 +107,10 @@ class DownloadManager(
queue.add(0, toAdd)
reorderQueue(queue)
if (!downloader.isRunning) {
- if (DownloadService.isRunning(context)) {
+ if (DownloadJob.isRunning(context)) {
downloader.start()
} else {
- DownloadService.start(context)
+ DownloadJob.start(context)
}
}
}
@@ -143,7 +146,7 @@ class DownloadManager(
addAll(0, downloads)
reorderQueue(this)
}
- if (!DownloadService.isRunning(context)) DownloadService.start(context)
+ if (!DownloadJob.isRunning(context)) DownloadJob.start(context)
}
/**
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
deleted file mode 100644
index 9ce5c5b2a3..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-package eu.kanade.tachiyomi.data.download
-
-import android.app.Notification
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.os.IBinder
-import android.os.PowerManager
-import androidx.core.content.ContextCompat
-import dev.icerock.moko.resources.StringResource
-import eu.kanade.tachiyomi.data.notification.Notifications
-import eu.kanade.tachiyomi.util.system.acquireWakeLock
-import eu.kanade.tachiyomi.util.system.isConnectedToWifi
-import eu.kanade.tachiyomi.util.system.isOnline
-import eu.kanade.tachiyomi.util.system.isServiceRunning
-import eu.kanade.tachiyomi.util.system.notificationBuilder
-import eu.kanade.tachiyomi.util.system.toast
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import logcat.LogPriority
-import ru.beryukhov.reactivenetwork.ReactiveNetwork
-import tachiyomi.core.i18n.stringResource
-import tachiyomi.core.util.lang.withUIContext
-import tachiyomi.core.util.system.logcat
-import tachiyomi.domain.download.service.DownloadPreferences
-import tachiyomi.i18n.MR
-import uy.kohesive.injekt.injectLazy
-
-/**
- * This service is used to manage the downloader. The system can decide to stop the service, in
- * which case the downloader is also stopped. It's also stopped while there's no network available.
- * While the downloader is running, a wake lock will be held.
- */
-class DownloadService : Service() {
-
- companion object {
-
- private val _isRunning = MutableStateFlow(false)
- val isRunning = _isRunning.asStateFlow()
-
- /**
- * Starts this service.
- *
- * @param context the application context.
- */
- fun start(context: Context) {
- val intent = Intent(context, DownloadService::class.java)
- ContextCompat.startForegroundService(context, intent)
- }
-
- /**
- * Stops this service.
- *
- * @param context the application context.
- */
- fun stop(context: Context) {
- context.stopService(Intent(context, DownloadService::class.java))
- }
-
- /**
- * Returns the status of the service.
- *
- * @param context the application context.
- * @return true if the service is running, false otherwise.
- */
- fun isRunning(context: Context): Boolean {
- return context.isServiceRunning(DownloadService::class.java)
- }
- }
-
- private val downloadManager: DownloadManager by injectLazy()
- private val downloadPreferences: DownloadPreferences by injectLazy()
-
- /**
- * Wake lock to prevent the device to enter sleep mode.
- */
- private lateinit var wakeLock: PowerManager.WakeLock
-
- private lateinit var scope: CoroutineScope
-
- override fun onCreate() {
- scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
- startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification())
- wakeLock = acquireWakeLock(javaClass.name)
- _isRunning.value = true
- listenNetworkChanges()
- }
-
- override fun onDestroy() {
- scope.cancel()
- _isRunning.value = false
- downloadManager.downloaderStop()
- if (wakeLock.isHeld) {
- wakeLock.release()
- }
- }
-
- // Not used
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- return START_NOT_STICKY
- }
-
- // Not used
- override fun onBind(intent: Intent): IBinder? {
- return null
- }
-
- private fun downloaderStop(string: StringResource) {
- downloadManager.downloaderStop(stringResource(string))
- }
-
- private fun listenNetworkChanges() {
- ReactiveNetwork()
- .observeNetworkConnectivity(applicationContext)
- .onEach {
- withUIContext {
- if (isOnline()) {
- if (downloadPreferences.downloadOnlyOverWifi().get() && !isConnectedToWifi()) {
- downloaderStop(MR.strings.download_notifier_text_only_wifi)
- } else {
- val started = downloadManager.downloaderStart()
- if (!started) stopSelf()
- }
- } else {
- downloaderStop(MR.strings.download_notifier_no_network)
- }
- }
- }
- .catch { error ->
- withUIContext {
- logcat(LogPriority.ERROR, error)
- toast(MR.strings.download_queue_error)
- stopSelf()
- }
- }
- .launchIn(scope)
- }
-
- private fun getPlaceholderNotification(): Notification {
- return notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
- setContentTitle(stringResource(MR.strings.download_notifier_downloader_title))
- }.build()
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
index 5e58d4f6a8..1dd117f41a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
@@ -161,10 +161,7 @@ class Downloader(
isPaused = false
- // Prevent recursion when DownloadService.onDestroy() calls downloader.stop()
- if (DownloadService.isRunning.value) {
- DownloadService.stop(context)
- }
+ DownloadJob.stop(context)
}
/**
@@ -310,7 +307,7 @@ class Downloader(
)
}
}
- DownloadService.start(context)
+ DownloadJob.start(context)
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreenModel.kt
index 3cfb3ff1ab..3f88966324 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreenModel.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreenModel.kt
@@ -11,12 +11,14 @@ import eu.kanade.tachiyomi.source.model.Page
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
@@ -137,8 +139,8 @@ class DownloadQueueScreenModel(
adapter = null
}
- val isDownloaderRunning
- get() = downloadManager.isDownloaderRunning
+ val isDownloaderRunning = downloadManager.isDownloaderRunning
+ .stateIn(screenModelScope, SharingStarted.WhileSubscribed(5000), false)
fun getDownloadStatusFlow() = downloadManager.statusFlow()
fun getDownloadProgressFlow() = downloadManager.progressFlow()
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 889ac693d3..fa08d896a6 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -15,7 +15,6 @@ android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1
google-services-gradle = "com.google.gms:google-services:4.4.0"
rxjava = "io.reactivex:rxjava:1.3.8"
-flowreactivenetwork = "ru.beryukhov:flowreactivenetwork:1.0.4"
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" }