Merge pull request #4101 from Bnyro/stop-download

Notification action to stop download
This commit is contained in:
Bnyro 2023-06-25 11:35:11 +02:00 committed by GitHub
commit ef14d1a04a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 12 deletions

View File

@ -24,6 +24,9 @@ interface DownloadDao {
@Query("SELECT * FROM downloaditem WHERE id = :id")
suspend fun findDownloadItemById(id: Int): DownloadItem
@Query("DELETE FROM downloaditem WHERE id = :id")
suspend fun deleteDownloadItemById(id: Int)
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertDownload(download: Download)

View File

@ -6,6 +6,8 @@ sealed class DownloadStatus {
object Paused : DownloadStatus()
object Stopped : DownloadStatus()
data class Progress(
val progress: Long,
val downloaded: Long,

View File

@ -25,5 +25,7 @@ class NotificationReceiver : BroadcastReceiver() {
"com.github.libretube.receivers.NotificationReceiver.ACTION_DOWNLOAD_RESUME"
const val ACTION_DOWNLOAD_PAUSE =
"com.github.libretube.receivers.NotificationReceiver.ACTION_DOWNLOAD_PAUSE"
const val ACTION_DOWNLOAD_STOP =
"com.github.libretube.receivers.NotificationReceiver.ACTION_DOWNLOAD_STOP"
}
}

View File

@ -11,6 +11,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.getSystemService
import androidx.core.util.remove
import androidx.core.util.set
import androidx.core.util.valueIterator
import androidx.lifecycle.LifecycleService
@ -37,6 +38,7 @@ import com.github.libretube.parcelable.DownloadData
import com.github.libretube.receivers.NotificationReceiver
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_PAUSE
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_RESUME
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_STOP
import com.github.libretube.ui.activities.MainActivity
import java.io.File
import java.net.HttpURLConnection
@ -45,23 +47,25 @@ import java.net.URL
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.concurrent.Executors
import kotlin.io.path.absolute
import kotlin.io.path.createFile
import kotlin.io.path.deleteIfExists
import kotlin.io.path.fileSize
import kotlin.math.min
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okio.buffer
import okio.sink
import okio.source
import kotlin.io.path.absolute
import kotlin.io.path.createFile
import kotlin.io.path.deleteIfExists
import kotlin.io.path.fileSize
import kotlin.math.min
/**
* Download service with custom implementation of downloading using [HttpURLConnection].
@ -87,9 +91,11 @@ class DownloadService : LifecycleService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val downloadId = intent?.getIntExtra("id", -1)
when (intent?.action) {
ACTION_DOWNLOAD_RESUME -> resume(intent.getIntExtra("id", -1))
ACTION_DOWNLOAD_PAUSE -> pause(intent.getIntExtra("id", -1))
ACTION_DOWNLOAD_RESUME -> resume(downloadId!!)
ACTION_DOWNLOAD_PAUSE -> pause(downloadId!!)
ACTION_DOWNLOAD_STOP -> stop(downloadId!!)
}
val downloadData = intent?.parcelableExtra<DownloadData>(IntentData.downloadData)
@ -200,7 +206,7 @@ class DownloadService : LifecycleService() {
notificationBuilder
.setContentText(
totalRead.formatAsFileSize() + " / " +
item.downloadSize.formatAsFileSize()
item.downloadSize.formatAsFileSize()
)
.setProgress(
item.downloadSize.toInt(),
@ -230,6 +236,11 @@ class DownloadService : LifecycleService() {
}
}
if (_downloadFlow.firstOrNull { it.first == item.id }?.second == DownloadStatus.Stopped) {
downloadQueue.remove(item.id, false)
return
}
val completed = when {
totalRead < item.downloadSize -> {
_downloadFlow.emit(item.id to DownloadStatus.Paused)
@ -324,9 +335,30 @@ class DownloadService : LifecycleService() {
fun pause(id: Int) {
downloadQueue[id] = false
// Stop the service if no downloads are active.
stopServiceIfDone()
}
/**
* Stop downloading job for given [id]. If no downloads are active, stop the service.
*/
private fun stop(id: Int) = CoroutineScope(Dispatchers.IO).launch {
downloadQueue[id] = false
_downloadFlow.emit(id to DownloadStatus.Stopped)
lifecycleScope.launch {
val item = Database.downloadDao().findDownloadItemById(id)
notificationManager.cancel(item.getNotificationId())
Database.downloadDao().deleteDownloadItemById(id)
stopServiceIfDone()
}
}
/**
* Stop service if no downloads are active
*/
private fun stopServiceIfDone() {
if (downloadQueue.valueIterator().asSequence().none { it }) {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH)
ServiceCompat.stopForeground(this@DownloadService, ServiceCompat.STOP_FOREGROUND_DETACH)
sendBroadcast(Intent(ACTION_SERVICE_STOPPED))
stopSelf()
}
@ -398,6 +430,7 @@ class DownloadService : LifecycleService() {
.setOngoing(true)
.clearActions()
.addAction(getPauseAction(item.id))
.addAction(getStopAction(item.id))
notificationManager.notify(item.getNotificationId(), notificationBuilder.build())
}
@ -421,6 +454,7 @@ class DownloadService : LifecycleService() {
.setSmallIcon(R.drawable.ic_pause)
.setContentText(getString(R.string.download_paused))
.addAction(getResumeAction(item.id))
.addAction(getStopAction(item.id))
}
notificationManager.notify(item.getNotificationId(), notificationBuilder.build())
}
@ -437,6 +471,21 @@ class DownloadService : LifecycleService() {
).build()
}
private fun getStopAction(id: Int): NotificationCompat.Action {
val intent = Intent(this, NotificationReceiver::class.java).apply {
action = ACTION_DOWNLOAD_STOP
putExtra("id", id)
}
// the request code must differ from the one of the pause/resume action
val requestCode = Int.MAX_VALUE / 2 - id
return NotificationCompat.Action.Builder(
R.drawable.ic_stop,
getString(R.string.stop),
PendingIntentCompat.getBroadcast(this, requestCode, intent, FLAG_UPDATE_CURRENT, false)
).build()
}
private fun getPauseAction(id: Int): NotificationCompat.Action {
val intent = Intent(this, NotificationReceiver::class.java)
.setAction(ACTION_DOWNLOAD_PAUSE)

View File

@ -24,12 +24,12 @@ import com.github.libretube.receivers.DownloadReceiver
import com.github.libretube.services.DownloadService
import com.github.libretube.ui.adapters.DownloadsAdapter
import com.github.libretube.ui.viewholders.DownloadsViewHolder
import kotlin.io.path.fileSize
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.io.path.fileSize
class DownloadsFragment : Fragment() {
private var _binding: FragmentDownloadsBinding? = null
@ -165,13 +165,15 @@ class DownloadsFragment : Fragment() {
downloadOverlay.visibility = View.GONE
}
DownloadStatus.Stopped -> Unit
is DownloadStatus.Progress -> {
downloadOverlay.visibility = View.VISIBLE
resumePauseBtn.setImageResource(R.drawable.ic_pause)
if (progressBar.isIndeterminate) return
progressBar.incrementProgressBy(status.progress.toInt())
val progressInfo = progressBar.progress.formatAsFileSize() +
" /\n" + progressBar.max.formatAsFileSize()
" /\n" + progressBar.max.formatAsFileSize()
fileSize.text = progressInfo
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6,6h12v12H6z" />
</vector>

View File

@ -168,6 +168,7 @@
<string name="unknown">Unknown</string>
<string name="pause">Pause</string>
<string name="resume">Resume</string>
<string name="stop">Stop</string>
<string name="player_autoplay">Autoplay</string>
<string name="instance_frontend_url">URL to instance frontend</string>
<string name="quality">Quality</string>