Fix extension installation with shizuku (#3630)

* Fix extension installation with shizuku

* Changelog

* Move changelog to end
This commit is contained in:
NGB-Was-Taken
2026-07-25 18:36:11 +05:45
committed by GitHub
parent 9c3cc1ca5f
commit 4ac9f3474b
4 changed files with 15 additions and 43 deletions
@@ -1,7 +1,7 @@
package mihon.app.shizuku;
interface IShellInterface {
void install(in AssetFileDescriptor apk) = 1;
void install(in AssetFileDescriptor apk, in IntentSender intentSender) = 1;
void destroy() = 16777114;
}
@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.extension.installer
import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.ComponentName
@@ -39,6 +40,7 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
.processNameSuffix("shizuku_service")
.debuggable(BuildConfig.DEBUG)
.daemon(false)
.version(2)
}
private val connection = object : ServiceConnection {
@@ -53,6 +55,13 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
}
}
private val statusIntent = PendingIntent.getBroadcast(
service.applicationContext,
0,
Intent(ACTION_INSTALL_RESULT).setPackage(BuildConfig.APPLICATION_ID),
PendingIntent.FLAG_MUTABLE,
)
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Int.MIN_VALUE)
@@ -110,7 +119,7 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
super.processEntry(entry)
try {
service.contentResolver.openAssetFileDescriptor(entry.uri, "r").use {
shellInterface?.install(it)
shellInterface?.install(it, statusIntent.intentSender)
}
service.contentResolver.delete(entry.uri, null, null)
} catch (e: Exception) {
@@ -40,9 +40,6 @@
package mihon.app.shizuku
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.content.pm.PackageInstaller
import android.content.res.AssetFileDescriptor
@@ -51,21 +48,18 @@ import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.os.UserHandle
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.extension.installer.ACTION_INSTALL_RESULT
import rikka.shizuku.SystemServiceHelper
import java.io.OutputStream
import kotlin.system.exitProcess
class ShellInterface : IShellInterface.Stub() {
private val context = createContext()
private val userId = UserHandle::class.java
.getMethod("myUserId")
.invoke(null) as Int
private val packageName = BuildConfig.APPLICATION_ID
@SuppressLint("PrivateApi")
override fun install(apk: AssetFileDescriptor) {
override fun install(apk: AssetFileDescriptor, intentSender: IntentSender) {
val pmInterface = Class.forName($$"android.content.pm.IPackageManager$Stub")
.getMethod("asInterface", IBinder::class.java)
.invoke(null, SystemServiceHelper.getSystemService("package"))
@@ -136,50 +130,18 @@ class ShellInterface : IShellInterface.Stub() {
apk.createInputStream().use { input -> input.copyTo(output) }
}
val statusIntent = PendingIntent.getBroadcast(
context,
0,
Intent(ACTION_INSTALL_RESULT).setPackage(packageName),
PendingIntent.FLAG_MUTABLE,
)
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
session::class.java.getMethod("commit", IntentSender::class.java, Boolean::class.java)
.invoke(session, statusIntent.intentSender, false)
.invoke(session, intentSender, false)
} else {
session::class.java.getMethod("commit", IntentSender::class.java)
.invoke(session, statusIntent.intentSender)
.invoke(session, intentSender)
}
}
override fun destroy() {
exitProcess(0)
}
@SuppressLint("PrivateApi")
private fun createContext(): Context {
val activityThread = Class.forName("android.app.ActivityThread")
val systemMain = activityThread.getMethod("systemMain").invoke(null)
val systemContext = activityThread.getMethod("getSystemContext").invoke(systemMain) as Context
val shellUserHandle = UserHandle::class.java
.getConstructor(Int::class.java)
.newInstance(userId)
val shellContext = systemContext::class.java.getMethod(
"createPackageContextAsUser",
String::class.java,
Int::class.java,
UserHandle::class.java,
).invoke(
systemContext,
"com.android.shell",
Context.CONTEXT_INCLUDE_CODE or Context.CONTEXT_IGNORE_SECURITY,
shellUserHandle,
) as Context
return shellContext.createPackageContext("com.android.shell", 0)
}
}
// Constant hidden from the SDK