0
0
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:
Christine Coenen 2022-10-28 20:26:36 +02:00
parent 72b3bfa6c9
commit 922b80934c
8 changed files with 1 additions and 896 deletions

View File

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

View File

@ -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

View File

@ -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

View File

@ -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)
}
}
}

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -33,9 +33,6 @@ allprojects {
repositories {
google()
mavenCentral()
// TODO: remove once androidx.tonyodev.fetch2 has migrated
jcenter()
}
}