From 98318bdb1f5ebcc643c3b062acfc548e75e18801 Mon Sep 17 00:00:00 2001
From: deniscerri <64997243+deniscerri@users.noreply.github.com>
Date: Thu, 9 Apr 2026 20:31:39 +0200
Subject: [PATCH] .
---
app/src/main/AndroidManifest.xml | 3 -
.../ytdl/core/packages/PackageBase.kt | 114 +++++++++++++-----
.../settings/updating/PackagesFragment.kt | 15 +--
.../java/com/deniscerri/ytdl/util/UiUtil.kt | 24 ++--
.../com/deniscerri/ytdl/util/UpdateUtil.kt | 95 +++++++++++----
app/src/main/res/values/strings.xml | 1 +
6 files changed, 179 insertions(+), 73 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7b211db6..1ff9e60a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,9 +25,6 @@
-
-
diff --git a/app/src/main/java/com/deniscerri/ytdl/core/packages/PackageBase.kt b/app/src/main/java/com/deniscerri/ytdl/core/packages/PackageBase.kt
index 9d1f06a0..ff9538fa 100644
--- a/app/src/main/java/com/deniscerri/ytdl/core/packages/PackageBase.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/core/packages/PackageBase.kt
@@ -1,18 +1,28 @@
package com.deniscerri.ytdl.core.packages
+import android.annotation.SuppressLint
+import android.app.DownloadManager
+import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
+import android.os.Environment
+import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.edit
+import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
+import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.core.RuntimeManager
import com.deniscerri.ytdl.core.ZipUtils
import com.deniscerri.ytdl.database.models.GithubReleaseAsset
+import com.deniscerri.ytdl.util.FileUtil
import com.google.gson.annotations.SerializedName
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerialName
@@ -187,44 +197,88 @@ abstract class PackageBase {
)
}
- suspend fun downloadReleaseApk(context: Context, release: PackageRelease, onProgress: (Int) -> Unit) : Result {
- return withContext(Dispatchers.IO) {
- try {
- val tempApk = File(context.cacheDir, "${packageFolderName}_${release.version.replace(".", "")}.apk")
-
- //download
- val request = Request.Builder()
- .url(release.assets.first().browser_download_url)
- .build()
-
- val response = sharedClient.newCall(request).execute()
- if (!response.isSuccessful) {
- return@withContext Result.failure(Throwable(response.body.string()))
+ private var downloadReleaseId = -1L
+ private val onDownloadReleaseComplete = object : BroadcastReceiver() {
+ @SuppressLint("Range")
+ override fun onReceive(context: Context?, intent: Intent) {
+ if (context == null) return;
+ val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
+ val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
+ Log.e("sssssss",id.toString())
+
+ if (id == downloadReleaseId) {
+ context.unregisterReceiver(this)
+
+ val query = DownloadManager.Query().setFilterById(id)
+ val cursor = downloadManager.query(query)
+
+ if (cursor.moveToFirst()) {
+ val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
+ if (status == DownloadManager.STATUS_SUCCESSFUL) {
+ val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
+ Log.e("sssssss", localUri)
+ FileUtil.openFileIntent(context, localUri)
+ }
}
+ cursor.close()
+ }
+ }
+ }
- val body = response.body
- val totalBytes = body.contentLength()
- var bytesDownloaded = 0L
-
- body.byteStream().use { inputStream ->
- tempApk.outputStream().use { outputStream ->
- val buffer = ByteArray(8192) // 8KB buffer
- var bytesRead: Int
-
- while (inputStream.read(buffer).also { bytesRead = it } != -1) {
- if (!isActive) throw CancellationException("Download cancelled by user")
-
- outputStream.write(buffer, 0, bytesRead)
- bytesDownloaded += bytesRead
+ @SuppressLint("UnspecifiedRegisterReceiverFlag", "Range")
+ suspend fun downloadReleaseApk(context: Context, release: PackageRelease, onProgress: (Long) -> Unit) : Result {
+ return withContext(Dispatchers.IO) {
+ try {
+ val releaseVersion = release.assets.first()
+
+ val uri = releaseVersion.browser_download_url.toUri()
+ Environment
+ .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ .mkdirs()
+ val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
+ downloadReleaseId = downloadManager.enqueue(
+ DownloadManager.Request(uri)
+ .setAllowedNetworkTypes(
+ DownloadManager.Request.NETWORK_WIFI or
+ DownloadManager.Request.NETWORK_MOBILE
+ )
+ .setAllowedOverRoaming(true)
+ .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
+ .setTitle(releaseVersion.name)
+ .setDescription(context.getString(R.string.downloading_update))
+ .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name)
+ )
+
+ var downloading = true
+ while (downloading) {
+ val query = DownloadManager.Query().setFilterById(downloadReleaseId)
+ val cursor = downloadManager.query(query)
+
+ if (cursor.moveToFirst()) {
+ val bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
+ val totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
+ val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
+
+ if (status == DownloadManager.STATUS_SUCCESSFUL) {
+ downloading = false
+ }
- // Calculate percentage
- val progress = ((bytesDownloaded * 100) / totalBytes).toInt()
+ if (totalBytes > 0) {
+ val progress = (bytesDownloaded * 100L / totalBytes)
onProgress(progress)
}
}
+ cursor.close()
+ delay(500)
}
- Result.success(tempApk)
+ val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ context.registerReceiver(onDownloadReleaseComplete, intentFilter, Context.RECEIVER_EXPORTED)
+ } else {
+ context.registerReceiver(onDownloadReleaseComplete, intentFilter)
+ }
+ Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/updating/PackagesFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/updating/PackagesFragment.kt
index c0054bc0..1c492715 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/updating/PackagesFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/updating/PackagesFragment.kt
@@ -199,6 +199,8 @@ class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, Packag
}
override fun onDownloadReleaseClick(item: PackageBase.PackageRelease) {
+ bottomSheet?.dismiss()
+
var positiveButton: Button? = null
val updateDialog = MaterialAlertDialogBuilder(requireContext())
.setTitle("${item.tag_name} (${FileUtil.convertFileSize(item.downloadSize)})")
@@ -245,15 +247,14 @@ class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, Packag
}
}
}
- fileResp.onSuccess { file ->
- view.dismiss()
- val contentUri = FileProvider.getUriForFile(requireContext(), requireContext().packageName + ".fileprovider", file)
- val intent = Intent(Intent.ACTION_VIEW).apply {
- setDataAndType(contentUri, "application/vnd.android.package-archive")
- flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ fileResp.onSuccess {
+ lifecycleScope.launch {
+ withContext(Dispatchers.Main) {
+ view.dismiss()
+ Snackbar.make(requireView(), getString(R.string.install_downloaded_file), Snackbar.LENGTH_LONG).show()
+ }
}
- installLauncher.launch(intent)
}
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt
index 03ad6d30..079760b2 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt
@@ -2757,18 +2757,26 @@ object UiUtil {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
- Snackbar.make(context.findViewById(R.id.frame_layout), it.message ?: context.getString(R.string.errored), Snackbar.LENGTH_LONG).show()
+ Snackbar.make(
+ context.findViewById(R.id.frame_layout),
+ it.message ?: context.getString(R.string.errored),
+ Snackbar.LENGTH_LONG
+ ).show()
}
}
}
- fileResp.onSuccess { file ->
- view.dismiss()
- val contentUri = FileProvider.getUriForFile(context, context.packageName + ".fileprovider", file)
- val intent = Intent(Intent.ACTION_VIEW).apply {
- setDataAndType(contentUri, "application/vnd.android.package-archive")
- flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
+
+ fileResp.onSuccess {
+ lifecycleScope.launch {
+ withContext(Dispatchers.Main) {
+ view.dismiss()
+ Snackbar.make(
+ context.findViewById(R.id.frame_layout),
+ context.getString(R.string.install_downloaded_file),
+ Snackbar.LENGTH_LONG
+ ).show()
+ }
}
- context.startActivity(intent)
}
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt
index e0ed3063..5d758818 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt
@@ -1,8 +1,14 @@
package com.deniscerri.ytdl.util
import android.annotation.SuppressLint
+import android.app.DownloadManager
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.Uri
import android.os.Build
+import android.os.Environment
import android.util.Log
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.BuildConfig
@@ -15,6 +21,7 @@ import com.deniscerri.ytdl.database.models.GithubRelease
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import okhttp3.Request
@@ -23,6 +30,7 @@ import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import kotlin.coroutines.cancellation.CancellationException
+import androidx.core.net.toUri
class UpdateUtil(var context: Context) {
@@ -158,45 +166,82 @@ class UpdateUtil(var context: Context) {
}
- suspend fun downloadReleaseApk(context: Context, release: GithubRelease, onProgress: (Int) -> Unit) : Result {
+ @SuppressLint("Range", "UnspecifiedRegisterReceiverFlag")
+ suspend fun downloadReleaseApk(context: Context, release: GithubRelease, onProgress: (Long) -> Unit) : Result {
return withContext(Dispatchers.IO) {
try {
- val tempApk = File(context.cacheDir, "YTDLnis-${release.tag_name}_interally_downloaded.apk")
val releaseVersion = release.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }
- //download
- val request = Request.Builder()
- .url(releaseVersion!!.browser_download_url)
- .build()
+ val uri = releaseVersion!!.browser_download_url.toUri()
+ Environment
+ .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ .mkdirs()
+ val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
+ val downloadID = downloadManager.enqueue(
+ DownloadManager.Request(uri)
+ .setAllowedNetworkTypes(
+ DownloadManager.Request.NETWORK_WIFI or
+ DownloadManager.Request.NETWORK_MOBILE
+ )
+ .setAllowedOverRoaming(true)
+ .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
+ .setTitle(releaseVersion.name)
+ .setDescription(context.getString(R.string.downloading_update))
+ .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name)
+ )
+
+ var downloading = true
+ while (downloading) {
+ val query = DownloadManager.Query().setFilterById(downloadID)
+ val cursor = downloadManager.query(query)
+
+ if (cursor.moveToFirst()) {
+ val bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
+ val totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
+ val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
+
+ if (status == DownloadManager.STATUS_SUCCESSFUL) {
+ downloading = false
+ }
- val response = sharedClient.newCall(request).execute()
- if (!response.isSuccessful) {
- return@withContext Result.failure(Throwable(response.body.string()))
+ if (totalBytes > 0) {
+ val progress = (bytesDownloaded * 100L / totalBytes)
+ onProgress(progress)
+ }
+ }
+ cursor.close()
+ delay(500)
}
- val body = response.body
- val totalBytes = body.contentLength()
- var bytesDownloaded = 0L
+ val onDownloadComplete = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent) {
+ val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
- body.byteStream().use { inputStream ->
- tempApk.outputStream().use { outputStream ->
- val buffer = ByteArray(8192) // 8KB buffer
- var bytesRead: Int
+ if (id == downloadID) {
+ context?.unregisterReceiver(this)
- while (inputStream.read(buffer).also { bytesRead = it } != -1) {
- if (!isActive) throw CancellationException("Download cancelled by user")
+ val query = DownloadManager.Query().setFilterById(id)
+ val cursor = downloadManager.query(query)
- outputStream.write(buffer, 0, bytesRead)
- bytesDownloaded += bytesRead
-
- // Calculate percentage
- val progress = ((bytesDownloaded * 100) / totalBytes).toInt()
- onProgress(progress)
+ if (cursor.moveToFirst()) {
+ val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
+ if (status == DownloadManager.STATUS_SUCCESSFUL) {
+ val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
+ FileUtil.openFileIntent(context!!, localUri)
+ }
+ }
+ cursor.close()
}
}
}
- Result.success(tempApk)
+ val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ context.registerReceiver(onDownloadComplete, intentFilter, Context.RECEIVER_EXPORTED)
+ } else {
+ context.registerReceiver(onDownloadComplete, intentFilter)
+ }
+ Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6565dadb..189773aa 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -519,4 +519,5 @@
Save to playlist subdirectory
Save files in folders named as the playlist name
Trim filenames
+ Install the downloaded file