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