add shizuku install

pull/1327/merge
deniscerri 3 weeks ago
parent acd35ed2ee
commit aea0edc3ae
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -586,6 +586,14 @@
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name="rikka.shizuku.ShizukuProvider"
android:authorities="${applicationId}.shizuku"
android:multiprocess="false"
android:enabled="true"
android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"/>

@ -14,6 +14,7 @@ import com.deniscerri.ytdl.core.models.ExecuteException
import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.repository.ObserveSourcesRepository
import com.deniscerri.ytdl.services.BgUtilsPoTokenGeneratorService
import com.deniscerri.ytdl.util.ApkInstallUtil
import com.deniscerri.ytdl.util.BgUtilsPoTokenGeneratorUtil
import com.deniscerri.ytdl.util.Extensions.hasReachedEnd
import com.deniscerri.ytdl.util.NotificationUtil

@ -179,6 +179,13 @@ class SettingsActivity : BaseActivity(), SettingHost {
}
}
}
ApkInstallUtil.registerShizukuPermissionListener()
}
override fun onDestroy() {
super.onDestroy()
ApkInstallUtil.unregisterShizukuPermissionListener()
}
override fun onResume() {

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.PackageManagerCompat
import androidx.core.content.edit
import androidx.lifecycle.ViewModelProvider
@ -18,6 +19,7 @@ import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.ui.more.settings.SettingHost
import com.deniscerri.ytdl.ui.more.settings.SettingModule
import com.deniscerri.ytdl.util.ApkInstallUtil
import com.deniscerri.ytdl.util.ApkInstallUtil.REQUEST_CODE_SHIZUKU
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.UpdateUtil
@ -25,6 +27,7 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rikka.shizuku.Shizuku
import java.io.File
@ -97,9 +100,22 @@ object UpdateSettingsModule : SettingModule {
"apk_install_method" -> {
pref.apply {
setOnPreferenceChangeListener { _, newValue ->
host.findPref("apk_install_external_apk_id")?.isVisible = (newValue as String) == "external"
host.refreshUI()
true
var resp = true
if ((newValue as String) == "shizuku") {
ApkInstallUtil.requestShizukuPermission { granted, error ->
if (!granted) {
Snackbar.make(host.hostView!!, error ?: "Shizuku permission not granted", Snackbar.LENGTH_LONG).show()
resp = false
}
}
}
if (resp) {
host.findPref("apk_install_external_apk_id")?.isVisible = (newValue as String) == "external"
host.refreshUI()
}
resp
}
}
}

@ -26,6 +26,8 @@ object ApkInstallUtil {
// can't take a per-call lambda directly.
private var pendingInstallCallback: ((Result<Unit>) -> Unit)? = null
private var pendingShizukuPermissionCallback: ((Boolean) -> Unit)? = null
/**
* Call this once per Activity/Fragment (e.g. in onCreate) to create the launcher.
* Wires the launcher's result back into whatever callback was passed to installApk().
@ -45,6 +47,62 @@ object ApkInstallUtil {
}
}
private val shizukuPermissionListener =
Shizuku.OnRequestPermissionResultListener { requestCode, grantResult ->
if (requestCode != REQUEST_CODE_SHIZUKU) return@OnRequestPermissionResultListener
val granted = grantResult == android.content.pm.PackageManager.PERMISSION_GRANTED
val callback = pendingShizukuPermissionCallback
pendingShizukuPermissionCallback = null
callback?.let {
Handler(Looper.getMainLooper()).post { it(granted) }
}
}
fun registerShizukuPermissionListener() {
Shizuku.addRequestPermissionResultListener(shizukuPermissionListener)
}
fun unregisterShizukuPermissionListener() {
Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener)
}
fun requestShizukuPermission(onResult: (granted: Boolean, error: String?) -> Unit) {
if (!Shizuku.pingBinder()) {
onResult(false, "Please start the Shizuku service first")
return
}
if (Shizuku.isPreV11()) {
onResult(false, "Shizuku version not supported")
return
}
try {
when {
Shizuku.checkSelfPermission() == android.content.pm.PackageManager.PERMISSION_GRANTED -> {
onResult(true, null)
}
Shizuku.shouldShowRequestPermissionRationale() -> {
onResult(false, "Shizuku permission was denied. Please enable it manually.")
}
else -> {
pendingShizukuPermissionCallback = { granted ->
if (granted) onResult(true, null)
else onResult(false, "Shizuku permission denied")
}
Shizuku.requestPermission(REQUEST_CODE_SHIZUKU)
}
}
} catch (e: IllegalStateException) {
// Binder wasn't actually available despite pingBinder() check —
// service likely died/restarted between the check and this call.
pendingShizukuPermissionCallback = null
onResult(false, "Shizuku service is not ready. Please try again.")
}
}
fun installApk(
context: Context,
apkFile: File,
@ -75,14 +133,16 @@ object ApkInstallUtil {
}
}
"shizuku" -> {
if (!checkShizukuPermission()) {
onResult(Result.failure(Exception("Please start Shizuku service!")))
return
requestShizukuPermission { granted, error ->
if (!granted) {
onResult(Result.failure(Exception(error)))
return@requestShizukuPermission
}
Thread {
val result = installApkWithShizuku(apkFile)
Handler(Looper.getMainLooper()).post { onResult(result) }
}.start()
}
Thread {
val result = installApkWithShizuku(apkFile)
Handler(Looper.getMainLooper()).post { onResult(result) }
}.start()
}
"external" -> {
val packageName = preferences.getString("apk_install_external_apk_id", "")!!
@ -96,7 +156,7 @@ object ApkInstallUtil {
}
}
private const val REQUEST_CODE_SHIZUKU = 1001
const val REQUEST_CODE_SHIZUKU = 1001
fun checkShizukuPermission(): Boolean {
if (!Shizuku.pingBinder()) {
@ -131,7 +191,8 @@ object ApkInstallUtil {
private fun installApkWithShizuku(apkFile: File): Result<Unit> {
return try {
val command = arrayOf("pm", "install", "-r", apkFile.absolutePath)
val apkSize = apkFile.length()
val command = arrayOf("pm", "install", "-r", "-S", apkSize.toString())
val process: Process = newShizukuProcess(command, null, null)
process.outputStream.use { stdin ->
@ -144,7 +205,7 @@ object ApkInstallUtil {
val errorOutput = process.errorStream.bufferedReader().readText()
val exitCode = process.waitFor()
if (exitCode == 0) Result.success(Unit)
if (exitCode == 0 && output.contains("Success")) Result.success(Unit)
else Result.failure(Exception(errorOutput.ifBlank { output }))
} catch (e: Exception) {
e.printStackTrace()

@ -2907,10 +2907,13 @@ object UiUtil {
var tmpDownloadJob : Job? = null
var positiveButton: Button? = null
var negativeButton: Button? = null
val updateDialog = MaterialAlertDialogBuilder(context)
.setTitle("${item.tag_name} (${FileUtil.convertFileSize(item.downloadSize)})")
.setMessage(item.body)
.setIcon(R.drawable.ic_update_app)
.setCancelable(false)
.setNegativeButton(context.getString(R.string.cancel)) { _: DialogInterface?, _: Int ->
tmpDownloadJob?.cancel()
}
@ -2932,6 +2935,8 @@ object UiUtil {
val lifecycleScope = lifecycleOwner.lifecycleScope
positiveButton = view.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
negativeButton = view.getButton(android.app.AlertDialog.BUTTON_NEGATIVE)
positiveButton?.setOnClickListener {
positiveButton.isEnabled = false
positiveButton.text = "0%"
@ -2960,8 +2965,13 @@ object UiUtil {
fileResp.onSuccess { file ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
ApkInstallUtil.installApk(context, file, installLauncher, onResult)
positiveButton.text = context.getString(R.string.please_wait)
negativeButton.isEnabled = false
ApkInstallUtil.installApk(context, file, installLauncher) { result ->
onResult(result)
view.dismiss()
}
}
}
}

Loading…
Cancel
Save