add request install packages for github flavor only

pull/1185/merge
deniscerri 3 months ago
parent c3a5b9e1e1
commit dd8866098d
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Install YTDLnis packages permission for github releases only, as its an anti feature for F-droid builds. You have to manually install the downloaded apks and then restart the app to apply the changes-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
</manifest>

@ -1,28 +1,18 @@
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
@ -197,86 +187,44 @@ abstract class PackageBase {
)
}
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
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))
FileUtil.openFileIntent(context, localUri)
}
}
cursor.close()
}
}
}
@SuppressLint("UnspecifiedRegisterReceiverFlag", "Range")
suspend fun downloadReleaseApk(context: Context, release: PackageRelease, onProgress: (Long) -> Unit) : Result<Unit> {
suspend fun downloadReleaseApk(release: PackageRelease, onProgress: (Long) -> Unit) : Result<File> {
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
}
File(FileUtil.getDefaultApksPath()).mkdirs()
val tempApk = File(FileUtil.getDefaultApksPath(), "${packageFolderName}_${release.version.replace(".", "")}.apk")
val request = Request.Builder()
.url(release.assets.first().browser_download_url)
.build()
if (totalBytes > 0) {
val progress = (bytesDownloaded * 100L / totalBytes)
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdirs()
val response = sharedClient.newCall(request).execute()
if (!response.isSuccessful) {
return@withContext Result.failure(Throwable(response.body.string()))
}
val body = response.body
val totalBytes = body.contentLength()
var bytesDownloaded = 0L
body.byteStream().use { inputStream ->
tempApk.outputStream().use { outputStream ->
val buffer = ByteArray(8192)
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
val progress = ((bytesDownloaded * 100) / totalBytes)
onProgress(progress)
}
}
cursor.close()
delay(500)
}
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)
Result.success(tempApk)
} catch (e: Exception) {
Result.failure(e)
}

@ -116,7 +116,7 @@ object DownloadSettingsModule : SettingModule {
if (!scheduler.canSchedule() && Build.VERSION.SDK_INT >= 31){
Intent().also { intent ->
intent.action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
context.startActivity(intent)
host.getHostContext().startActivity(intent)
}
allowChange = false
}
@ -135,7 +135,7 @@ object DownloadSettingsModule : SettingModule {
if (!scheduler.canSchedule() && Build.VERSION.SDK_INT >= 31){
Intent().also { intent ->
intent.action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
context.startActivity(intent)
host.getHostContext().startActivity(intent)
}
allowChange = false
}else{

@ -1,12 +1,12 @@
package com.deniscerri.ytdl.ui.more.settings.updating
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.util.DisplayMetrics
@ -20,7 +20,6 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import androidx.core.view.isVisible
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
@ -51,10 +50,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import androidx.core.net.toUri
import com.anggrayudi.storage.file.toRawFile
import com.deniscerri.ytdl.core.packages.Deno
import java.io.File
import com.deniscerri.ytdl.util.Extensions.hasPermission
class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, PackageReleaseAdapter.OnItemClickListener {
@ -231,7 +228,7 @@ class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, Packag
tmpDownloadJob = lifecycleScope.launch {
val instance = tmpItem!!.getInstance()
val fileResp = instance.downloadReleaseApk(requireContext(), item) { progress ->
val fileResp = instance.downloadReleaseApk(item) { progress ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
positiveButton.text = "$progress%"
@ -248,11 +245,27 @@ class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, Packag
}
}
fileResp.onSuccess {
fileResp.onSuccess { file ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
Snackbar.make(requireView(), getString(R.string.install_downloaded_file), Snackbar.LENGTH_LONG).show()
val canRequestPackageInstalls = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
android.Manifest.permission.REQUEST_INSTALL_PACKAGES.hasPermission(requireContext())
} else {
true
}
if (canRequestPackageInstalls) {
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
}
installLauncher.launch(intent)
} else {
Snackbar.make(requireView(), getString(R.string.install_downloaded_file), Snackbar.LENGTH_LONG).show()
}
}
}
}

@ -4,6 +4,7 @@ import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
@ -16,6 +17,7 @@ import android.graphics.drawable.shapes.OvalShape
import android.media.MediaMetadataRetriever
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
import android.net.Uri
import android.os.Build
import android.text.Editable
import android.text.Spanned
import android.text.TextWatcher
@ -697,4 +699,21 @@ object Extensions {
null
}
}
fun String.hasPermission(context: Context) : Boolean {
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.getPackageInfo(
context.packageName,
PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong())
)
} else {
@Suppress("DEPRECATION")
context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_PERMISSIONS
)
}
return packageInfo.requestedPermissions?.contains(this) ?: false
}
}

