2022-06-28 20:02:26 +05:30
|
|
|
package com.github.libretube.services
|
2022-03-03 12:08:36 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
import android.app.NotificationManager
|
|
|
|
import android.app.PendingIntent
|
2022-05-21 13:32:04 +05:30
|
|
|
import android.app.Service
|
2022-03-03 12:08:36 +05:30
|
|
|
import android.content.Intent
|
2022-12-16 22:14:21 +05:30
|
|
|
import android.os.Binder
|
|
|
|
import android.os.Build
|
2022-03-03 12:08:36 +05:30
|
|
|
import android.os.IBinder
|
|
|
|
import androidx.core.app.NotificationCompat
|
2022-09-08 22:12:52 +05:30
|
|
|
import com.github.libretube.R
|
2022-12-24 23:39:56 +05:30
|
|
|
import com.github.libretube.api.CronetHelper
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.api.RetrofitInstance
|
2023-01-14 22:47:34 +05:30
|
|
|
import com.github.libretube.compat.PendingIntentCompat
|
2022-09-08 22:11:57 +05:30
|
|
|
import com.github.libretube.constants.DOWNLOAD_CHANNEL_ID
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.constants.DOWNLOAD_PROGRESS_NOTIFICATION_ID
|
|
|
|
import com.github.libretube.constants.IntentData
|
2022-12-21 21:10:48 +05:30
|
|
|
import com.github.libretube.db.DatabaseHolder.Companion.Database
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.db.obj.Download
|
|
|
|
import com.github.libretube.db.obj.DownloadItem
|
|
|
|
import com.github.libretube.enums.FileType
|
|
|
|
import com.github.libretube.extensions.awaitQuery
|
2022-12-17 15:56:57 +05:30
|
|
|
import com.github.libretube.extensions.formatAsFileSize
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.extensions.getContentLength
|
|
|
|
import com.github.libretube.extensions.query
|
|
|
|
import com.github.libretube.extensions.toDownloadItems
|
2022-12-17 15:56:57 +05:30
|
|
|
import com.github.libretube.extensions.toastFromMainThread
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.obj.DownloadStatus
|
|
|
|
import com.github.libretube.receivers.NotificationReceiver
|
2022-12-21 21:10:48 +05:30
|
|
|
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_PAUSE
|
|
|
|
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_RESUME
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.ui.activities.MainActivity
|
2022-09-10 15:21:50 +05:30
|
|
|
import com.github.libretube.util.DownloadHelper
|
2022-12-21 21:18:30 +05:30
|
|
|
import com.github.libretube.util.DownloadHelper.getNotificationId
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.util.ImageHelper
|
2022-12-25 18:36:23 +05:30
|
|
|
import java.io.File
|
|
|
|
import java.net.HttpURLConnection
|
|
|
|
import java.net.SocketTimeoutException
|
|
|
|
import java.net.URL
|
|
|
|
import java.util.concurrent.Executors
|
2022-12-21 21:10:48 +05:30
|
|
|
import kotlinx.coroutines.CancellationException
|
2022-12-16 22:14:21 +05:30
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.SupervisorJob
|
2022-12-24 23:39:56 +05:30
|
|
|
import kotlinx.coroutines.asCoroutineDispatcher
|
2022-12-16 22:14:21 +05:30
|
|
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
|
|
import kotlinx.coroutines.flow.SharedFlow
|
|
|
|
import kotlinx.coroutines.launch
|
2022-12-24 23:39:56 +05:30
|
|
|
import kotlinx.coroutines.withContext
|
2022-12-16 22:14:21 +05:30
|
|
|
import okio.BufferedSink
|
|
|
|
import okio.buffer
|
|
|
|
import okio.sink
|
2022-12-21 21:10:48 +05:30
|
|
|
import okio.source
|
2022-03-04 23:30:50 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
/**
|
2022-12-21 21:10:48 +05:30
|
|
|
* Download service with custom implementation of downloading using [HttpURLConnection].
|
2022-12-16 22:14:21 +05:30
|
|
|
*/
|
2022-05-20 03:52:10 +05:30
|
|
|
class DownloadService : Service() {
|
2022-07-12 01:30:56 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private val binder = LocalBinder()
|
2022-12-24 23:39:56 +05:30
|
|
|
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
2022-12-16 22:14:21 +05:30
|
|
|
private val jobMain = SupervisorJob()
|
2022-12-24 23:39:56 +05:30
|
|
|
private val scope = CoroutineScope(dispatcher + jobMain)
|
2022-10-23 23:12:33 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private lateinit var notificationManager: NotificationManager
|
|
|
|
private lateinit var summaryNotificationBuilder: NotificationCompat.Builder
|
|
|
|
|
|
|
|
private val jobs = mutableMapOf<Int, Job>()
|
2022-12-21 21:10:48 +05:30
|
|
|
private val downloadQueue = mutableMapOf<Int, Boolean>()
|
2022-12-16 22:14:21 +05:30
|
|
|
private val _downloadFlow = MutableSharedFlow<Pair<Int, DownloadStatus>>()
|
|
|
|
val downloadFlow: SharedFlow<Pair<Int, DownloadStatus>> = _downloadFlow
|
2022-09-09 16:41:54 +05:30
|
|
|
|
2022-03-04 21:27:10 +05:30
|
|
|
override fun onCreate() {
|
|
|
|
super.onCreate()
|
2022-09-19 23:37:55 +05:30
|
|
|
IS_DOWNLOAD_RUNNING = true
|
2022-12-16 22:14:21 +05:30
|
|
|
notifyForeground()
|
2022-12-21 21:10:48 +05:30
|
|
|
sendBroadcast(Intent(ACTION_SERVICE_STARTED))
|
2022-03-04 21:27:10 +05:30
|
|
|
}
|
|
|
|
|
2022-03-03 12:08:36 +05:30
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
2022-12-16 22:14:21 +05:30
|
|
|
when (intent?.action) {
|
2022-12-21 21:10:48 +05:30
|
|
|
ACTION_DOWNLOAD_RESUME -> resume(intent.getIntExtra("id", -1))
|
|
|
|
ACTION_DOWNLOAD_PAUSE -> pause(intent.getIntExtra("id", -1))
|
2022-08-24 21:26:57 +05:30
|
|
|
}
|
2022-09-09 21:09:49 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
val videoId = intent?.getStringExtra(IntentData.videoId) ?: return START_NOT_STICKY
|
|
|
|
val fileName = intent.getStringExtra(IntentData.fileName) ?: videoId
|
|
|
|
val videoFormat = intent.getStringExtra(IntentData.videoFormat)
|
|
|
|
val videoQuality = intent.getStringExtra(IntentData.videoQuality)
|
|
|
|
val audioFormat = intent.getStringExtra(IntentData.audioFormat)
|
|
|
|
val audioQuality = intent.getStringExtra(IntentData.audioQuality)
|
|
|
|
val subtitleCode = intent.getStringExtra(IntentData.subtitleCode)
|
|
|
|
|
|
|
|
scope.launch {
|
|
|
|
try {
|
|
|
|
val streams = RetrofitInstance.api.getStreams(videoId)
|
|
|
|
|
2023-01-14 22:47:34 +05:30
|
|
|
val thumbnailTargetFile = getDownloadFile(DownloadHelper.THUMBNAIL_DIR, fileName)
|
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
awaitQuery {
|
2022-12-21 21:10:48 +05:30
|
|
|
Database.downloadDao().insertDownload(
|
2022-12-16 22:14:21 +05:30
|
|
|
Download(
|
|
|
|
videoId = videoId,
|
|
|
|
title = streams.title ?: "",
|
2023-01-14 22:47:34 +05:30
|
|
|
thumbnailPath = thumbnailTargetFile.absolutePath,
|
2022-12-16 22:14:21 +05:30
|
|
|
description = streams.description ?: "",
|
2022-12-21 21:38:07 +05:30
|
|
|
uploadDate = streams.uploadDate,
|
|
|
|
uploader = streams.uploader ?: ""
|
2022-12-16 22:14:21 +05:30
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
streams.thumbnailUrl?.let { url ->
|
2022-12-21 21:10:48 +05:30
|
|
|
ImageHelper.downloadImage(
|
|
|
|
this@DownloadService,
|
|
|
|
url,
|
2023-01-14 22:47:34 +05:30
|
|
|
thumbnailTargetFile.absolutePath
|
2022-12-21 21:10:48 +05:30
|
|
|
)
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
val downloadItems = streams.toDownloadItems(
|
|
|
|
videoId,
|
|
|
|
fileName,
|
|
|
|
videoFormat,
|
|
|
|
videoQuality,
|
|
|
|
audioFormat,
|
|
|
|
audioQuality,
|
|
|
|
subtitleCode
|
|
|
|
)
|
|
|
|
downloadItems.forEach { start(it) }
|
|
|
|
} catch (e: Exception) {
|
|
|
|
return@launch
|
|
|
|
}
|
2022-06-05 23:10:16 +05:30
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
return START_NOT_STICKY
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
2022-05-21 13:32:04 +05:30
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
|
|
|
* Initiate download [Job] using [DownloadItem] by creating file according to [FileType]
|
|
|
|
* for the requested file.
|
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
private fun start(item: DownloadItem) {
|
|
|
|
val file: File = when (item.type) {
|
2023-01-14 22:47:34 +05:30
|
|
|
FileType.AUDIO -> getDownloadFile(DownloadHelper.AUDIO_DIR, item.fileName)
|
|
|
|
FileType.VIDEO -> getDownloadFile(DownloadHelper.VIDEO_DIR, item.fileName)
|
|
|
|
FileType.SUBTITLE -> getDownloadFile(DownloadHelper.SUBTITLE_DIR, item.fileName)
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
|
|
|
file.createNewFile()
|
|
|
|
item.path = file.absolutePath
|
|
|
|
|
|
|
|
item.id = awaitQuery {
|
2022-12-21 21:10:48 +05:30
|
|
|
Database.downloadDao().insertDownloadItem(item)
|
2022-12-16 22:14:21 +05:30
|
|
|
}.toInt()
|
|
|
|
|
|
|
|
jobs[item.id] = scope.launch {
|
|
|
|
downloadFile(item)
|
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
|
|
|
* Download file and emit [DownloadStatus] to the collectors of [downloadFlow]
|
|
|
|
* and notification.
|
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
private suspend fun downloadFile(item: DownloadItem) {
|
2022-12-21 21:10:48 +05:30
|
|
|
downloadQueue[item.id] = true
|
2022-12-17 15:56:57 +05:30
|
|
|
val notificationBuilder = getNotificationBuilder(item)
|
|
|
|
setResumeNotification(notificationBuilder, item)
|
2022-12-16 22:14:21 +05:30
|
|
|
val file = File(item.path)
|
|
|
|
var totalRead = file.length()
|
|
|
|
val url = URL(item.url ?: return)
|
|
|
|
|
|
|
|
url.getContentLength().let { size ->
|
|
|
|
if (size > 0 && size != item.downloadSize) {
|
|
|
|
item.downloadSize = size
|
|
|
|
query {
|
2022-12-21 21:10:48 +05:30
|
|
|
Database.downloadDao().updateDownloadItem(item)
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
2022-05-21 13:32:04 +05:30
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
try {
|
2022-12-21 21:10:48 +05:30
|
|
|
// Set start range where last downloading was held.
|
2022-12-24 23:39:56 +05:30
|
|
|
val con = CronetHelper.getCronetEngine().openConnection(url) as HttpURLConnection
|
2022-12-21 21:10:48 +05:30
|
|
|
con.requestMethod = "GET"
|
|
|
|
con.setRequestProperty("Range", "bytes=$totalRead-")
|
|
|
|
con.connectTimeout = DownloadHelper.DEFAULT_TIMEOUT
|
|
|
|
con.readTimeout = DownloadHelper.DEFAULT_TIMEOUT
|
2022-12-25 10:56:26 +05:30
|
|
|
|
2022-12-24 23:39:56 +05:30
|
|
|
withContext(Dispatchers.IO) {
|
2022-12-25 10:56:26 +05:30
|
|
|
// Retry connecting to server for n times.
|
|
|
|
for (i in 1..DownloadHelper.DEFAULT_RETRY) {
|
|
|
|
try {
|
|
|
|
con.connect()
|
|
|
|
break
|
|
|
|
} catch (_: SocketTimeoutException) {
|
|
|
|
val message = getString(R.string.downloadfailed) + " " + i
|
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Error(message))
|
|
|
|
toastFromMainThread(message)
|
|
|
|
}
|
|
|
|
}
|
2022-12-24 23:39:56 +05:30
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
|
2022-12-25 10:16:53 +05:30
|
|
|
// If link is expired try to regenerate using available info.
|
|
|
|
if (con.responseCode == 403) {
|
|
|
|
regenerateLink(item)
|
|
|
|
con.disconnect()
|
|
|
|
downloadFile(item)
|
|
|
|
return
|
|
|
|
} else if (con.responseCode !in 200..299) {
|
2022-12-21 21:10:48 +05:30
|
|
|
val message = getString(R.string.downloadfailed) + ": " + con.responseMessage
|
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Error(message))
|
|
|
|
toastFromMainThread(message)
|
2022-12-25 10:16:53 +05:30
|
|
|
con.disconnect()
|
|
|
|
pause(item.id)
|
|
|
|
return
|
2022-12-21 21:10:48 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
val sink: BufferedSink = file.sink(true).buffer()
|
|
|
|
val sourceByte = con.inputStream.source()
|
|
|
|
|
|
|
|
var lastTime = System.currentTimeMillis() / 1000
|
|
|
|
var lastRead: Long = 0
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Check if downloading is still active and read next bytes.
|
|
|
|
while (downloadQueue[item.id] == true &&
|
|
|
|
sourceByte
|
|
|
|
.read(sink.buffer, DownloadHelper.DOWNLOAD_CHUNK_SIZE)
|
|
|
|
.also { lastRead = it } != -1L
|
2022-12-17 15:56:57 +05:30
|
|
|
) {
|
2022-12-21 21:10:48 +05:30
|
|
|
sink.emit()
|
|
|
|
totalRead += lastRead
|
|
|
|
_downloadFlow.emit(
|
|
|
|
item.id to DownloadStatus.Progress(
|
|
|
|
lastRead,
|
|
|
|
totalRead,
|
|
|
|
item.downloadSize
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if (item.downloadSize != -1L &&
|
|
|
|
System.currentTimeMillis() / 1000 > lastTime
|
|
|
|
) {
|
|
|
|
notificationBuilder
|
2022-12-25 18:36:23 +05:30
|
|
|
.setContentText(
|
|
|
|
totalRead.formatAsFileSize() + " / " +
|
|
|
|
item.downloadSize.formatAsFileSize()
|
|
|
|
)
|
|
|
|
.setProgress(
|
|
|
|
item.downloadSize.toInt(),
|
|
|
|
totalRead.toInt(),
|
|
|
|
false
|
|
|
|
)
|
|
|
|
notificationManager.notify(
|
|
|
|
item.getNotificationId(),
|
|
|
|
notificationBuilder.build()
|
|
|
|
)
|
2022-12-21 21:10:48 +05:30
|
|
|
lastTime = System.currentTimeMillis() / 1000
|
|
|
|
}
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
} catch (_: CancellationException) {
|
|
|
|
} catch (e: Exception) {
|
|
|
|
toastFromMainThread("${getString(R.string.download)}: ${e.message}")
|
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Error(e.message.toString(), e))
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
|
2022-12-24 23:39:56 +05:30
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
sink.flush()
|
|
|
|
sink.close()
|
|
|
|
sourceByte.close()
|
|
|
|
con.disconnect()
|
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
} catch (_: Exception) { }
|
2022-09-09 21:09:49 +05:30
|
|
|
|
2022-12-25 14:37:01 +05:30
|
|
|
val completed = when {
|
|
|
|
totalRead < item.downloadSize -> {
|
2022-12-17 15:56:57 +05:30
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Paused)
|
|
|
|
false
|
|
|
|
}
|
2022-12-25 14:37:01 +05:30
|
|
|
else -> {
|
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Completed)
|
|
|
|
true
|
|
|
|
}
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
2022-12-17 15:56:57 +05:30
|
|
|
setPauseNotification(notificationBuilder, item, completed)
|
2022-12-21 21:10:48 +05:30
|
|
|
pause(item.id)
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
2022-09-09 16:41:54 +05:30
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
|
|
|
* Resume download which may have been paused.
|
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
fun resume(id: Int) {
|
2022-12-21 21:10:48 +05:30
|
|
|
// If file is already downloading then avoid new download job.
|
|
|
|
if (downloadQueue[id] == true) return
|
|
|
|
|
2022-12-25 15:33:28 +05:30
|
|
|
if (downloadQueue.values.count { it } >= DownloadHelper.getMaxConcurrentDownloads()) {
|
|
|
|
toastFromMainThread(getString(R.string.concurrent_downloads_limit_reached))
|
|
|
|
scope.launch {
|
|
|
|
_downloadFlow.emit(id to DownloadStatus.Paused)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
val downloadItem = awaitQuery {
|
2022-12-21 21:10:48 +05:30
|
|
|
Database.downloadDao().findDownloadItemById(id)
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
scope.launch {
|
2022-12-16 22:14:21 +05:30
|
|
|
downloadFile(downloadItem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
2022-12-21 21:10:48 +05:30
|
|
|
* Pause downloading job for given [id]. If no downloads are active, stop the service.
|
2022-12-17 15:56:57 +05:30
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
fun pause(id: Int) {
|
2022-12-21 21:10:48 +05:30
|
|
|
downloadQueue[id] = false
|
2022-12-17 15:56:57 +05:30
|
|
|
|
|
|
|
// Stop the service if no downloads are active.
|
2022-12-21 21:10:48 +05:30
|
|
|
if (downloadQueue.none { it.value }) {
|
2022-12-17 15:56:57 +05:30
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
|
|
stopForeground(STOP_FOREGROUND_DETACH)
|
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
sendBroadcast(Intent(ACTION_SERVICE_STOPPED))
|
2022-12-17 15:56:57 +05:30
|
|
|
stopSelf()
|
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
|
|
|
|
2022-12-25 10:16:53 +05:30
|
|
|
/**
|
|
|
|
* Regenerate stream url using available info format and quality.
|
|
|
|
*/
|
|
|
|
private suspend fun regenerateLink(item: DownloadItem) {
|
|
|
|
val streams = RetrofitInstance.api.getStreams(item.videoId)
|
|
|
|
val stream = when (item.type) {
|
|
|
|
FileType.AUDIO -> streams.audioStreams
|
|
|
|
FileType.VIDEO -> streams.videoStreams
|
|
|
|
else -> null
|
|
|
|
}
|
|
|
|
stream?.find { it.format == item.format && it.quality == item.quality }?.let {
|
|
|
|
item.url = it.url
|
|
|
|
}
|
|
|
|
query {
|
|
|
|
Database.downloadDao().updateDownloadItem(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
2022-12-21 21:10:48 +05:30
|
|
|
* Check whether the file downloading or not.
|
2022-12-17 15:56:57 +05:30
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
fun isDownloading(id: Int): Boolean {
|
2022-12-21 21:10:48 +05:30
|
|
|
return downloadQueue[id] ?: false
|
2022-06-05 21:38:47 +05:30
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private fun notifyForeground() {
|
|
|
|
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
2022-09-09 21:09:49 +05:30
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
summaryNotificationBuilder = NotificationCompat
|
|
|
|
.Builder(this, DOWNLOAD_CHANNEL_ID)
|
2023-01-14 22:47:34 +05:30
|
|
|
.setSmallIcon(R.drawable.ic_launcher_lockscreen)
|
2022-12-17 15:56:57 +05:30
|
|
|
.setContentTitle(getString(R.string.downloading))
|
|
|
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
|
|
|
.setGroup(DOWNLOAD_NOTIFICATION_GROUP)
|
|
|
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
|
|
.setOnlyAlertOnce(true)
|
|
|
|
.setGroupSummary(true)
|
|
|
|
|
|
|
|
startForeground(DOWNLOAD_PROGRESS_NOTIFICATION_ID, summaryNotificationBuilder.build())
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getNotificationBuilder(item: DownloadItem): NotificationCompat.Builder {
|
2023-01-18 09:00:27 +05:30
|
|
|
val activityIntent = PendingIntentCompat.getActivity(
|
|
|
|
this@DownloadService,
|
|
|
|
0,
|
|
|
|
Intent(this@DownloadService, MainActivity::class.java)
|
|
|
|
.putExtra("fragmentToOpen", "downloads"),
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT
|
|
|
|
)
|
2022-12-16 22:14:21 +05:30
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
return NotificationCompat
|
2022-12-16 22:14:21 +05:30
|
|
|
.Builder(this, DOWNLOAD_CHANNEL_ID)
|
2022-12-21 21:10:48 +05:30
|
|
|
.setContentTitle("[${item.type}] ${item.fileName}")
|
2022-12-17 15:56:57 +05:30
|
|
|
.setProgress(0, 0, true)
|
2022-12-16 22:14:21 +05:30
|
|
|
.setOngoing(true)
|
2022-12-17 15:56:57 +05:30
|
|
|
.setContentIntent(activityIntent)
|
2022-12-16 22:14:21 +05:30
|
|
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
|
|
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
2022-12-17 15:56:57 +05:30
|
|
|
.setGroup(DOWNLOAD_NOTIFICATION_GROUP)
|
|
|
|
}
|
2022-12-16 22:14:21 +05:30
|
|
|
|
2022-12-25 18:36:23 +05:30
|
|
|
private fun setResumeNotification(
|
|
|
|
notificationBuilder: NotificationCompat.Builder,
|
|
|
|
item: DownloadItem
|
|
|
|
) {
|
2022-12-17 15:56:57 +05:30
|
|
|
notificationBuilder
|
|
|
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
|
|
|
.setWhen(System.currentTimeMillis())
|
|
|
|
.setOngoing(true)
|
|
|
|
.clearActions()
|
|
|
|
.addAction(getPauseAction(item.id))
|
|
|
|
|
2022-12-21 21:18:30 +05:30
|
|
|
notificationManager.notify(item.getNotificationId(), notificationBuilder.build())
|
2022-12-17 15:56:57 +05:30
|
|
|
}
|
|
|
|
|
2022-12-21 21:10:48 +05:30
|
|
|
private fun setPauseNotification(
|
|
|
|
notificationBuilder: NotificationCompat.Builder,
|
|
|
|
item: DownloadItem,
|
|
|
|
isCompleted: Boolean = false
|
|
|
|
) {
|
2022-12-17 15:56:57 +05:30
|
|
|
notificationBuilder
|
|
|
|
.setProgress(0, 0, false)
|
|
|
|
.setOngoing(false)
|
|
|
|
.clearActions()
|
|
|
|
|
|
|
|
if (isCompleted) {
|
|
|
|
notificationBuilder
|
|
|
|
.setSmallIcon(R.drawable.ic_done)
|
|
|
|
.setContentText(getString(R.string.download_completed))
|
|
|
|
} else {
|
|
|
|
notificationBuilder
|
|
|
|
.setSmallIcon(R.drawable.ic_pause)
|
|
|
|
.setContentText(getString(R.string.download_paused))
|
|
|
|
.addAction(getResumeAction(item.id))
|
|
|
|
}
|
2022-12-21 21:18:30 +05:30
|
|
|
notificationManager.notify(item.getNotificationId(), notificationBuilder.build())
|
2022-06-05 21:38:47 +05:30
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private fun getResumeAction(id: Int): NotificationCompat.Action {
|
2023-01-14 22:47:34 +05:30
|
|
|
val intent = Intent(this, NotificationReceiver::class.java).apply {
|
|
|
|
action = ACTION_DOWNLOAD_RESUME
|
|
|
|
putExtra("id", id)
|
2022-06-05 21:38:47 +05:30
|
|
|
}
|
2022-12-16 22:14:21 +05:30
|
|
|
|
|
|
|
return NotificationCompat.Action.Builder(
|
2022-12-17 15:56:57 +05:30
|
|
|
R.drawable.ic_play,
|
|
|
|
getString(R.string.resume),
|
2023-01-18 09:00:27 +05:30
|
|
|
PendingIntentCompat.getBroadcast(this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
2022-12-16 22:14:21 +05:30
|
|
|
).build()
|
2022-06-05 23:10:16 +05:30
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private fun getPauseAction(id: Int): NotificationCompat.Action {
|
2023-01-18 09:00:27 +05:30
|
|
|
val intent = Intent(this, NotificationReceiver::class.java)
|
|
|
|
.setAction(ACTION_DOWNLOAD_PAUSE)
|
|
|
|
.putExtra("id", id)
|
2022-06-05 23:10:16 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
return NotificationCompat.Action.Builder(
|
|
|
|
R.drawable.ic_pause,
|
2022-12-17 15:56:57 +05:30
|
|
|
getString(R.string.pause),
|
2023-01-18 09:00:27 +05:30
|
|
|
PendingIntentCompat.getBroadcast(this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
2022-12-16 22:14:21 +05:30
|
|
|
).build()
|
|
|
|
}
|
2022-09-08 22:11:57 +05:30
|
|
|
|
2023-01-14 22:47:34 +05:30
|
|
|
/**
|
|
|
|
* Get a [File] from the corresponding download directory and the file name
|
|
|
|
*/
|
|
|
|
private fun getDownloadFile(directory: String, fileName: String): File {
|
|
|
|
return File(
|
|
|
|
DownloadHelper.getDownloadDir(
|
|
|
|
this@DownloadService,
|
|
|
|
directory
|
|
|
|
),
|
|
|
|
fileName
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
override fun onDestroy() {
|
2022-12-21 21:10:48 +05:30
|
|
|
downloadQueue.clear()
|
2022-12-16 22:14:21 +05:30
|
|
|
IS_DOWNLOAD_RUNNING = false
|
2022-12-21 21:10:48 +05:30
|
|
|
sendBroadcast(Intent(ACTION_SERVICE_STOPPED))
|
2022-03-03 12:08:36 +05:30
|
|
|
super.onDestroy()
|
|
|
|
}
|
2022-09-19 23:37:55 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
override fun onBind(intent: Intent?): IBinder {
|
2022-12-21 21:10:48 +05:30
|
|
|
val ids = intent?.getIntArrayExtra("ids")
|
|
|
|
ids?.forEach { id -> resume(id) }
|
2022-12-16 22:14:21 +05:30
|
|
|
return binder
|
|
|
|
}
|
|
|
|
|
|
|
|
inner class LocalBinder : Binder() {
|
|
|
|
fun getService(): DownloadService = this@DownloadService
|
|
|
|
}
|
|
|
|
|
2022-09-19 23:37:55 +05:30
|
|
|
companion object {
|
2022-12-17 15:56:57 +05:30
|
|
|
private const val DOWNLOAD_NOTIFICATION_GROUP = "download_notification_group"
|
2022-12-21 21:10:48 +05:30
|
|
|
const val ACTION_SERVICE_STARTED =
|
|
|
|
"com.github.libretube.services.DownloadService.ACTION_SERVICE_STARTED"
|
|
|
|
const val ACTION_SERVICE_STOPPED =
|
|
|
|
"com.github.libretube.services.DownloadService.ACTION_SERVICE_STOPPED"
|
2022-09-19 23:37:55 +05:30
|
|
|
var IS_DOWNLOAD_RUNNING = false
|
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|