Fixed: the download notification controls was not working.

* Refactored/cleanup code.
* Added foreground permission in core module so that it can be used in both modules.
This commit is contained in:
MohitMaliFtechiz 2024-11-26 19:43:16 +05:30
parent 1f62caebbe
commit da9528b8ff
10 changed files with 128 additions and 221 deletions

View File

@ -8,9 +8,6 @@
tools:ignore="CoarseFineLocation" /> tools:ignore="CoarseFineLocation" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="${permission}" /> <uses-permission android:name="${permission}" />
<!-- Device with versions >= Pie need this permission -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission <uses-permission
android:name="android.permission.NEARBY_WIFI_DEVICES" android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" android:usesPermissionFlags="neverForLocation"

View File

@ -23,9 +23,7 @@ import android.app.Service
import android.content.Context import android.content.Context
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationActionsBroadcastReceiver
import org.kiwix.kiwixmobile.core.qr.GenerateQR import org.kiwix.kiwixmobile.core.qr.GenerateQR
import org.kiwix.kiwixmobile.core.read_aloud.ReadAloudNotificationManger
import org.kiwix.kiwixmobile.di.ServiceScope import org.kiwix.kiwixmobile.di.ServiceScope
import org.kiwix.kiwixmobile.webserver.KiwixServer import org.kiwix.kiwixmobile.webserver.KiwixServer
import org.kiwix.kiwixmobile.webserver.WebServerHelper import org.kiwix.kiwixmobile.webserver.WebServerHelper
@ -35,12 +33,6 @@ import org.kiwix.kiwixmobile.webserver.wifi_hotspot.IpAddressCallbacks
@Module @Module
class ServiceModule { class ServiceModule {
@Provides
@ServiceScope
fun providesReadAloudNotificationManager(
notificationManager: NotificationManager,
context: Context
): ReadAloudNotificationManger = ReadAloudNotificationManger(notificationManager, context)
@Provides @Provides
@ServiceScope @ServiceScope
@ -76,17 +68,4 @@ class ServiceModule {
@Provides @Provides
@ServiceScope @ServiceScope
fun providesGenerateQr(): GenerateQR = GenerateQR() fun providesGenerateQr(): GenerateQR = GenerateQR()
@Provides
@ServiceScope
fun providesDownloadNotificationActionsBroadcastReceiverCallback(service: Service):
DownloadNotificationActionsBroadcastReceiver.Callback =
service as DownloadNotificationActionsBroadcastReceiver.Callback
@Provides
@ServiceScope
fun providesDownloadNotificationActionsBroadcastReceiver(
callBack: DownloadNotificationActionsBroadcastReceiver.Callback
): DownloadNotificationActionsBroadcastReceiver =
DownloadNotificationActionsBroadcastReceiver(callBack)
} }

View File

@ -16,6 +16,9 @@
<uses-permission <uses-permission
android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<!-- Device with versions >= Pie need this permission -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.TTS_SERVICE" /> <action android:name="android.intent.action.TTS_SERVICE" />

View File

@ -35,8 +35,8 @@ const val CONNECTION_TIMEOUT = 10L
// increase the read and call timeout since the content is 19MB large so it takes // increase the read and call timeout since the content is 19MB large so it takes
// more time to read on slow internet connection, and due to less read timeout // more time to read on slow internet connection, and due to less read timeout
// the request is canceled. // the request is canceled.
const val READ_TIMEOUT = 180L const val READ_TIMEOUT = 300L
const val CALL_TIMEOUT = 180L const val CALL_TIMEOUT = 300L
const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}" const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}"
const val KIWIX_DOWNLOAD_URL = "https://mirror.download.kiwix.org/" const val KIWIX_DOWNLOAD_URL = "https://mirror.download.kiwix.org/"

View File

