mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 10:46:53 -04:00
Fixed: Downloading starts very slowly on Android 14+.
* Upgraded Fetch to 3.4.1. * Upgraded Kotlin kotlin-stdlib from JDK7 to JDK8. * Upgraded Kotlin version to 2.0.0 to support the latest Fetch library. * Upgraded Dagger to 2.53.1. * Upgraded ObjectBox to 4.0.3. * Notification now shows "Pause" and "Resume" buttons instead of "pause" and "resume". * Fixed: Notification buttons were not working on Android 13 and above. * Fixed: Downloading was not working in the background. Added a foreground service for Fetch so that downloading will continue in the background.
This commit is contained in:
parent
0f16730120
commit
2e63d70e6c
@ -12,7 +12,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation("com.android.tools.build:gradle:8.1.3")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0")
|
||||
implementation("org.jacoco:org.jacoco.core:0.8.12")
|
||||
implementation("org.jlleitschuh.gradle:ktlint-gradle:10.3.0")
|
||||
implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20230406-2.0.0") {
|
||||
|
@ -101,7 +101,7 @@ object Libs {
|
||||
/**
|
||||
* https://kotlinlang.org/
|
||||
*/
|
||||
const val kotlin_stdlib_jdk7: String = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:" +
|
||||
const val kotlin_stdlib_jdk8: String = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:" +
|
||||
Versions.org_jetbrains_kotlin
|
||||
|
||||
/**
|
||||
|
@ -26,13 +26,13 @@ object Versions {
|
||||
|
||||
const val com_squareup_okhttp3: String = "4.12.0"
|
||||
|
||||
const val org_jetbrains_kotlin: String = "1.9.20"
|
||||
const val org_jetbrains_kotlin: String = "2.0.0"
|
||||
|
||||
const val androidx_navigation: String = "2.5.3"
|
||||
|
||||
const val navigation_ui_ktx: String = "2.4.1"
|
||||
|
||||
const val com_google_dagger: String = "2.48.1"
|
||||
const val com_google_dagger: String = "2.53.1"
|
||||
|
||||
const val androidx_test: String = "1.6.1"
|
||||
|
||||
@ -40,7 +40,7 @@ object Versions {
|
||||
|
||||
const val androidx_test_orchestrator: String = "1.5.0"
|
||||
|
||||
const val io_objectbox: String = "3.5.0"
|
||||
const val io_objectbox: String = "4.0.3"
|
||||
|
||||
const val io_mockk: String = "1.13.13"
|
||||
|
||||
@ -110,7 +110,7 @@ object Versions {
|
||||
|
||||
const val keeper = "0.16.1"
|
||||
|
||||
const val fetch: String = "3.3.0"
|
||||
const val fetch: String = "3.4.1"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,6 +28,7 @@ import org.gradle.kotlin.dsl.apply
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
|
||||
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jlleitschuh.gradle.ktlint.KtlintExtension
|
||||
|
||||
@ -75,7 +76,10 @@ class AllProjectConfigurer {
|
||||
targetCompatibility = Config.javaVersion
|
||||
}
|
||||
target.tasks.withType(KotlinCompile::class.java) {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_1_8)
|
||||
freeCompilerArgs.add("-Xjvm-default=all-compatibility")
|
||||
}
|
||||
}
|
||||
buildFeatures.apply {
|
||||
viewBinding = true
|
||||
@ -191,7 +195,7 @@ class AllProjectConfigurer {
|
||||
|
||||
fun configureDependencies(target: Project) {
|
||||
target.dependencies {
|
||||
implementation(Libs.kotlin_stdlib_jdk7)
|
||||
implementation(Libs.kotlin_stdlib_jdk8)
|
||||
implementation(Libs.appcompat)
|
||||
implementation(Libs.material)
|
||||
implementation(Libs.constraintlayout)
|
||||
@ -227,7 +231,6 @@ class AllProjectConfigurer {
|
||||
implementation(Libs.roomRxjava2)
|
||||
kapt(Libs.roomCompiler)
|
||||
implementation(Libs.tracing)
|
||||
implementation(Libs.fetch)
|
||||
implementation(Libs.fetchOkhttp)
|
||||
}
|
||||
}
|
||||
|
@ -63,4 +63,8 @@ dependencies {
|
||||
implementation(Libs.kotlinx_coroutines_android)
|
||||
implementation(Libs.kotlinx_coroutines_rx3)
|
||||
implementation(Libs.zxing)
|
||||
api(Libs.fetch) {
|
||||
// Todo: Will remove this when we add support for Android 15
|
||||
exclude("androidx.core", "core-ktx")
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import androidx.multidex.MultiDex
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import org.kiwix.kiwixmobile.core.di.components.CoreComponent
|
||||
import org.kiwix.kiwixmobile.core.di.components.DaggerCoreComponent
|
||||
import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor
|
||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||
import org.kiwix.kiwixmobile.core.utils.files.FileLogger
|
||||
import javax.inject.Inject
|
||||
@ -43,9 +42,6 @@ abstract class CoreApp : Application() {
|
||||
lateinit var coreComponent: CoreComponent
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var downloadMonitor: DownloadMonitor
|
||||
|
||||
@Inject
|
||||
lateinit var darkModeConfig: DarkModeConfig
|
||||
|
||||
@ -84,7 +80,6 @@ abstract class CoreApp : Application() {
|
||||
AndroidThreeTen.init(this)
|
||||
coreComponent.inject(this)
|
||||
serviceWorkerInitialiser.init(this)
|
||||
downloadMonitor.init()
|
||||
darkModeConfig.init()
|
||||
fileLogger.writeLogFile(this)
|
||||
configureStrictMode()
|
||||
|
@ -35,6 +35,7 @@ import javax.inject.Inject
|
||||
|
||||
class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
|
||||
|
||||
@Suppress("NoOp")
|
||||
fun books() = box.asFlowable()
|
||||
.flatMap { books ->
|
||||
io.reactivex.rxjava3.core.Flowable.fromIterable(books)
|
||||
|
@ -49,6 +49,7 @@ import org.kiwix.kiwixmobile.core.di.modules.JNIModule
|
||||
import org.kiwix.kiwixmobile.core.di.modules.MutexModule
|
||||
import org.kiwix.kiwixmobile.core.di.modules.NetworkModule
|
||||
import org.kiwix.kiwixmobile.core.di.modules.SearchModule
|
||||
import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor
|
||||
import org.kiwix.kiwixmobile.core.downloader.Downloader
|
||||
import org.kiwix.kiwixmobile.core.error.ErrorActivity
|
||||
import org.kiwix.kiwixmobile.core.main.KiwixWebView
|
||||
@ -111,6 +112,7 @@ interface CoreComponent {
|
||||
fun notificationManager(): NotificationManager
|
||||
fun searchResultGenerator(): SearchResultGenerator
|
||||
fun mutex(): Mutex
|
||||
fun provideDownloadMonitor(): DownloadMonitor
|
||||
|
||||
fun inject(application: CoreApp)
|
||||
fun inject(kiwixWebView: KiwixWebView)
|
||||
|
@ -32,7 +32,6 @@ import org.kiwix.kiwixmobile.core.data.remote.KiwixService
|
||||
import org.kiwix.kiwixmobile.core.downloader.DownloadRequester
|
||||
import org.kiwix.kiwixmobile.core.downloader.Downloader
|
||||
import org.kiwix.kiwixmobile.core.downloader.DownloaderImpl
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMonitor
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerRequester
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.FetchDownloadNotificationManager
|
||||
import org.kiwix.kiwixmobile.core.utils.CONNECT_TIME_OUT
|
||||
@ -56,10 +55,9 @@ object DownloaderModule {
|
||||
@Singleton
|
||||
fun providesDownloadRequester(
|
||||
fetch: Fetch,
|
||||
sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
downloadManagerMonitor: DownloadManagerMonitor
|
||||
sharedPreferenceUtil: SharedPreferenceUtil
|
||||
): DownloadRequester =
|
||||
DownloadManagerRequester(fetch, sharedPreferenceUtil, downloadManagerMonitor)
|
||||
DownloadManagerRequester(fetch, sharedPreferenceUtil)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
@ -18,5 +18,6 @@
|
||||
package org.kiwix.kiwixmobile.core.downloader
|
||||
|
||||
interface DownloadMonitor {
|
||||
fun init()
|
||||
fun startMonitoringDownload()
|
||||
fun stopListeningDownloads()
|
||||
}
|
||||
|
@ -20,10 +20,16 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import com.tonyodev.fetch2.Download
|
||||
import com.tonyodev.fetch2.Error
|
||||
import com.tonyodev.fetch2.Fetch
|
||||
import com.tonyodev.fetch2.FetchListener
|
||||
import com.tonyodev.fetch2core.DownloadBlock
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
|
||||
import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor
|
||||
import org.kiwix.kiwixmobile.core.extensions.isServiceRunning
|
||||
import javax.inject.Inject
|
||||
|
||||
const val ZERO = 0
|
||||
@ -33,27 +39,103 @@ const val DEFAULT_INT_VALUE = -1
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
class DownloadManagerMonitor @Inject constructor(
|
||||
val context: Context
|
||||
val fetch: Fetch,
|
||||
val context: Context,
|
||||
val downloadRoomDao: DownloadRoomDao
|
||||
) : DownloadMonitor {
|
||||
private val updater = PublishSubject.create<() -> Unit>()
|
||||
private var updaterDisposable: Disposable? = null
|
||||
|
||||
init {
|
||||
startMonitoringDownloads()
|
||||
private fun setupUpdater() {
|
||||
updaterDisposable = updater.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(
|
||||
{ it.invoke() },
|
||||
Throwable::printStackTrace
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts monitoring the downloads by ensuring that the `DownloadMonitorService` is running.
|
||||
* This service keeps the Fetch instance alive when the application is in the background
|
||||
* or has been killed by the user or system, allowing downloads to continue in the background.
|
||||
*/
|
||||
fun startMonitoringDownloads() {
|
||||
if (!context.isServiceRunning(DownloadMonitorService::class.java)) {
|
||||
context.startService(Intent(context, DownloadMonitorService::class.java)).also {
|
||||
Log.e("DOWNLOAD_MANAGER_MONITOR", "Starting DownloadMonitorService")
|
||||
}
|
||||
private val fetchListener = object : FetchListener {
|
||||
override fun onAdded(download: Download) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun onCancelled(download: Download) {
|
||||
delete(download)
|
||||
}
|
||||
|
||||
override fun onCompleted(download: Download) {
|
||||
update(download)
|
||||
}
|
||||
|
||||
override fun onDeleted(download: Download) {
|
||||
delete(download)
|
||||
}
|
||||
|
||||
override fun onDownloadBlockUpdated(
|
||||
download: Download,
|
||||
downloadBlock: DownloadBlock,
|
||||
totalBlocks: Int
|
||||
) {
|
||||
update(download)
|
||||
}
|
||||
|
||||
override fun onError(download: Download, error: Error, throwable: Throwable?) {
|
||||
update(download)
|
||||
}
|
||||
|
||||
override fun onPaused(download: Download) {
|
||||
update(download)
|
||||
}
|
||||
|
||||
override fun onProgress(
|
||||
download: Download,
|
||||
etaInMilliSeconds: Long,
|
||||
downloadedBytesPerSecond: Long
|
||||
) {
|
||||
update(download)
|
||||
}
|
||||
|
||||
override fun onQueued(download: Download, waitingOnNetwork: Boolean) {
|
||||
update(download)
|
||||
}
|
||||
|
||||
override fun onRemoved(download: Download) {
|
||||
delete(download)
|
||||
}
|
||||
|
||||
override fun onResumed(download: Download) {
|
||||
update(download)
|
||||
}
|
||||
|
||||
override fun onStarted(
|
||||
download: Download,
|
||||
downloadBlocks: List<DownloadBlock>,
|
||||
totalBlocks: Int
|
||||
) {
|
||||
update(download)
|
||||
}
|
||||
|
||||
override fun onWaitingNetwork(download: Download) {
|
||||
update(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
// empty method to so class does not get reported unused
|
||||
private fun update(download: Download) {
|
||||
updater.onNext { downloadRoomDao.update(download) }
|
||||
}
|
||||
|
||||
private fun delete(download: Download) {
|
||||
updater.onNext { downloadRoomDao.delete(download) }
|
||||
}
|
||||
|
||||
override fun startMonitoringDownload() {
|
||||
fetch.addListener(fetchListener, true)
|
||||
setupUpdater()
|
||||
}
|
||||
|
||||
override fun stopListeningDownloads() {
|
||||
fetch.removeListener(fetchListener)
|
||||
updaterDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,9 @@
|
||||
package org.kiwix.kiwixmobile.core.downloader.downloadManager
|
||||
|
||||
import com.tonyodev.fetch2.Fetch
|
||||
import com.tonyodev.fetch2.Request
|
||||
import com.tonyodev.fetch2.NetworkType.ALL
|
||||
import com.tonyodev.fetch2.NetworkType.WIFI_ONLY
|
||||
import com.tonyodev.fetch2.Request
|
||||
import org.kiwix.kiwixmobile.core.downloader.DownloadRequester
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest
|
||||
import org.kiwix.kiwixmobile.core.utils.AUTO_RETRY_MAX_ATTEMPTS
|
||||
@ -30,27 +30,20 @@ import javax.inject.Inject
|
||||
|
||||
class DownloadManagerRequester @Inject constructor(
|
||||
private val fetch: Fetch,
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
private val downloadManagerMonitor: DownloadManagerMonitor
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil
|
||||
) : DownloadRequester {
|
||||
override fun enqueue(downloadRequest: DownloadRequest): Long {
|
||||
val request = downloadRequest.toFetchRequest(sharedPreferenceUtil)
|
||||
fetch.enqueue(request)
|
||||
return request.id.toLong().also {
|
||||
downloadManagerMonitor.startMonitoringDownloads()
|
||||
}
|
||||
return request.id.toLong()
|
||||
}
|
||||
|
||||
override fun cancel(downloadId: Long) {
|
||||
fetch.delete(downloadId.toInt()).also {
|
||||
downloadManagerMonitor.startMonitoringDownloads()
|
||||
}
|
||||
fetch.delete(downloadId.toInt())
|
||||
}
|
||||
|
||||
override fun retryDownload(downloadId: Long) {
|
||||
fetch.retry(downloadId.toInt()).also {
|
||||
downloadManagerMonitor.startMonitoringDownloads()
|
||||
}
|
||||
fetch.retry(downloadId.toInt())
|
||||
}
|
||||
|
||||
override fun pauseResumeDownload(downloadId: Long, isPause: Boolean) {
|
||||
@ -58,8 +51,6 @@ class DownloadManagerRequester @Inject constructor(
|
||||
fetch.resume(downloadId.toInt())
|
||||
} else {
|
||||
fetch.pause(downloadId.toInt())
|
||||
}.also {
|
||||
downloadManagerMonitor.startMonitoringDownloads()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import com.tonyodev.fetch2.Download
|
||||
import com.tonyodev.fetch2.Error
|
||||
import com.tonyodev.fetch2.Fetch
|
||||
import com.tonyodev.fetch2.FetchListener
|
||||
import com.tonyodev.fetch2.Status
|
||||
import com.tonyodev.fetch2core.DownloadBlock
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.disposables.Disposable
|
||||
@ -45,6 +46,9 @@ class DownloadMonitorService : Service() {
|
||||
@Inject
|
||||
lateinit var fetch: Fetch
|
||||
|
||||
@Inject
|
||||
lateinit var fetchDownloadNotificationManager: FetchDownloadNotificationManager
|
||||
|
||||
@Inject
|
||||
lateinit var downloadRoomDao: DownloadRoomDao
|
||||
|
||||
@ -55,11 +59,16 @@ class DownloadMonitorService : Service() {
|
||||
.build()
|
||||
.inject(this)
|
||||
super.onCreate()
|
||||
fetch.addListener(fetchListener, true)
|
||||
setupUpdater()
|
||||
startMonitoringDownloads()
|
||||
fetch.addListener(fetchListener, true)
|
||||
setForegroundNotification()
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodically checks if there are active downloads.
|
||||
* If no downloads are active, it stops the foreground service.
|
||||
*/
|
||||
private fun startMonitoringDownloads() {
|
||||
// Check if monitoring is already active. If it is, do nothing.
|
||||
if (monitoringDisposable?.isDisposed == false) return
|
||||
@ -96,7 +105,38 @@ class DownloadMonitorService : Service() {
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = START_NOT_STICKY
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent?.action == STOP_DOWNLOAD_SERVICE) {
|
||||
stopForegroundServiceForDownloads()
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the foreground notification for the service.
|
||||
* This notification is used to display the current download progress,
|
||||
* and it is updated dynamically based on the state of the downloads.
|
||||
*
|
||||
* The method checks for any active downloads and, if found, updates the notification
|
||||
* with the latest download progress. If there are no active downloads,
|
||||
* the service is stopped and removed from the foreground.
|
||||
*/
|
||||
private fun setForegroundNotification() {
|
||||
updater.onNext {
|
||||
fetch.getDownloads { downloadList ->
|
||||
downloadList.firstOrNull {
|
||||
it.status == Status.NONE ||
|
||||
it.status == Status.ADDED ||
|
||||
it.status == Status.QUEUED ||
|
||||
it.status == Status.DOWNLOADING
|
||||
}?.let {
|
||||
val notificationBuilder =
|
||||
fetchDownloadNotificationManager.getNotificationBuilder(it.id, it.id)
|
||||
startForeground(it.id, notificationBuilder.build())
|
||||
} ?: kotlin.run(::stopForegroundServiceForDownloads)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val fetchListener = object : FetchListener {
|
||||
override fun onAdded(download: Download) {
|
||||
@ -108,7 +148,7 @@ class DownloadMonitorService : Service() {
|
||||
}
|
||||
|
||||
override fun onCompleted(download: Download) {
|
||||
update(download)
|
||||
update(download, true)
|
||||
}
|
||||
|
||||
override fun onDeleted(download: Download) {
|
||||
@ -124,11 +164,11 @@ class DownloadMonitorService : Service() {
|
||||
}
|
||||
|
||||
override fun onError(download: Download, error: Error, throwable: Throwable?) {
|
||||
update(download)
|
||||
update(download, true)
|
||||
}
|
||||
|
||||
override fun onPaused(download: Download) {
|
||||
update(download)
|
||||
update(download, true)
|
||||
}
|
||||
|
||||
override fun onProgress(
|
||||
@ -163,25 +203,33 @@ class DownloadMonitorService : Service() {
|
||||
update(download)
|
||||
}
|
||||
|
||||
private fun update(download: Download) {
|
||||
updater.onNext { downloadRoomDao.update(download) }
|
||||
private fun update(download: Download, shouldSetForegroundNotification: Boolean = false) {
|
||||
updater.onNext { downloadRoomDao.update(download) }.also {
|
||||
if (shouldSetForegroundNotification) {
|
||||
setForegroundNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun delete(download: Download) {
|
||||
updater.onNext { downloadRoomDao.delete(download) }
|
||||
updater.onNext { downloadRoomDao.delete(download) }.also {
|
||||
setForegroundNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the foreground service, disposes of resources, and removes the Fetch listener.
|
||||
*/
|
||||
private fun stopForegroundServiceForDownloads() {
|
||||
// foreGroundServiceInformation = true to DEFAULT_INT_VALUE
|
||||
Log.e(
|
||||
"DOWNLOAD_MANAGER_MONITOR",
|
||||
"Stopping DownloadMonitorService as there is no more download available to track"
|
||||
)
|
||||
monitoringDisposable?.dispose()
|
||||
updaterDisposable?.dispose()
|
||||
fetch.removeListener(fetchListener)
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STOP_DOWNLOAD_SERVICE = "stop_download_service"
|
||||
}
|
||||
}
|
||||
|
@ -21,46 +21,52 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.PendingIntent.getActivity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.tonyodev.fetch2.ACTION_TYPE_CANCEL
|
||||
import com.tonyodev.fetch2.ACTION_TYPE_DELETE
|
||||
import com.tonyodev.fetch2.ACTION_TYPE_INVALID
|
||||
import com.tonyodev.fetch2.ACTION_TYPE_PAUSE
|
||||
import com.tonyodev.fetch2.ACTION_TYPE_RESUME
|
||||
import com.tonyodev.fetch2.ACTION_TYPE_RETRY
|
||||
import com.tonyodev.fetch2.DefaultFetchNotificationManager
|
||||
import com.tonyodev.fetch2.DownloadNotification
|
||||
import com.tonyodev.fetch2.EXTRA_ACTION_TYPE
|
||||
import com.tonyodev.fetch2.EXTRA_DOWNLOAD_ID
|
||||
import com.tonyodev.fetch2.EXTRA_GROUP_ACTION
|
||||
import com.tonyodev.fetch2.EXTRA_NAMESPACE
|
||||
import com.tonyodev.fetch2.EXTRA_NOTIFICATION_GROUP_ID
|
||||
import com.tonyodev.fetch2.EXTRA_NOTIFICATION_ID
|
||||
import com.tonyodev.fetch2.Fetch
|
||||
import com.tonyodev.fetch2.R.drawable
|
||||
import com.tonyodev.fetch2.R.string
|
||||
import com.tonyodev.fetch2.util.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET
|
||||
import org.kiwix.kiwixmobile.core.CoreApp
|
||||
import org.kiwix.kiwixmobile.core.Intents
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
|
||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
const val DOWNLOAD_NOTIFICATION_TITLE = "OPEN_ZIM_FILE"
|
||||
|
||||
class FetchDownloadNotificationManager(
|
||||
private val context: Context,
|
||||
class FetchDownloadNotificationManager @Inject constructor(
|
||||
context: Context,
|
||||
private val downloadRoomDao: DownloadRoomDao
|
||||
) : DefaultFetchNotificationManager(context) {
|
||||
override fun getFetchInstanceForNamespace(namespace: String): Fetch = Fetch.getDefaultInstance()
|
||||
|
||||
override fun registerBroadcastReceiver() {
|
||||
val context = CoreApp.instance.applicationContext
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.registerReceiver(
|
||||
broadcastReceiver,
|
||||
IntentFilter(notificationManagerAction),
|
||||
Context.RECEIVER_EXPORTED
|
||||
)
|
||||
} else {
|
||||
context.registerReceiver(
|
||||
broadcastReceiver,
|
||||
IntentFilter(notificationManagerAction)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createNotificationChannels(
|
||||
context: Context,
|
||||
notificationManager: NotificationManager
|
||||
@ -90,7 +96,6 @@ class FetchDownloadNotificationManager(
|
||||
downloadNotification: DownloadNotification,
|
||||
context: Context
|
||||
) {
|
||||
// super method but with pause button removed
|
||||
val smallIcon = if (downloadNotification.isDownloading) {
|
||||
android.R.drawable.stat_sys_download
|
||||
} else {
|
||||
@ -124,7 +129,7 @@ class FetchDownloadNotificationManager(
|
||||
getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.DELETE)
|
||||
).addAction(
|
||||
drawable.fetch_notification_pause,
|
||||
context.getString(R.string.tts_pause),
|
||||
context.getString(R.string.notification_pause_button_text),
|
||||
getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.PAUSE)
|
||||
)
|
||||
|
||||
@ -132,7 +137,7 @@ class FetchDownloadNotificationManager(
|
||||
notificationBuilder.setTimeoutAfter(getNotificationTimeOutMillis())
|
||||
.addAction(
|
||||
drawable.fetch_notification_resume,
|
||||
context.getString(R.string.tts_resume),
|
||||
context.getString(R.string.notification_resume_button_text),
|
||||
getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.RESUME)
|
||||
)
|
||||
.addAction(
|
||||
@ -149,35 +154,6 @@ class FetchDownloadNotificationManager(
|
||||
notificationCustomisation(downloadNotification, notificationBuilder, context)
|
||||
}
|
||||
|
||||
override fun getActionPendingIntent(
|
||||
downloadNotification: DownloadNotification,
|
||||
actionType: DownloadNotification.ActionType
|
||||
): PendingIntent {
|
||||
val intent = Intent(notificationManagerAction).apply {
|
||||
putExtra(EXTRA_NAMESPACE, downloadNotification.namespace)
|
||||
putExtra(EXTRA_DOWNLOAD_ID, downloadNotification.notificationId)
|
||||
putExtra(EXTRA_NOTIFICATION_ID, downloadNotification.notificationId)
|
||||
putExtra(EXTRA_GROUP_ACTION, false)
|
||||
putExtra(EXTRA_NOTIFICATION_GROUP_ID, downloadNotification.groupId)
|
||||
}
|
||||
val action = when (actionType) {
|
||||
DownloadNotification.ActionType.CANCEL -> ACTION_TYPE_CANCEL
|
||||
DownloadNotification.ActionType.DELETE -> ACTION_TYPE_DELETE
|
||||
DownloadNotification.ActionType.RESUME -> ACTION_TYPE_RESUME
|
||||
DownloadNotification.ActionType.PAUSE -> ACTION_TYPE_PAUSE
|
||||
DownloadNotification.ActionType.RETRY -> ACTION_TYPE_RETRY
|
||||
else -> ACTION_TYPE_INVALID
|
||||
}
|
||||
intent.putExtra(EXTRA_ACTION_TYPE, action)
|
||||
val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
return PendingIntent.getBroadcast(
|
||||
context,
|
||||
downloadNotification.notificationId + action,
|
||||
intent,
|
||||
flags
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
private fun notificationCustomisation(
|
||||
downloadNotification: DownloadNotification,
|
||||
|
@ -52,9 +52,13 @@ import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions.Super.ShouldCa
|
||||
import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator
|
||||
import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToRoomMigrator
|
||||
import org.kiwix.kiwixmobile.core.di.components.CoreActivityComponent
|
||||
import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadMonitorService
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadMonitorService.Companion.STOP_DOWNLOAD_SERVICE
|
||||
import org.kiwix.kiwixmobile.core.error.ErrorActivity
|
||||
import org.kiwix.kiwixmobile.core.extensions.browserIntent
|
||||
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
|
||||
import org.kiwix.kiwixmobile.core.extensions.isServiceRunning
|
||||
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||
@ -98,6 +102,9 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
|
||||
@Inject lateinit var objectBoxToLibkiwixMigrator: ObjectBoxToLibkiwixMigrator
|
||||
@Inject lateinit var objectBoxToRoomMigrator: ObjectBoxToRoomMigrator
|
||||
|
||||
@Inject
|
||||
lateinit var downloadMonitor: DownloadMonitor
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(R.style.KiwixTheme)
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -140,17 +147,51 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
downloadMonitor.startMonitoringDownload()
|
||||
stopDownloadServiceIfRunning()
|
||||
rateDialogHandler.checkForRateDialog(getIconResId())
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
configureActivityBasedOn(destination)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the DownloadService if it is currently running,
|
||||
* as the application is now in the foreground and can handle downloads directly.
|
||||
*/
|
||||
private fun stopDownloadServiceIfRunning() {
|
||||
if (isServiceRunning(DownloadMonitorService::class.java)) {
|
||||
startService(
|
||||
Intent(
|
||||
this,
|
||||
DownloadMonitorService::class.java
|
||||
).setAction(STOP_DOWNLOAD_SERVICE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
onBackPressedCallBack.remove()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
startMonitoringDownloads()
|
||||
downloadMonitor.stopListeningDownloads()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts monitoring the downloads by ensuring that the `DownloadMonitorService` is running.
|
||||
* This service keeps the Fetch instance alive when the application is in the background
|
||||
* or has been killed by the user or system, allowing downloads to continue in the background.
|
||||
*/
|
||||
private fun startMonitoringDownloads() {
|
||||
if (!isServiceRunning(DownloadMonitorService::class.java)) {
|
||||
startService(Intent(this, DownloadMonitorService::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
open fun configureActivityBasedOn(destination: NavDestination) {
|
||||
if (destination.id !in topLevelDestinations) {
|
||||
handleDrawerOnNavigation()
|
||||
|
@ -1851,8 +1851,7 @@ abstract class CoreReaderFragment :
|
||||
setIsCloseAllTabButtonClickable(true)
|
||||
restoreDeletedTabs()
|
||||
}
|
||||
show()
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,19 +78,19 @@ abstract class CorePrefsFragment :
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
protected var sharedPreferenceUtil: SharedPreferenceUtil? = null
|
||||
var sharedPreferenceUtil: SharedPreferenceUtil? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
protected var storageCalculator: StorageCalculator? = null
|
||||
var storageCalculator: StorageCalculator? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
protected var darkModeConfig: DarkModeConfig? = null
|
||||
var darkModeConfig: DarkModeConfig? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
protected var alertDialogShower: DialogShower? = null
|
||||
var alertDialogShower: DialogShower? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
|
@ -147,6 +147,8 @@
|
||||
<string name="tts_pause">pause</string>
|
||||
<string name="tts_resume">resume</string>
|
||||
<string name="stop">stop</string>
|
||||
<string name="notification_pause_button_text">Pause</string>
|
||||
<string name="notification_resume_button_text">Resume</string>
|
||||
<string name="internal_storage">Internal</string>
|
||||
<string name="external_storage">External</string>
|
||||
<string name="yes">Yes</string>
|
||||
|
@ -1,4 +1,5 @@
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx6096m
|
||||
kotlin.code.style=1.7
|
||||
kotlin.code.style=1.8
|
||||
kapt.use.k2=true
|
||||
|
Loading…
x
Reference in New Issue
Block a user