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" }