@ -19,94 +19,82 @@
package org.kiwix.kiwixmobile.core.downloader.downloadManager package org.kiwix.kiwixmobile.core.downloader.downloadManager
import android.app.DownloadManager import android.app.DownloadManager
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_CANCEL
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_PAUSE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_QUERY_DOWNLOAD_STATUS
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_RESUME
import javax.inject.Inject import javax.inject.Inject
class DownloadManagerMonitor @Inject constructor( class DownloadManagerMonitor @Inject constructor(
val downloadRoomDao: DownloadRoomDao, val downloadRoomDao: DownloadRoomDao,
private val context: Context private val context: Context
) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback, DownloadMonitorServiceCallback { ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback {
private val lock = Any() private val lock = Any()
private var downloadMonitorService: DownloadMonitorService? = null
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
downloadMonitorService =
(binder as? DownloadMonitorService.DownloadMonitorBinder)?.downloadMonitorService?.get()
downloadMonitorService?.registerCallback(this@DownloadManagerMonitor)
CoroutineScope(Dispatchers.IO).launch {
if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) {
startService()
}
}
}
override fun onServiceDisconnected(name: ComponentName?) {
downloadMonitorService = null
}
}
init { init {
bindService() CoroutineScope(Dispatchers.IO).launch {
if (getActiveDownloads().isNotEmpty()) {
startService()
}
}
} }
private fun bindService() { private suspend fun getActiveDownloads(): List<DownloadRoomEntity> =
val serviceIntent = Intent(context, DownloadMonitorService::class.java) downloadRoomDao.downloadRoomEntity().blockingFirst().filter {
context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE) it.status != Status.PAUSED && it.status != Status.CANCELLED
} }
override fun downloadCompleteOrCancelled(intent: Intent) { override fun downloadCompleteOrCancelled(intent: Intent) {
synchronized(lock) { synchronized(lock) {
intent.extras?.let { intent.extras?.let {
val downloadId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L) val downloadId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
if (downloadId != -1L) { if (downloadId != -1L) {
downloadMonitorService?.queryDownloadStatus(downloadId) context.startService(
getDownloadMonitorIntent(
ACTION_QUERY_DOWNLOAD_STATUS,
downloadId.toInt()
)
)
} }
} }
} }
} }
fun startMonitoringDownloads() { fun startMonitoringDownloads() {
bindService()
startService() startService()
downloadMonitorService?.startMonitoringDownloads()
} }
private fun startService() { private fun startService() {
ContextCompat.startForegroundService( context.startService(Intent(context, DownloadMonitorService::class.java))
context,
Intent(context, DownloadMonitorService::class.java)
)
} }
fun pauseDownload(downloadId: Long) { fun pauseDownload(downloadId: Long) {
downloadMonitorService?.pauseDownload(downloadId) context.startService(getDownloadMonitorIntent(ACTION_PAUSE, downloadId.toInt()))
} }
fun resumeDownload(downloadId: Long) { fun resumeDownload(downloadId: Long) {
downloadMonitorService?.resumeDownload(downloadId) context.startService(getDownloadMonitorIntent(ACTION_RESUME, downloadId.toInt()))
} }
fun cancelDownload(downloadId: Long) { fun cancelDownload(downloadId: Long) {
downloadMonitorService?.cancelDownload(downloadId) context.startService(getDownloadMonitorIntent(ACTION_CANCEL, downloadId.toInt()))
} }
private fun getDownloadMonitorIntent(action: String, downloadId: Int): Intent =
Intent(context, DownloadMonitorService::class.java).apply {
putExtra(DownloadNotificationManager.NOTIFICATION_ACTION, action)
putExtra(DownloadNotificationManager.EXTRA_DOWNLOAD_ID, downloadId)
}
override fun init() { override fun init() {
// empty method to so class does not get reported unused // empty method to so class does not get reported unused
} }
override fun onServiceDestroyed() {
downloadMonitorService?.registerCallback(null)
context.unbindService(serviceConnection)
downloadMonitorService = null
}
} }

View File