@ -363,6 +363,10 @@ object FileUtil {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Command"
}
fun getDefaultApksPath() : String {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Apks"
}
fun getDefaultApplicationPath() : String {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis"
}

@ -5,18 +5,14 @@ import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.text.Editable
import android.text.TextWatcher
import android.text.format.DateFormat
@ -51,7 +47,6 @@ import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
@ -72,6 +67,7 @@ import com.deniscerri.ytdl.ui.downloadcard.VideoCutListener
import com.deniscerri.ytdl.util.Extensions.createBadge
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.Extensions.getMediaDuration
import com.deniscerri.ytdl.util.Extensions.hasPermission
import com.deniscerri.ytdl.util.Extensions.toStringDuration
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
@ -2745,7 +2741,7 @@ object UiUtil {
val lifecycleScope = lifecycleOwner.lifecycleScope
tmpDownloadJob = lifecycleScope.launch {
val fileResp = updateUtil.downloadReleaseApk(context, v) { progress ->
val fileResp = updateUtil.downloadReleaseApk(v) { progress ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
positiveButton.text = "$progress%"
@ -2766,15 +2762,27 @@ object UiUtil {
}
}
fileResp.onSuccess {
fileResp.onSuccess { file ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
val canRequestPackageInstalls = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
android.Manifest.permission.REQUEST_INSTALL_PACKAGES.hasPermission(context)
} else {
true
}
if (canRequestPackageInstalls) {
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
}
context.startActivity(intent)
} else {
Snackbar.make(context.findViewById(R.id.frame_layout), context.getString(R.string.install_downloaded_file), Snackbar.LENGTH_LONG).show()
}
view.dismiss()
Snackbar.make(
context.findViewById(R.id.frame_layout),
context.getString(R.string.install_downloaded_file),
Snackbar.LENGTH_LONG
).show()
}
}
}

@ -1,14 +1,8 @@
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
@ -16,12 +10,10 @@ import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.core.RuntimeManager
import com.deniscerri.ytdl.core.models.YTDLRequest
import com.deniscerri.ytdl.core.packages.PackageBase.Companion.sharedClient
import com.deniscerri.ytdl.core.packages.PackageBase.PackageRelease
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
@ -30,7 +22,6 @@ 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) {
@ -167,81 +158,46 @@ class UpdateUtil(var context: Context) {
}
@SuppressLint("Range", "UnspecifiedRegisterReceiverFlag")
suspend fun downloadReleaseApk(context: Context, release: GithubRelease, onProgress: (Long) -> Unit) : Result<Unit> {
suspend fun downloadReleaseApk(release: GithubRelease, onProgress: (Long) -> Unit) : Result<File> {
return withContext(Dispatchers.IO) {
try {
val releaseVersion = release.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }
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
}
if (totalBytes > 0) {
val progress = (bytesDownloaded * 100L / totalBytes)
onProgress(progress)
}
}
cursor.close()
delay(500)
val releaseVersion = release.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }!!
File(FileUtil.getDefaultApksPath()).mkdirs()
val tempApk = File(FileUtil.getDefaultApksPath(), "${releaseVersion.browser_download_url.split("/").last()}")
//download
val request = Request.Builder()
.url(releaseVersion.browser_download_url)
.build()
val response = sharedClient.newCall(request).execute()
if (!response.isSuccessful) {
return@withContext Result.failure(Throwable(response.body.string()))
}
val onDownloadComplete = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
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
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
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()
// Calculate percentage
val progress = ((bytesDownloaded * 100) / totalBytes)
onProgress(progress)
}
}
}
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)
Result.success(tempApk)
} catch (e: Exception) {
Result.failure(e)
}

Loading…
Cancel
Save