mirror of
https://github.com/mediathekview/zapp.git
synced 2024-09-19 20:02:17 +02:00
Remove fetch from project
This commit is contained in:
parent
72b3bfa6c9
commit
922b80934c
@ -141,10 +141,6 @@ dependencies {
|
||||
// markdown
|
||||
implementation 'ru.noties:markwon:2.0.2'
|
||||
|
||||
// fetch download manager
|
||||
implementation 'androidx.tonyodev.fetch2:xfetch2:3.1.6'
|
||||
implementation 'androidx.tonyodev.fetch2okhttp:xfetch2okhttp:3.1.6'
|
||||
|
||||
// work manager
|
||||
def work_version = "2.7.1"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
|
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
@ -1,5 +1 @@
|
||||
## start fetch
|
||||
## fixes https://github.com/mediathekview/zapp/issues/224 and https://github.com/mediathekview/zapp/issues/249
|
||||
-keep class com.tonyodev.fetch2.** { *; }
|
||||
-keep class com.tonyodev.fetch2core.** { *; }
|
||||
## end fetch
|
||||
|
||||
|
@ -9,7 +9,6 @@ import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.net.toFile
|
||||
import com.tonyodev.fetch2.Download
|
||||
import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository
|
||||
import de.christinecoenen.code.zapp.models.shows.DownloadStatus
|
||||
import de.christinecoenen.code.zapp.models.shows.MediathekShow
|
||||
@ -25,18 +24,6 @@ class DownloadFileInfoManager(
|
||||
private val settingsRepository: SettingsRepository
|
||||
) {
|
||||
|
||||
fun deleteDownloadFile(download: Download) {
|
||||
val file = File(download.file)
|
||||
|
||||
try {
|
||||
file.delete()
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e)
|
||||
}
|
||||
|
||||
updateDownloadFileInMediaCollection(download.fileUri, DownloadStatus.DELETED)
|
||||
}
|
||||
|
||||
fun deleteDownloadFile(filePath: String) {
|
||||
val file = File(filePath)
|
||||
|
||||
@ -52,18 +39,6 @@ class DownloadFileInfoManager(
|
||||
)
|
||||
}
|
||||
|
||||
fun shouldDeleteDownload(download: Download): Boolean {
|
||||
val filePath = download.file
|
||||
|
||||
if (isMediaStoreFile(filePath)) {
|
||||
return isDeletedMediaStoreFile(filePath)
|
||||
}
|
||||
|
||||
val downloadFile = File(download.file)
|
||||
return !downloadFile.exists() &&
|
||||
Environment.MEDIA_MOUNTED == Environment.getExternalStorageState(downloadFile)
|
||||
}
|
||||
|
||||
fun shouldDeleteDownload(show: PersistedMediathekShow): Boolean {
|
||||
val filePath = show.downloadedVideoPath ?: return false
|
||||
|
||||
|
@ -1,300 +0,0 @@
|
||||
package de.christinecoenen.code.zapp.app.mediathek.controller.downloads.legacy
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import com.tonyodev.fetch2.*
|
||||
import com.tonyodev.fetch2.Fetch.Impl.getInstance
|
||||
import com.tonyodev.fetch2.database.DownloadInfo
|
||||
import com.tonyodev.fetch2core.DownloadBlock
|
||||
import com.tonyodev.fetch2core.Downloader.FileDownloaderType
|
||||
import com.tonyodev.fetch2okhttp.OkHttpDownloader
|
||||
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.DownloadFileInfoManager
|
||||
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.IDownloadController
|
||||
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.DownloadException
|
||||
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.NoNetworkException
|
||||
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.WrongNetworkConditionException
|
||||
import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository
|
||||
import de.christinecoenen.code.zapp.models.shows.DownloadStatus
|
||||
import de.christinecoenen.code.zapp.models.shows.PersistedMediathekShow
|
||||
import de.christinecoenen.code.zapp.models.shows.Quality
|
||||
import de.christinecoenen.code.zapp.repositories.MediathekRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.JavaNetCookieJar
|
||||
import okhttp3.OkHttpClient
|
||||
import org.joda.time.DateTime
|
||||
import java.net.CookieManager
|
||||
import java.net.CookiePolicy
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class DownloadController(
|
||||
applicationContext: Context,
|
||||
private val scope: CoroutineScope,
|
||||
private val mediathekRepository: MediathekRepository
|
||||
) : FetchListener, IDownloadController {
|
||||
|
||||
private lateinit var fetch: Fetch
|
||||
private val connectivityManager: ConnectivityManager =
|
||||
applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
private val settingsRepository: SettingsRepository = SettingsRepository(applicationContext)
|
||||
private val downloadFileInfoManager: DownloadFileInfoManager =
|
||||
DownloadFileInfoManager(applicationContext, settingsRepository)
|
||||
|
||||
init {
|
||||
val cookieManager = CookieManager()
|
||||
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL)
|
||||
|
||||
val client: OkHttpClient = OkHttpClient.Builder()
|
||||
.readTimeout(40, TimeUnit.SECONDS) // fetch default: 20 seconds
|
||||
.connectTimeout(30, TimeUnit.SECONDS) // fetch default: 15 seconds
|
||||
.cache(null)
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.retryOnConnectionFailure(false)
|
||||
.cookieJar(JavaNetCookieJar(cookieManager))
|
||||
.build()
|
||||
|
||||
val fetchConfiguration: FetchConfiguration = FetchConfiguration.Builder(applicationContext)
|
||||
.setNotificationManager(object :
|
||||
ZappNotificationManager(applicationContext, scope, mediathekRepository) {
|
||||
override fun getFetchInstanceForNamespace(namespace: String): Fetch {
|
||||
return fetch
|
||||
}
|
||||
})
|
||||
.enableRetryOnNetworkGain(false)
|
||||
.setAutoRetryMaxAttempts(0)
|
||||
.setDownloadConcurrentLimit(1)
|
||||
.preAllocateFileOnCreation(false) // true causes downloads to sd card to hang
|
||||
.setHttpDownloader(OkHttpDownloader(client, FileDownloaderType.SEQUENTIAL))
|
||||
.enableLogging(true)
|
||||
.build()
|
||||
|
||||
fetch = getInstance(fetchConfiguration)
|
||||
fetch.addListener(this)
|
||||
}
|
||||
|
||||
override suspend fun startDownload(show: PersistedMediathekShow, quality: Quality) {
|
||||
val downloadUrl = show.mediathekShow.getVideoUrl(quality)
|
||||
?: throw DownloadException("$quality is no valid download quality.")
|
||||
|
||||
val download = getDownload(show.downloadId)
|
||||
|
||||
if (download.id != 0 && download.url == downloadUrl) {
|
||||
// same quality as existing download
|
||||
// save new settings to request
|
||||
applySettingsToRequest(download.request)
|
||||
fetch.updateRequest(download.id, download.request, false, null, null)
|
||||
|
||||
// update show properties
|
||||
show.downloadedAt = DateTime.now()
|
||||
show.downloadProgress = 0
|
||||
|
||||
mediathekRepository.updateShow(show)
|
||||
|
||||
// retry
|
||||
fetch.retry(show.downloadId)
|
||||
} else {
|
||||
// delete old file with wrong quality
|
||||
fetch.delete(show.downloadId)
|
||||
|
||||
val filePath =
|
||||
downloadFileInfoManager.getDownloadFilePath(show.mediathekShow, quality)
|
||||
|
||||
val request: Request
|
||||
try {
|
||||
request = Request(downloadUrl, filePath)
|
||||
request.identifier = show.id.toLong()
|
||||
} catch (e: Exception) {
|
||||
throw DownloadException("Constructing download request failed.", e)
|
||||
}
|
||||
|
||||
// update show properties
|
||||
show.downloadId = request.id
|
||||
show.downloadedAt = DateTime.now()
|
||||
show.downloadProgress = 0
|
||||
|
||||
mediathekRepository.updateShow(show)
|
||||
|
||||
enqueueDownload(request)
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopDownload(persistedShowId: Int) {
|
||||
fetch.getDownloadsByRequestIdentifier(persistedShowId.toLong()) { downloadList ->
|
||||
for (download in downloadList) {
|
||||
fetch.cancel(download.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteDownload(persistedShowId: Int) {
|
||||
fetch.getDownloadsByRequestIdentifier(persistedShowId.toLong()) { downloadList ->
|
||||
for (download in downloadList) {
|
||||
fetch.delete(download.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDownloadStatus(persistedShowId: Int): Flow<DownloadStatus> {
|
||||
return mediathekRepository.getDownloadStatus(persistedShowId)
|
||||
}
|
||||
|
||||
override fun getDownloadProgress(persistedShowId: Int): Flow<Int> {
|
||||
return mediathekRepository.getDownloadProgress(persistedShowId)
|
||||
}
|
||||
|
||||
override fun deleteDownloadsWithDeletedFiles() {
|
||||
fetch.getDownloadsWithStatus(Status.COMPLETED) { downloads ->
|
||||
for (download in downloads) {
|
||||
if (downloadFileInfoManager.shouldDeleteDownload(download)) {
|
||||
fetch.remove(download.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return download with the given id or empty download with id of 0
|
||||
*/
|
||||
private suspend fun getDownload(downloadId: Int): Download = suspendCoroutine { continuation ->
|
||||
fetch.getDownload(downloadId) { download ->
|
||||
if (download == null) {
|
||||
continuation.resume(DownloadInfo())
|
||||
} else {
|
||||
continuation.resume(download)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun enqueueDownload(request: Request) {
|
||||
applySettingsToRequest(request)
|
||||
fetch.enqueue(request, null, null)
|
||||
}
|
||||
|
||||
private fun applySettingsToRequest(request: Request) {
|
||||
request.networkType = if (settingsRepository.downloadOverUnmeteredNetworkOnly) {
|
||||
NetworkType.UNMETERED
|
||||
} else {
|
||||
NetworkType.ALL
|
||||
}
|
||||
|
||||
if (connectivityManager.activeNetwork == null) {
|
||||
throw NoNetworkException("No active network available.")
|
||||
}
|
||||
if (settingsRepository.downloadOverUnmeteredNetworkOnly && connectivityManager.isActiveNetworkMetered) {
|
||||
throw WrongNetworkConditionException("Download over metered networks prohibited.")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateDownloadStatus(download: Download) {
|
||||
val downloadStatus = DownloadStatus.values()[download.status.value]
|
||||
mediathekRepository.updateDownloadStatus(download.id, downloadStatus)
|
||||
}
|
||||
|
||||
private suspend fun updateDownloadProgress(download: Download, progress: Int) {
|
||||
mediathekRepository.updateDownloadProgress(download.id, progress)
|
||||
}
|
||||
|
||||
override fun onAdded(download: Download) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelled(download: Download) {
|
||||
scope.launch {
|
||||
fetch.delete(download.id)
|
||||
updateDownloadStatus(download)
|
||||
updateDownloadProgress(download, 0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCompleted(download: Download) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
mediathekRepository.updateDownloadedVideoPath(download.id, download.file)
|
||||
downloadFileInfoManager.updateDownloadFileInMediaCollection(
|
||||
download.fileUri,
|
||||
DownloadStatus.COMPLETED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleted(download: Download) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
updateDownloadProgress(download, 0)
|
||||
downloadFileInfoManager.updateDownloadFileInMediaCollection(
|
||||
download.fileUri,
|
||||
DownloadStatus.DELETED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDownloadBlockUpdated(
|
||||
download: Download,
|
||||
downloadBlock: DownloadBlock,
|
||||
totalBlocks: Int
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onError(download: Download, error: Error, throwable: Throwable?) {
|
||||
scope.launch {
|
||||
downloadFileInfoManager.deleteDownloadFile(download)
|
||||
updateDownloadStatus(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPaused(download: Download) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgress(
|
||||
download: Download,
|
||||
etaInMilliSeconds: Long,
|
||||
downloadedBytesPerSecond: Long
|
||||
) {
|
||||
scope.launch {
|
||||
updateDownloadProgress(download, download.progress)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueued(download: Download, waitingOnNetwork: Boolean) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRemoved(download: Download) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResumed(download: Download) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStarted(
|
||||
download: Download,
|
||||
downloadBlocks: List<DownloadBlock>,
|
||||
totalBlocks: Int
|
||||
) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWaitingNetwork(download: Download) {
|
||||
scope.launch {
|
||||
updateDownloadStatus(download)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,533 +0,0 @@
|
||||
package de.christinecoenen.code.zapp.app.mediathek.controller.downloads.legacy
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Bundle
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import com.tonyodev.fetch2.*
|
||||
import com.tonyodev.fetch2.util.DEFAULT_NOTIFICATION_TIMEOUT_AFTER
|
||||
import com.tonyodev.fetch2.util.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET
|
||||
import com.tonyodev.fetch2.util.onDownloadNotificationActionTriggered
|
||||
import de.christinecoenen.code.zapp.app.ZappApplication
|
||||
import de.christinecoenen.code.zapp.models.shows.PersistedMediathekShow
|
||||
import de.christinecoenen.code.zapp.repositories.MediathekRepository
|
||||
import de.christinecoenen.code.zapp.utils.system.NotificationHelper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
const val ACTION_TYPE_REPORT_ERROR = 42
|
||||
|
||||
abstract class ZappNotificationManager(
|
||||
context: Context,
|
||||
private val scope: CoroutineScope,
|
||||
private val mediathekRepository: MediathekRepository
|
||||
) : FetchNotificationManager {
|
||||
|
||||
private val application: ZappApplication = context.applicationContext as ZappApplication
|
||||
private val context: Context = context.applicationContext
|
||||
private val notificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
private val downloadNotificationsMap = mutableMapOf<Int, DownloadNotification>()
|
||||
private val downloadNotificationsBuilderMap = mutableMapOf<Int, NotificationCompat.Builder>()
|
||||
private val errorMap = mutableMapOf<Int, Error>()
|
||||
private val downloadNotificationExcludeSet = mutableSetOf<Int>()
|
||||
|
||||
override val notificationManagerAction: String =
|
||||
"DEFAULT_FETCH2_NOTIFICATION_MANAGER_ACTION_" + System.currentTimeMillis()
|
||||
|
||||
override val broadcastReceiver: BroadcastReceiver
|
||||
get() = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
// handled by fetch
|
||||
onDownloadNotificationActionTriggered(context, intent, this@ZappNotificationManager)
|
||||
|
||||
// handled by zapp - may be error report intent
|
||||
if (intent != null) {
|
||||
val actionType = intent.getIntExtra(EXTRA_ACTION_TYPE, ACTION_TYPE_INVALID)
|
||||
val notificationId =
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID_INVALID)
|
||||
|
||||
if (actionType == ACTION_TYPE_REPORT_ERROR && notificationId != NOTIFICATION_ID_INVALID) {
|
||||
val error = errorMap[notificationId]
|
||||
application.reportError(error!!.throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
initialize()
|
||||
}
|
||||
|
||||
private fun initialize() {
|
||||
registerBroadcastReceiver()
|
||||
createNotificationChannels(context, notificationManager)
|
||||
}
|
||||
|
||||
override fun registerBroadcastReceiver() {
|
||||
context.registerReceiver(broadcastReceiver, IntentFilter(notificationManagerAction))
|
||||
}
|
||||
|
||||
override fun unregisterBroadcastReceiver() {
|
||||
context.unregisterReceiver(broadcastReceiver)
|
||||
}
|
||||
|
||||
override fun createNotificationChannels(
|
||||
context: Context,
|
||||
notificationManager: NotificationManager
|
||||
) {
|
||||
NotificationHelper.createDownloadEventChannel(context)
|
||||
NotificationHelper.createDownloadProgressChannel(context)
|
||||
}
|
||||
|
||||
override fun getChannelId(notificationId: Int, context: Context): String {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
val notification = downloadNotificationsMap[notificationId]
|
||||
|
||||
return if (notification == null || notification.isDownloading) {
|
||||
NotificationHelper.CHANNEL_ID_DOWNLOAD_PROGRESS
|
||||
} else {
|
||||
NotificationHelper.CHANNEL_ID_DOWNLOAD_EVENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateGroupSummaryNotification(
|
||||
groupId: Int,
|
||||
notificationBuilder: NotificationCompat.Builder,
|
||||
downloadNotifications: List<DownloadNotification>,
|
||||
context: Context
|
||||
): Boolean {
|
||||
val style = NotificationCompat.InboxStyle()
|
||||
|
||||
for (downloadNotification in downloadNotifications) {
|
||||
val contentTitle = getSubtitleText(context, downloadNotification)
|
||||
style.addLine("${downloadNotification.total} $contentTitle")
|
||||
}
|
||||
|
||||
notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentTitle(context.getString(R.string.fetch_notification_default_channel_name))
|
||||
.setContentText("")
|
||||
.setStyle(style)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setGroup(groupId.toString())
|
||||
.setGroupSummary(true)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun updateNotification(
|
||||
notificationBuilder: NotificationCompat.Builder,
|
||||
downloadNotification: DownloadNotification,
|
||||
context: Context
|
||||
) {
|
||||
val smallIcon = if (downloadNotification.isDownloading) {
|
||||
android.R.drawable.stat_sys_download
|
||||
} else {
|
||||
android.R.drawable.stat_sys_download_done
|
||||
}
|
||||
|
||||
notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setSmallIcon(smallIcon)
|
||||
.setContentTitle(downloadNotification.title)
|
||||
.setContentText(getSubtitleText(context, downloadNotification))
|
||||
.setSubText(getSubSubtitleText(downloadNotification))
|
||||
.setOngoing(downloadNotification.isOnGoingNotification)
|
||||
.setGroup(downloadNotification.groupId.toString())
|
||||
.setGroupSummary(false)
|
||||
.setContentIntent(getContentIntent(downloadNotification))
|
||||
|
||||
if (downloadNotification.isFailed || downloadNotification.isCompleted) {
|
||||
notificationBuilder.setProgress(0, 0, false)
|
||||
notificationBuilder.setAutoCancel(true)
|
||||
} else {
|
||||
val progressIndeterminate = downloadNotification.progressIndeterminate
|
||||
val maxProgress = if (downloadNotification.progressIndeterminate) 0 else 100
|
||||
val progress =
|
||||
if (downloadNotification.progress < 0) 0 else downloadNotification.progress
|
||||
notificationBuilder.setProgress(maxProgress, progress, progressIndeterminate)
|
||||
notificationBuilder.setAutoCancel(false)
|
||||
}
|
||||
|
||||
when {
|
||||
downloadNotification.isDownloading -> {
|
||||
notificationBuilder.setTimeoutAfter(getNotificationTimeOutMillis())
|
||||
.addAction(
|
||||
R.drawable.fetch_notification_cancel,
|
||||
context.getString(R.string.fetch_notification_download_cancel),
|
||||
getActionPendingIntent(
|
||||
downloadNotification,
|
||||
DownloadNotification.ActionType.CANCEL
|
||||
)
|
||||
)
|
||||
}
|
||||
downloadNotification.isPaused -> {
|
||||
notificationBuilder.setTimeoutAfter(getNotificationTimeOutMillis())
|
||||
.addAction(
|
||||
R.drawable.fetch_notification_resume,
|
||||
context.getString(R.string.fetch_notification_download_resume),
|
||||
getActionPendingIntent(
|
||||
downloadNotification,
|
||||
DownloadNotification.ActionType.RESUME
|
||||
)
|
||||
)
|
||||
.addAction(
|
||||
R.drawable.fetch_notification_cancel,
|
||||
context.getString(R.string.fetch_notification_download_cancel),
|
||||
getActionPendingIntent(
|
||||
downloadNotification,
|
||||
DownloadNotification.ActionType.CANCEL
|
||||
)
|
||||
)
|
||||
}
|
||||
downloadNotification.isQueued -> {
|
||||
notificationBuilder.setTimeoutAfter(getNotificationTimeOutMillis())
|
||||
}
|
||||
downloadNotification.isFailed -> {
|
||||
notificationBuilder.setTimeoutAfter(getNotificationTimeOutMillis())
|
||||
.addAction(
|
||||
R.drawable.fetch_notification_cancel,
|
||||
context.getString(de.christinecoenen.code.zapp.R.string.error_report),
|
||||
getReportPendingIntent(downloadNotification)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getActionPendingIntent(
|
||||
downloadNotification: DownloadNotification,
|
||||
actionType: DownloadNotification.ActionType
|
||||
): PendingIntent {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
val intent = Intent(notificationManagerAction)
|
||||
intent.putExtra(EXTRA_NAMESPACE, downloadNotification.namespace)
|
||||
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadNotification.notificationId)
|
||||
intent.putExtra(EXTRA_NOTIFICATION_ID, downloadNotification.notificationId)
|
||||
intent.putExtra(EXTRA_GROUP_ACTION, false)
|
||||
intent.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)
|
||||
return PendingIntent.getBroadcast(
|
||||
context,
|
||||
downloadNotification.notificationId + action,
|
||||
intent,
|
||||
getPendingIntentFlags()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getGroupActionPendingIntent(
|
||||
groupId: Int,
|
||||
downloadNotifications: List<DownloadNotification>,
|
||||
actionType: DownloadNotification.ActionType
|
||||
): PendingIntent {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
val intent = Intent(notificationManagerAction)
|
||||
intent.putExtra(EXTRA_NOTIFICATION_GROUP_ID, groupId)
|
||||
intent.putExtra(EXTRA_DOWNLOAD_NOTIFICATIONS, ArrayList(downloadNotifications))
|
||||
intent.putExtra(EXTRA_GROUP_ACTION, true)
|
||||
|
||||
val action = when (actionType) {
|
||||
DownloadNotification.ActionType.CANCEL_ALL -> ACTION_TYPE_CANCEL_ALL
|
||||
DownloadNotification.ActionType.DELETE_ALL -> ACTION_TYPE_DELETE_ALL
|
||||
DownloadNotification.ActionType.RESUME_ALL -> ACTION_TYPE_RESUME_ALL
|
||||
DownloadNotification.ActionType.PAUSE_ALL -> ACTION_TYPE_PAUSE_ALL
|
||||
DownloadNotification.ActionType.RETRY_ALL -> ACTION_TYPE_RETRY_ALL
|
||||
else -> ACTION_TYPE_INVALID
|
||||
}
|
||||
|
||||
intent.putExtra(EXTRA_ACTION_TYPE, action)
|
||||
return PendingIntent.getBroadcast(
|
||||
context,
|
||||
groupId + action,
|
||||
intent,
|
||||
getPendingIntentFlags()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelNotification(notificationId: Int) {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
notificationManager.cancel(notificationId)
|
||||
downloadNotificationsBuilderMap.remove(notificationId)
|
||||
downloadNotificationExcludeSet.remove(notificationId)
|
||||
|
||||
val downloadNotification = downloadNotificationsMap[notificationId]
|
||||
if (downloadNotification != null) {
|
||||
downloadNotificationsMap.remove(notificationId)
|
||||
notify(downloadNotification.groupId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelOngoingNotifications() {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
val iterator = downloadNotificationsMap.values.iterator()
|
||||
var downloadNotification: DownloadNotification
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
downloadNotification = iterator.next()
|
||||
if (!downloadNotification.isFailed && !downloadNotification.isCompleted) {
|
||||
notificationManager.cancel(downloadNotification.notificationId)
|
||||
downloadNotificationsBuilderMap.remove(downloadNotification.notificationId)
|
||||
downloadNotificationExcludeSet.remove(downloadNotification.notificationId)
|
||||
iterator.remove()
|
||||
notify(downloadNotification.groupId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun notify(groupId: Int) {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
val groupedDownloadNotifications =
|
||||
downloadNotificationsMap.values.filter { it.groupId == groupId }
|
||||
val groupSummaryNotificationBuilder = getNotificationBuilder(groupId, groupId)
|
||||
val useGroupNotification = updateGroupSummaryNotification(
|
||||
groupId,
|
||||
groupSummaryNotificationBuilder,
|
||||
groupedDownloadNotifications,
|
||||
context
|
||||
)
|
||||
var notificationId: Int
|
||||
var notificationBuilder: NotificationCompat.Builder
|
||||
|
||||
for (downloadNotification in groupedDownloadNotifications) {
|
||||
if (shouldUpdateNotification(downloadNotification)) {
|
||||
notificationId = downloadNotification.notificationId
|
||||
notificationBuilder = getNotificationBuilder(notificationId, groupId)
|
||||
updateNotification(notificationBuilder, downloadNotification, context)
|
||||
notificationManager.notify(notificationId, notificationBuilder.build())
|
||||
when (downloadNotification.status) {
|
||||
Status.COMPLETED,
|
||||
Status.FAILED -> {
|
||||
downloadNotificationExcludeSet.add(downloadNotification.notificationId)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useGroupNotification) {
|
||||
notificationManager.notify(groupId, groupSummaryNotificationBuilder.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldUpdateNotification(downloadNotification: DownloadNotification): Boolean {
|
||||
return !downloadNotificationExcludeSet.contains(downloadNotification.notificationId)
|
||||
}
|
||||
|
||||
override fun shouldCancelNotification(downloadNotification: DownloadNotification): Boolean {
|
||||
return downloadNotification.isPaused
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
override fun postDownloadUpdate(download: Download): Boolean {
|
||||
|
||||
scope.launch {
|
||||
val persistedShow = mediathekRepository
|
||||
.getPersistedShowByDownloadId(download.id)
|
||||
.first()
|
||||
|
||||
postDownloadUpdate(download, persistedShow)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun getNotificationBuilder(
|
||||
notificationId: Int,
|
||||
groupId: Int
|
||||
): NotificationCompat.Builder {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
val notificationBuilder = downloadNotificationsBuilderMap[notificationId]
|
||||
?: NotificationCompat.Builder(context, getChannelId(notificationId, context))
|
||||
downloadNotificationsBuilderMap[notificationId] = notificationBuilder
|
||||
|
||||
notificationBuilder
|
||||
.setChannelId(getChannelId(notificationId, context))
|
||||
.setGroup(notificationId.toString())
|
||||
.setStyle(null)
|
||||
.setProgress(0, 0, false)
|
||||
.setContentTitle(null)
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setContentIntent(null)
|
||||
.setGroupSummary(false)
|
||||
.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET)
|
||||
.setOngoing(false)
|
||||
.setGroup(groupId.toString())
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setAutoCancel(false)
|
||||
.mActions.clear()
|
||||
|
||||
return notificationBuilder
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDownloadNotificationTitle(download: Download): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun getNotificationTimeOutMillis(): Long {
|
||||
return DEFAULT_NOTIFICATION_TIMEOUT_AFTER
|
||||
}
|
||||
|
||||
abstract override fun getFetchInstanceForNamespace(namespace: String): Fetch
|
||||
|
||||
override fun getSubtitleText(
|
||||
context: Context,
|
||||
downloadNotification: DownloadNotification
|
||||
): String {
|
||||
return when {
|
||||
downloadNotification.isCompleted -> context.getString(R.string.fetch_notification_download_complete)
|
||||
downloadNotification.isFailed -> context.getString(R.string.fetch_notification_download_failed)
|
||||
downloadNotification.isPaused -> context.getString(R.string.fetch_notification_download_paused)
|
||||
downloadNotification.isQueued -> context.getString(R.string.fetch_notification_download_starting)
|
||||
downloadNotification.etaInMilliSeconds < 0 -> context.getString(R.string.fetch_notification_download_downloading)
|
||||
else -> getEtaText(context, downloadNotification.etaInMilliSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getReportPendingIntent(downloadNotification: DownloadNotification): PendingIntent {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
val intent = Intent(notificationManagerAction)
|
||||
intent.putExtra(EXTRA_NAMESPACE, downloadNotification.namespace)
|
||||
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadNotification.notificationId)
|
||||
intent.putExtra(EXTRA_NOTIFICATION_ID, downloadNotification.notificationId)
|
||||
intent.putExtra(EXTRA_GROUP_ACTION, false)
|
||||
intent.putExtra(EXTRA_NOTIFICATION_GROUP_ID, downloadNotification.groupId)
|
||||
|
||||
val action = ACTION_TYPE_REPORT_ERROR
|
||||
|
||||
intent.putExtra(EXTRA_ACTION_TYPE, action)
|
||||
return PendingIntent.getBroadcast(
|
||||
context,
|
||||
downloadNotification.notificationId + action,
|
||||
intent,
|
||||
getPendingIntentFlags()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSubSubtitleText(downloadNotification: DownloadNotification): String? {
|
||||
return when {
|
||||
downloadNotification.isFailed -> errorMap[downloadNotification.notificationId]?.toString()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun postDownloadUpdate(download: Download, persistedShow: PersistedMediathekShow) {
|
||||
return synchronized(downloadNotificationsMap) {
|
||||
if (downloadNotificationsMap.size > 50) {
|
||||
downloadNotificationsBuilderMap.clear()
|
||||
downloadNotificationsMap.clear()
|
||||
}
|
||||
|
||||
val downloadNotification = downloadNotificationsMap[download.id]
|
||||
?: DownloadNotification()
|
||||
downloadNotification.status = download.status
|
||||
downloadNotification.progress = download.progress
|
||||
downloadNotification.notificationId = download.id
|
||||
downloadNotification.groupId = download.group
|
||||
downloadNotification.etaInMilliSeconds = download.etaInMilliSeconds
|
||||
downloadNotification.downloadedBytesPerSecond = download.downloadedBytesPerSecond
|
||||
downloadNotification.total = download.total
|
||||
downloadNotification.downloaded = download.downloaded
|
||||
downloadNotification.namespace = download.namespace
|
||||
downloadNotification.title = persistedShow.mediathekShow.title
|
||||
downloadNotificationsMap[download.id] = downloadNotification
|
||||
|
||||
if (download.error != Error.NONE) {
|
||||
errorMap[downloadNotification.notificationId] = download.error
|
||||
}
|
||||
|
||||
if (downloadNotificationExcludeSet.contains(downloadNotification.notificationId)
|
||||
&& !downloadNotification.isFailed && !downloadNotification.isCompleted
|
||||
) {
|
||||
downloadNotificationExcludeSet.remove(downloadNotification.notificationId)
|
||||
}
|
||||
|
||||
if (downloadNotification.isCancelledNotification || shouldCancelNotification(
|
||||
downloadNotification
|
||||
)
|
||||
) {
|
||||
cancelNotification(downloadNotification.notificationId)
|
||||
} else {
|
||||
notify(download.group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getContentIntent(downloadNotification: DownloadNotification): PendingIntent {
|
||||
synchronized(downloadNotificationsMap) {
|
||||
return runBlocking {
|
||||
val persistedShow = mediathekRepository
|
||||
.getPersistedShowByDownloadId(downloadNotification.notificationId)
|
||||
.first()
|
||||
|
||||
return@runBlocking NavDeepLinkBuilder(context)
|
||||
.setGraph(de.christinecoenen.code.zapp.R.navigation.nav_graph)
|
||||
.setDestination(de.christinecoenen.code.zapp.R.id.mediathekDetailFragment)
|
||||
.setArguments(Bundle().apply {
|
||||
putSerializable("mediathek_show", persistedShow.mediathekShow)
|
||||
})
|
||||
.createPendingIntent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEtaText(context: Context, etaInMilliSeconds: Long): String {
|
||||
var seconds = (etaInMilliSeconds / 1000)
|
||||
val hours = (seconds / 3600)
|
||||
seconds -= (hours * 3600)
|
||||
val minutes = (seconds / 60)
|
||||
seconds -= (minutes * 60)
|
||||
|
||||
return when {
|
||||
hours > 0 -> context.getString(
|
||||
R.string.fetch_notification_download_eta_hrs,
|
||||
hours,
|
||||
minutes,
|
||||
seconds
|
||||
)
|
||||
minutes > 0 -> context.getString(
|
||||
R.string.fetch_notification_download_eta_min,
|
||||
minutes,
|
||||
seconds
|
||||
)
|
||||
else -> context.getString(R.string.fetch_notification_download_eta_sec, seconds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPendingIntentFlags(): Int {
|
||||
return PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
}
|
||||
}
|
@ -137,17 +137,4 @@
|
||||
<string name="notification_download_downloading">Downloading</string>
|
||||
<string name="notification_download_complete">Download complete</string>
|
||||
<string name="notification_download_failed">Download failed</string>
|
||||
|
||||
<string name="fetch_notification_download_eta_sec">%1$ds left</string>
|
||||
<string name="fetch_notification_download_eta_min">%1$dm %2$ds left</string>
|
||||
<string name="fetch_notification_download_eta_hrs">%1$dh %2$dm %3$ds left</string>
|
||||
<string name="fetch_notification_download_complete">Complete</string>
|
||||
<string name="fetch_notification_download_failed">Failed</string>
|
||||
<string name="fetch_notification_download_pause">Pause</string>
|
||||
<string name="fetch_notification_download_resume">Resume</string>
|
||||
<string name="fetch_notification_download_retry">Retry</string>
|
||||
<string name="fetch_notification_download_cancel">Cancel</string>
|
||||
<string name="fetch_notification_download_paused">Paused</string>
|
||||
<string name="fetch_notification_download_starting">Starting</string>
|
||||
<string name="fetch_notification_download_downloading">Downloading</string>
|
||||
</resources>
|
||||
|
@ -137,17 +137,4 @@
|
||||
<string name="notification_download_downloading">Lädt herunter</string>
|
||||
<string name="notification_download_complete">Download abgeschlossen</string>
|
||||
<string name="notification_download_failed">Download fehlgeschlagen</string>
|
||||
|
||||
<string name="fetch_notification_download_eta_sec">%1$ds verbleibend</string>
|
||||
<string name="fetch_notification_download_eta_min">%1$dm %2$ds verbleibend</string>
|
||||
<string name="fetch_notification_download_eta_hrs">%1$dh %2$dm %3$ds verbleibend</string>
|
||||
<string name="fetch_notification_download_complete">Fertig</string>
|
||||
<string name="fetch_notification_download_failed">Fehlgeschlagen</string>
|
||||
<string name="fetch_notification_download_pause">Pause</string>
|
||||
<string name="fetch_notification_download_resume">Fortsetzen</string>
|
||||
<string name="fetch_notification_download_retry">Wiederholen</string>
|
||||
<string name="fetch_notification_download_cancel">Abbrechen</string>
|
||||
<string name="fetch_notification_download_paused">Pausiert</string>
|
||||
<string name="fetch_notification_download_starting">Startet</string>
|
||||
<string name="fetch_notification_download_downloading">Lädt herunter</string>
|
||||
</resources>
|
||||
|
@ -33,9 +33,6 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
||||
// TODO: remove once androidx.tonyodev.fetch2 has migrated
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user