@ -41,12 +41,7 @@ class DownloadManagerRequester @Inject constructor(
private val downloadManagerMonitor: DownloadManagerMonitor private val downloadManagerMonitor: DownloadManagerMonitor
) : DownloadRequester { ) : DownloadRequester {
override fun enqueue(downloadRequest: DownloadRequest): Long = override fun enqueue(downloadRequest: DownloadRequest): Long =
downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)).also { downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil))
Log.e(
"DOWNLOADING_STEP",
"enqueue: ${downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)}"
)
}
override fun onDownloadAdded() { override fun onDownloadAdded() {
// Start monitoring downloads after enqueuing a new download request. // Start monitoring downloads after enqueuing a new download request.

View File

@ -26,7 +26,6 @@ import android.content.ContentValues
import android.content.Intent import android.content.Intent
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Binder
import android.os.IBinder import android.os.IBinder
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -35,10 +34,13 @@ import io.reactivex.subjects.PublishSubject
import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_CANCEL
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_PAUSE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_QUERY_DOWNLOAD_STATUS
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_RESUME
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
import org.kiwix.kiwixmobile.core.utils.files.Log import org.kiwix.kiwixmobile.core.utils.files.Log
import java.lang.ref.WeakReference
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -59,7 +61,7 @@ const val STATUS_PAUSED_BY_APP = 193
const val COLUMN_CONTROL = "control" const val COLUMN_CONTROL = "control"
val downloadBaseUri: Uri = Uri.parse("content://downloads/my_downloads") val downloadBaseUri: Uri = Uri.parse("content://downloads/my_downloads")
class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastReceiver.Callback { class DownloadMonitorService : Service() {
@Inject @Inject
lateinit var downloadManager: DownloadManager lateinit var downloadManager: DownloadManager
@ -73,19 +75,9 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
private var monitoringDisposable: Disposable? = null private var monitoringDisposable: Disposable? = null
private val downloadInfoMap = mutableMapOf<Long, DownloadInfo>() private val downloadInfoMap = mutableMapOf<Long, DownloadInfo>()
private val updater = PublishSubject.create<() -> Unit>() private val updater = PublishSubject.create<() -> Unit>()
private val downloadMonitorBinder: IBinder = DownloadMonitorBinder(this) private var foreGroundServiceInformation: Pair<Boolean, Int> = true to DEFAULT_INT_VALUE
private var downloadMonitorServiceCallback: DownloadMonitorServiceCallback? = null
private var isForeGroundServiceNotification: Boolean = true
// @set:Inject override fun onBind(intent: Intent?): IBinder? = null
// var downloadNotificationBroadcastReceiver: DownloadNotificationActionsBroadcastReceiver? = null
class DownloadMonitorBinder(downloadMonitorService: DownloadMonitorService) : Binder() {
val downloadMonitorService: WeakReference<DownloadMonitorService> =
WeakReference<DownloadMonitorService>(downloadMonitorService)
}
override fun onBind(intent: Intent?): IBinder = downloadMonitorBinder
override fun onCreate() { override fun onCreate() {
CoreApp.coreComponent CoreApp.coreComponent
@ -96,13 +88,22 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
super.onCreate() super.onCreate()
setupUpdater() setupUpdater()
startMonitoringDownloads() startMonitoringDownloads()
// downloadNotificationBroadcastReceiver?.let(this::registerReceiver)
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = START_NOT_STICKY override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val downloadId =
fun registerCallback(downloadMonitorServiceCallback: DownloadMonitorServiceCallback?) { intent?.getIntExtra(DownloadNotificationManager.EXTRA_DOWNLOAD_ID, DEFAULT_INT_VALUE)
this.downloadMonitorServiceCallback = downloadMonitorServiceCallback ?: DEFAULT_INT_VALUE
val notificationAction = intent?.getStringExtra(DownloadNotificationManager.NOTIFICATION_ACTION)
if (downloadId != DEFAULT_INT_VALUE) {
when (notificationAction) {
ACTION_PAUSE -> pauseDownload(downloadId.toLong())
ACTION_RESUME -> resumeDownload(downloadId.toLong())
ACTION_CANCEL -> cancelDownload(downloadId.toLong())
ACTION_QUERY_DOWNLOAD_STATUS -> queryDownloadStatus(downloadId.toLong())
}
}
return START_NOT_STICKY
} }
@Suppress("CheckResult") @Suppress("CheckResult")
@ -122,8 +123,7 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
* when there are no ongoing downloads to avoid unnecessary resource usage. * when there are no ongoing downloads to avoid unnecessary resource usage.
*/ */
@Suppress("MagicNumber") @Suppress("MagicNumber")
fun startMonitoringDownloads() { private fun startMonitoringDownloads() {
Log.e("DOWNLOADING_STEP", "startMonitoringDownloads:")
// Check if monitoring is already active. If it is, do nothing. // Check if monitoring is already active. If it is, do nothing.
if (monitoringDisposable?.isDisposed == false) return if (monitoringDisposable?.isDisposed == false) return
monitoringDisposable = Observable.interval(ZERO.toLong(), 5, TimeUnit.SECONDS) monitoringDisposable = Observable.interval(ZERO.toLong(), 5, TimeUnit.SECONDS)
@ -142,7 +142,7 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
} }
} }
} catch (ignore: Exception) { } catch (ignore: Exception) {
Log.i( Log.e(
"DOWNLOAD_MONITOR", "DOWNLOAD_MONITOR",
"Couldn't get the downloads update. Original exception = $ignore" "Couldn't get the downloads update. Original exception = $ignore"
) )
@ -155,7 +155,6 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
@SuppressLint("Range") @SuppressLint("Range")
private fun checkDownloads() { private fun checkDownloads() {
synchronized(lock) { synchronized(lock) {
Log.e("DOWNLOADING_STEP", "checkDownloads: lock")
val query = DownloadManager.Query().setFilterByStatus( val query = DownloadManager.Query().setFilterByStatus(
DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_RUNNING or
DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PAUSED or
@ -163,7 +162,6 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
DownloadManager.STATUS_SUCCESSFUL DownloadManager.STATUS_SUCCESSFUL
) )
downloadManager.query(query).use { cursor -> downloadManager.query(query).use { cursor ->
Log.e("DOWNLOADING_STEP", "checkDownloads:")
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
val downloadId = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)) val downloadId = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID))
@ -385,10 +383,8 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
) { ) {
synchronized(lock) { synchronized(lock) {
updater.onNext { updater.onNext {
Log.e("DOWNLOADING_STEP", "updateDownloadStatus:")
downloadRoomDao.getEntityForDownloadId(downloadId)?.let { downloadEntity -> downloadRoomDao.getEntityForDownloadId(downloadId)?.let { downloadEntity ->
if (shouldUpdateDownloadStatus(downloadEntity)) { if (shouldUpdateDownloadStatus(downloadEntity)) {
Log.e("DOWNLOADING_STEP", "shouldUpdateDownloadStatus:")
val downloadModel = DownloadModel(downloadEntity).apply { val downloadModel = DownloadModel(downloadEntity).apply {
if (shouldUpdateDownloadStatus(status, error, downloadEntity)) { if (shouldUpdateDownloadStatus(status, error, downloadEntity)) {
state = status state = status
@ -406,7 +402,6 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
} }
} }
downloadRoomDao.update(downloadModel) downloadRoomDao.update(downloadModel)
Log.e("DOWNLOADING_STEP", "updateNotification: $downloadModel")
updateNotification(downloadModel, downloadEntity.title, downloadEntity.description) updateNotification(downloadModel, downloadEntity.title, downloadEntity.description)
return@let return@let
} }
@ -472,14 +467,63 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
private fun cancelNotification(downloadId: Long) { private fun cancelNotification(downloadId: Long) {
downloadNotificationManager.cancelNotification(downloadId.toInt()) downloadNotificationManager.cancelNotification(downloadId.toInt())
assignNewForegroundNotification()
} }
private fun assignNewForegroundNotification() {
val activeDownloads = getActiveDownloads()
if (activeDownloads.isNotEmpty()) {
// Promote the first active download to foreground
val downloadRoomEntity = activeDownloads.first()
foreGroundServiceInformation =
foreGroundServiceInformation.first to downloadRoomEntity.downloadId.toInt()
val downloadNotificationModel =
getDownloadNotificationModel(
DownloadModel(downloadRoomEntity),
downloadRoomEntity.title,
downloadRoomEntity.description
)
val notification = downloadNotificationManager.createNotification(downloadNotificationModel)
startForeground(foreGroundServiceInformation.second, notification)
} else {
// Stop the service if no active downloads remain
stopMonitoringDownloads()
}
}
private fun getActiveDownloads(): List<DownloadRoomEntity> =
downloadRoomDao.downloadRoomEntity().blockingFirst().filter {
it.status != Status.PAUSED && it.status != Status.CANCELLED
}
private fun updateNotification( private fun updateNotification(
downloadModel: DownloadModel, downloadModel: DownloadModel,
title: String, title: String,
description: String? description: String?
) { ) {
val downloadNotificationModel = DownloadNotificationModel( val downloadNotificationModel = getDownloadNotificationModel(downloadModel, title, description)
val notification = downloadNotificationManager.createNotification(downloadNotificationModel)
if (foreGroundServiceInformation.first) {
startForeground(downloadModel.downloadId.toInt(), notification)
foreGroundServiceInformation = false to downloadModel.downloadId.toInt()
} else {
downloadNotificationManager.updateNotification(
downloadNotificationModel,
object : AssignNewForegroundServiceNotification {
override fun assignNewForegroundServiceNotification(downloadId: Long) {
cancelNotification(downloadId)
}
}
)
}
}
private fun getDownloadNotificationModel(
downloadModel: DownloadModel,
title: String,
description: String?
): DownloadNotificationModel =
DownloadNotificationModel(
downloadId = downloadModel.downloadId.toInt(), downloadId = downloadModel.downloadId.toInt(),
status = downloadModel.state, status = downloadModel.state,
progress = downloadModel.progress, progress = downloadModel.progress,
@ -493,28 +537,8 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
downloadModel.book.url downloadModel.book.url
).toReadableState(this).toString() ).toReadableState(this).toString()
) )
val notification = downloadNotificationManager.createNotification(downloadNotificationModel)
if (isForeGroundServiceNotification) {
startForeground(downloadModel.downloadId.toInt(), notification)
isForeGroundServiceNotification = false
} else {
downloadNotificationManager.updateNotification(downloadNotificationModel)
}
}
override fun pauseDownloads(downloadId: Long) { private fun pauseDownload(downloadId: Long) {
pauseDownload(downloadId)
}
override fun resumeDownloads(downloadId: Long) {
resumeDownload(downloadId)
}
override fun cancelDownloads(downloadId: Long) {
cancelNotification(downloadId)
}
fun pauseDownload(downloadId: Long) {
synchronized(lock) { synchronized(lock) {
updater.onNext { updater.onNext {
if (pauseResumeDownloadInDownloadManagerContentResolver( if (pauseResumeDownloadInDownloadManagerContentResolver(
@ -529,7 +553,7 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
} }
} }
fun resumeDownload(downloadId: Long) { private fun resumeDownload(downloadId: Long) {
synchronized(lock) { synchronized(lock) {
updater.onNext { updater.onNext {
if (pauseResumeDownloadInDownloadManagerContentResolver( if (pauseResumeDownloadInDownloadManagerContentResolver(
@ -544,7 +568,7 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
} }
} }
fun cancelDownload(downloadId: Long) { private fun cancelDownload(downloadId: Long) {
synchronized(lock) { synchronized(lock) {
downloadManager.remove(downloadId) downloadManager.remove(downloadId)
handleCancelledDownload(downloadId) handleCancelledDownload(downloadId)
@ -577,27 +601,23 @@ class DownloadMonitorService : Service(), DownloadNotificationActionsBroadcastRe
downloadRoomEntity.status != Status.COMPLETED downloadRoomEntity.status != Status.COMPLETED
override fun onDestroy() { override fun onDestroy() {
// downloadNotificationBroadcastReceiver?.let(::unregisterReceiver)
monitoringDisposable?.dispose() monitoringDisposable?.dispose()
super.onDestroy() super.onDestroy()
} }
private fun stopMonitoringDownloads() { private fun stopMonitoringDownloads() {
Log.e("DOWNLOADING_STEP", "stopMonitoringDownloads: ") foreGroundServiceInformation = true to DEFAULT_INT_VALUE
isForeGroundServiceNotification = true
monitoringDisposable?.dispose() monitoringDisposable?.dispose()
downloadMonitorServiceCallback?.onServiceDestroyed()
stopForeground(STOP_FOREGROUND_REMOVE) stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf() stopSelf()
} }
companion object {
private const val CHANNEL_ID = "download_monitor_channel"
private const val ONGOING_NOTIFICATION_ID = 1
}
} }
data class DownloadInfo( data class DownloadInfo(
var startTime: Long, var startTime: Long,
var initialBytesDownloaded: Int var initialBytesDownloaded: Int
) )
interface AssignNewForegroundServiceNotification {
fun assignNewForegroundServiceNotification(downloadId: Long)
}

View File

@ -1,23 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.downloader.downloadManager
interface DownloadMonitorServiceCallback {
fun onServiceDestroyed()
}

View File

@ -1,56 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.downloader.downloadManager
import android.content.Context
import android.content.Intent
import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_CANCEL
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_PAUSE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_RESUME
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.EXTRA_DOWNLOAD_ID
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.NOTIFICATION_ACTION
import javax.inject.Inject
const val DOWNLOAD_NOTIFICATION_ACTION = "org.kiwix.kiwixmobile.download_notification_action"
class DownloadNotificationActionsBroadcastReceiver @Inject constructor(
private val callback: Callback
) :
BaseBroadcastReceiver() {
override val action: String = DOWNLOAD_NOTIFICATION_ACTION
override fun onIntentWithActionReceived(context: Context, intent: Intent) {
val downloadId = intent.getIntExtra(EXTRA_DOWNLOAD_ID, -1)
val notificationAction = intent.getStringExtra(NOTIFICATION_ACTION)
if (downloadId != -1) {
when (notificationAction) {
ACTION_PAUSE -> callback.pauseDownloads(downloadId.toLong())
ACTION_RESUME -> callback.resumeDownloads(downloadId.toLong())
ACTION_CANCEL -> callback.cancelDownloads(downloadId.toLong())
}
}
}
interface Callback {
fun pauseDownloads(downloadId: Long)
fun resumeDownloads(downloadId: Long)
fun cancelDownloads(downloadId: Long)
}
}

View File

@ -54,7 +54,8 @@ class DownloadNotificationManager @Inject constructor(
} }
fun updateNotification( fun updateNotification(
downloadNotificationModel: DownloadNotificationModel downloadNotificationModel: DownloadNotificationModel,
assignNewForegroundServiceNotification: AssignNewForegroundServiceNotification
) { ) {
synchronized(downloadNotificationsBuilderMap) { synchronized(downloadNotificationsBuilderMap) {
if (shouldUpdateNotification(downloadNotificationModel)) { if (shouldUpdateNotification(downloadNotificationModel)) {
@ -64,7 +65,9 @@ class DownloadNotificationManager @Inject constructor(
) )
} else { } else {
// the download is cancelled/paused so remove the notification. // the download is cancelled/paused so remove the notification.
cancelNotification(downloadNotificationModel.downloadId) assignNewForegroundServiceNotification.assignNewForegroundServiceNotification(
downloadNotificationModel.downloadId.toLong()
)
} }
} }
} }
@ -191,16 +194,16 @@ class DownloadNotificationManager @Inject constructor(
} }
private fun getActionPendingIntent(action: String, downloadId: Int): PendingIntent { private fun getActionPendingIntent(action: String, downloadId: Int): PendingIntent {
val intent = val pendingIntent =
Intent(DOWNLOAD_NOTIFICATION_ACTION).apply { Intent(context, DownloadMonitorService::class.java).apply {
putExtra(NOTIFICATION_ACTION, action) putExtra(NOTIFICATION_ACTION, action)
putExtra(EXTRA_DOWNLOAD_ID, downloadId) putExtra(EXTRA_DOWNLOAD_ID, downloadId)
} }
val requestCode = downloadId + action.hashCode() val requestCode = downloadId + action.hashCode()
return PendingIntent.getBroadcast( return PendingIntent.getService(
context, context,
requestCode, requestCode,
intent, pendingIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
) )
} }
@ -250,6 +253,7 @@ class DownloadNotificationManager @Inject constructor(
const val ACTION_PAUSE = "action_pause" const val ACTION_PAUSE = "action_pause"
const val ACTION_RESUME = "action_resume" const val ACTION_RESUME = "action_resume"
const val ACTION_CANCEL = "action_cancel" const val ACTION_CANCEL = "action_cancel"
const val ACTION_QUERY_DOWNLOAD_STATUS = "action_query_download_status"
const val EXTRA_DOWNLOAD_ID = "extra_download_id" const val EXTRA_DOWNLOAD_ID = "extra_download_id"
} }
} }