Merge pull request #4210 from kiwix/Fixes#4207

Fixed: Download notification disappears when pausing a download.
This commit is contained in:
Kelson 2025-02-06 06:49:05 +01:00 committed by GitHub
commit c1a3b2bc40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 157 additions and 10 deletions

View File

@ -41,7 +41,8 @@ const val DEFAULT_INT_VALUE = -1
class DownloadManagerMonitor @Inject constructor( class DownloadManagerMonitor @Inject constructor(
val fetch: Fetch, val fetch: Fetch,
val context: Context, val context: Context,
val downloadRoomDao: DownloadRoomDao val downloadRoomDao: DownloadRoomDao,
private val fetchDownloadNotificationManager: FetchDownloadNotificationManager
) : DownloadMonitor { ) : DownloadMonitor {
private val updater = PublishSubject.create<() -> Unit>() private val updater = PublishSubject.create<() -> Unit>()
private var updaterDisposable: Disposable? = null private var updaterDisposable: Disposable? = null
@ -122,7 +123,12 @@ class DownloadManagerMonitor @Inject constructor(
} }
private fun update(download: Download) { private fun update(download: Download) {
updater.onNext { downloadRoomDao.update(download) } updater.onNext {
downloadRoomDao.update(download)
if (download.isPaused()) {
fetchDownloadNotificationManager.showDownloadPauseNotification(fetch, download)
}
}
} }
private fun delete(download: Download) { private fun delete(download: Download) {

View File

@ -116,12 +116,9 @@ class DownloadMonitorService : Service() {
it.status == Status.NONE || it.status == Status.NONE ||
it.status == Status.ADDED || it.status == Status.ADDED ||
it.status == Status.QUEUED || it.status == Status.QUEUED ||
it.status == Status.DOWNLOADING it.status == Status.DOWNLOADING ||
}?.let { it.isPaused()
val notificationBuilder = }?.let(::setForegroundNotificationForDownload) ?: kotlin.run {
fetchDownloadNotificationManager.getNotificationBuilder(it.id, it.id)
startForeground(it.id, notificationBuilder.build())
} ?: kotlin.run {
stopForegroundServiceForDownloads() stopForegroundServiceForDownloads()
// Cancel the last ongoing notification after detaching it from // Cancel the last ongoing notification after detaching it from
// the foreground service if no active downloads are found. // the foreground service if no active downloads are found.
@ -131,6 +128,21 @@ class DownloadMonitorService : Service() {
} }
} }
private fun setForegroundNotificationForDownload(it: Download) {
val notificationBuilder =
fetchDownloadNotificationManager.getNotificationBuilder(it.id, it.id)
var foreGroundServiceNotification = notificationBuilder.build()
if (it.isPaused()) {
// Clear any pending actions on this notification builder.
notificationBuilder.clearActions()
// If a download is paused that means there is no notification for it, so we have to
// show our custom cancel notification.
foreGroundServiceNotification =
fetchDownloadNotificationManager.getCancelNotification(fetch, it, notificationBuilder)
}
startForeground(it.id, foreGroundServiceNotification)
}
private fun cancelNotificationForId(downloadId: Int) { private fun cancelNotificationForId(downloadId: Int) {
notificationManager.cancel(downloadId) notificationManager.cancel(downloadId)
} }
@ -165,7 +177,7 @@ class DownloadMonitorService : Service() {
} }
override fun onPaused(download: Download) { override fun onPaused(download: Download) {
update(download, true) update(download)
} }
override fun onProgress( override fun onProgress(
@ -213,6 +225,13 @@ class DownloadMonitorService : Service() {
downloadRoomDao.downloads().blockingFirst() downloadRoomDao.downloads().blockingFirst()
} }
} }
// If someone pause the Download then post a notification since fetch removes the
// notification for ongoing download when pause so we needs to show our custom notification.
if (download.isPaused()) {
fetchDownloadNotificationManager.showDownloadPauseNotification(fetch, download).also {
setForeGroundServiceNotificationIfNoActiveDownloads(fetch, download)
}
}
if (shouldSetForegroundNotification) { if (shouldSetForegroundNotification) {
setForegroundNotification(download.id) setForegroundNotification(download.id)
} }
@ -227,6 +246,24 @@ class DownloadMonitorService : Service() {
} }
} }
private fun setForeGroundServiceNotificationIfNoActiveDownloads(
fetch: Fetch,
download: Download
) {
updater.onNext {
// Check if there are any ongoing downloads.
// If the list is empty, it means no other downloads are running,
// so we need to promote this download to a foreground service.
fetch.getDownloadsWithStatus(
listOf(Status.NONE, Status.ADDED, Status.QUEUED, Status.DOWNLOADING)
) { activeDownloads ->
if (activeDownloads.isEmpty()) {
setForegroundNotificationForDownload(download)
}
}
}
}
@Suppress("MagicNumber") @Suppress("MagicNumber")
private fun showDownloadCompletedNotification(download: Download) { private fun showDownloadCompletedNotification(download: Download) {
downloadNotificationChannel() downloadNotificationChannel()

View File

@ -19,8 +19,10 @@
package org.kiwix.kiwixmobile.core.downloader.downloadManager package org.kiwix.kiwixmobile.core.downloader.downloadManager
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.PendingIntent.getActivity import android.app.PendingIntent.getActivity
@ -30,12 +32,36 @@ import android.content.IntentFilter
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.Builder
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.DefaultFetchNotificationManager
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.DownloadNotification import com.tonyodev.fetch2.DownloadNotification
import com.tonyodev.fetch2.DownloadNotification.ActionType.CANCEL
import com.tonyodev.fetch2.DownloadNotification.ActionType.DELETE
import com.tonyodev.fetch2.DownloadNotification.ActionType.PAUSE
import com.tonyodev.fetch2.DownloadNotification.ActionType.RESUME
import com.tonyodev.fetch2.DownloadNotification.ActionType.RETRY
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.Fetch
import com.tonyodev.fetch2.R.drawable import com.tonyodev.fetch2.R.drawable
import com.tonyodev.fetch2.R.string import com.tonyodev.fetch2.R.string
import com.tonyodev.fetch2.Status
import com.tonyodev.fetch2.util.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET import com.tonyodev.fetch2.util.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.Intents import org.kiwix.kiwixmobile.core.Intents
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
@ -46,9 +72,13 @@ import javax.inject.Inject
const val DOWNLOAD_NOTIFICATION_TITLE = "OPEN_ZIM_FILE" const val DOWNLOAD_NOTIFICATION_TITLE = "OPEN_ZIM_FILE"
class FetchDownloadNotificationManager @Inject constructor( class FetchDownloadNotificationManager @Inject constructor(
context: Context, val context: Context,
private val downloadRoomDao: DownloadRoomDao private val downloadRoomDao: DownloadRoomDao
) : DefaultFetchNotificationManager(context) { ) : DefaultFetchNotificationManager(context) {
private val downloadNotificationManager: NotificationManager by lazy {
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
override fun getFetchInstanceForNamespace(namespace: String): Fetch = Fetch.getDefaultInstance() override fun getFetchInstanceForNamespace(namespace: String): Fetch = Fetch.getDefaultInstance()
override fun registerBroadcastReceiver() { override fun registerBroadcastReceiver() {
@ -176,4 +206,78 @@ class FetchDownloadNotificationManager @Inject constructor(
notificationBuilder.setAutoCancel(true) notificationBuilder.setAutoCancel(true)
} }
} }
fun showDownloadPauseNotification(fetch: Fetch, download: Download) {
CoroutineScope(Dispatchers.IO).launch {
val notificationBuilder = getNotificationBuilder(download.id, download.id)
val cancelNotification = getCancelNotification(fetch, download, notificationBuilder)
downloadNotificationManager.notify(download.id, cancelNotification)
} }
}
fun getCancelNotification(
fetch: Fetch,
download: Download,
notificationBuilder: Builder
): Notification {
val downloadTitle = getDownloadNotificationTitle(download)
val notificationTitle =
runBlocking(Dispatchers.IO) {
downloadRoomDao.getEntityForFileName(downloadTitle)?.title
?: downloadTitle
}
return notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentTitle(notificationTitle)
.setContentText(context.getString(string.fetch_notification_download_paused))
// Set the ongoing true so that could not cancel the pause notification.
// However, on Android 14 and above user can cancel the notification by swipe right so we
// can't control that see https://developer.android.com/about/versions/14/behavior-changes-all#non-dismissable-notifications
.setOngoing(true)
.setGroup(download.id.toString())
.setGroupSummary(false)
.setProgress(HUNDERED, download.progress, false)
.addAction(
drawable.fetch_notification_cancel,
context.getString(R.string.cancel),
getActionPendingIntent(fetch, download, DownloadNotification.ActionType.DELETE)
)
.addAction(
drawable.fetch_notification_resume,
context.getString(R.string.notification_resume_button_text),
getActionPendingIntent(fetch, download, DownloadNotification.ActionType.RESUME)
)
.build()
}
private fun getActionPendingIntent(
fetch: Fetch,
download: Download,
actionType: DownloadNotification.ActionType
): PendingIntent {
val intent = Intent(notificationManagerAction).apply {
putExtra(EXTRA_NAMESPACE, fetch.namespace)
putExtra(EXTRA_DOWNLOAD_ID, download.id)
putExtra(EXTRA_NOTIFICATION_ID, download.id)
putExtra(EXTRA_GROUP_ACTION, false)
putExtra(EXTRA_NOTIFICATION_GROUP_ID, download.id)
}
val action = when (actionType) {
CANCEL -> ACTION_TYPE_CANCEL
DELETE -> ACTION_TYPE_DELETE
RESUME -> ACTION_TYPE_RESUME
PAUSE -> ACTION_TYPE_PAUSE
RETRY -> ACTION_TYPE_RETRY
else -> ACTION_TYPE_INVALID
}
intent.putExtra(EXTRA_ACTION_TYPE, action)
return PendingIntent.getBroadcast(
context,
download.id + action,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
}
}
fun Download.isPaused() = status == Status.PAUSED