mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-08 18:18:56 +01:00
Update shizuku.version to v13.1.5 (#2566)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
@@ -7,7 +7,7 @@ indent_style = space
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.{xml,sq,sqm}]
|
[*.{xml,sq,sqm,aidl}]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
# noinspection EditorConfigKeyCorrectness
|
# noinspection EditorConfigKeyCorrectness
|
||||||
|
|||||||
@@ -138,9 +138,9 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
|
aidl = true
|
||||||
|
|
||||||
// Disable some unused things
|
// Disable some unused things
|
||||||
aidl = false
|
|
||||||
renderScript = false
|
renderScript = false
|
||||||
shaders = false
|
shaders = false
|
||||||
}
|
}
|
||||||
|
|||||||
7
app/src/main/aidl/mihon/app/shizuku/IShellInterface.aidl
Normal file
7
app/src/main/aidl/mihon/app/shizuku/IShellInterface.aidl
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package mihon.app.shizuku;
|
||||||
|
|
||||||
|
interface IShellInterface {
|
||||||
|
void install(in AssetFileDescriptor apk) = 1;
|
||||||
|
|
||||||
|
void destroy() = 16777114;
|
||||||
|
}
|
||||||
@@ -1,27 +1,73 @@
|
|||||||
package eu.kanade.tachiyomi.extension.installer
|
package eu.kanade.tachiyomi.extension.installer
|
||||||
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Process
|
import android.os.IBinder
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.util.system.getUriSize
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.app.shizuku.IShellInterface
|
||||||
|
import mihon.app.shizuku.ShellInterface
|
||||||
import rikka.shizuku.Shizuku
|
import rikka.shizuku.Shizuku
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
class ShizukuInstaller(private val service: Service) : Installer(service) {
|
class ShizukuInstaller(private val service: Service) : Installer(service) {
|
||||||
|
|
||||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
|
||||||
|
private var shellInterface: IShellInterface? = null
|
||||||
|
|
||||||
|
private val shizukuArgs by lazy {
|
||||||
|
Shizuku.UserServiceArgs(
|
||||||
|
ComponentName(service, ShellInterface::class.java),
|
||||||
|
)
|
||||||
|
.tag("shizuku_service")
|
||||||
|
.processNameSuffix("shizuku_service")
|
||||||
|
.debuggable(BuildConfig.DEBUG)
|
||||||
|
.daemon(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val connection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
|
shellInterface = IShellInterface.Stub.asInterface(service)
|
||||||
|
ready = true
|
||||||
|
checkQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
shellInterface = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Int.MIN_VALUE)
|
||||||
|
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||||
|
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||||
|
|
||||||
|
if (status == PackageInstaller.STATUS_SUCCESS) {
|
||||||
|
continueQueue(InstallStep.Installed)
|
||||||
|
} else {
|
||||||
|
logcat(LogPriority.ERROR) { "Failed to install extension $packageName: $message" }
|
||||||
|
continueQueue(InstallStep.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val shizukuDeadListener = Shizuku.OnBinderDeadListener {
|
private val shizukuDeadListener = Shizuku.OnBinderDeadListener {
|
||||||
logcat { "Shizuku was killed prematurely" }
|
logcat { "Shizuku was killed prematurely" }
|
||||||
service.stopSelf()
|
service.stopSelf()
|
||||||
@@ -31,8 +77,8 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
|
|||||||
override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
|
override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
|
||||||
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
|
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
|
||||||
if (grantResult == PackageManager.PERMISSION_GRANTED) {
|
if (grantResult == PackageManager.PERMISSION_GRANTED) {
|
||||||
ready = true
|
|
||||||
checkQueue()
|
checkQueue()
|
||||||
|
Shizuku.bindUserService(shizukuArgs, connection)
|
||||||
} else {
|
} else {
|
||||||
service.stopSelf()
|
service.stopSelf()
|
||||||
}
|
}
|
||||||
@@ -41,42 +87,36 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun initShizuku() {
|
||||||
|
if (ready) return
|
||||||
|
if (!Shizuku.pingBinder()) {
|
||||||
|
logcat(LogPriority.ERROR) { "Shizuku is not ready to use" }
|
||||||
|
service.toast(MR.strings.ext_installer_shizuku_stopped)
|
||||||
|
service.stopSelf()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Shizuku.bindUserService(shizukuArgs, connection)
|
||||||
|
} else {
|
||||||
|
Shizuku.addRequestPermissionResultListener(shizukuPermissionListener)
|
||||||
|
Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_CODE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override var ready = false
|
override var ready = false
|
||||||
|
|
||||||
override fun processEntry(entry: Entry) {
|
override fun processEntry(entry: Entry) {
|
||||||
super.processEntry(entry)
|
super.processEntry(entry)
|
||||||
scope.launch {
|
|
||||||
var sessionId: String? = null
|
|
||||||
try {
|
try {
|
||||||
val size = service.getUriSize(entry.uri) ?: throw IllegalStateException()
|
shellInterface?.install(
|
||||||
service.contentResolver.openInputStream(entry.uri)!!.use {
|
service.contentResolver.openAssetFileDescriptor(entry.uri, "r"),
|
||||||
val userId = Process.myUserHandle().hashCode()
|
)
|
||||||
val createCommand = "pm install-create --user $userId -r -i ${service.packageName} -S $size"
|
|
||||||
val createResult = exec(createCommand)
|
|
||||||
sessionId = SESSION_ID_REGEX.find(createResult.out)?.value
|
|
||||||
?: throw RuntimeException("Failed to create install session")
|
|
||||||
|
|
||||||
val writeResult = exec("pm install-write -S $size $sessionId base -", it)
|
|
||||||
if (writeResult.resultCode != 0) {
|
|
||||||
throw RuntimeException("Failed to write APK to session $sessionId")
|
|
||||||
}
|
|
||||||
|
|
||||||
val commitResult = exec("pm install-commit $sessionId")
|
|
||||||
if (commitResult.resultCode != 0) {
|
|
||||||
throw RuntimeException("Failed to commit install session $sessionId")
|
|
||||||
}
|
|
||||||
|
|
||||||
continueQueue(InstallStep.Installed)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to install extension ${entry.downloadId} ${entry.uri}" }
|
logcat(LogPriority.ERROR, e) { "Failed to install extension ${entry.downloadId} ${entry.uri}" }
|
||||||
if (sessionId != null) {
|
|
||||||
exec("pm install-abandon $sessionId")
|
|
||||||
}
|
|
||||||
continueQueue(InstallStep.Error)
|
continueQueue(InstallStep.Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Don't cancel if entry is already started installing
|
// Don't cancel if entry is already started installing
|
||||||
override fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry
|
override fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry
|
||||||
@@ -84,41 +124,26 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Shizuku.removeBinderDeadListener(shizukuDeadListener)
|
Shizuku.removeBinderDeadListener(shizukuDeadListener)
|
||||||
Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener)
|
Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener)
|
||||||
|
Shizuku.unbindUserService(shizukuArgs, connection, true)
|
||||||
|
service.unregisterReceiver(receiver)
|
||||||
|
logcat { "ShizukuInstaller destroy" }
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
val process = Shizuku.newProcess(arrayOf("sh", "-c", command), null, null)
|
|
||||||
if (stdin != null) {
|
|
||||||
process.outputStream.use { stdin.copyTo(it) }
|
|
||||||
}
|
|
||||||
val output = process.inputStream.bufferedReader().use(BufferedReader::readText)
|
|
||||||
val resultCode = process.waitFor()
|
|
||||||
return ShellResult(resultCode, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class ShellResult(val resultCode: Int, val out: String)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Shizuku.addBinderDeadListener(shizukuDeadListener)
|
Shizuku.addBinderDeadListener(shizukuDeadListener)
|
||||||
ready = if (Shizuku.pingBinder()) {
|
|
||||||
if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
|
ContextCompat.registerReceiver(
|
||||||
true
|
service,
|
||||||
} else {
|
receiver,
|
||||||
Shizuku.addRequestPermissionResultListener(shizukuPermissionListener)
|
IntentFilter(ACTION_INSTALL_RESULT),
|
||||||
Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_CODE)
|
ContextCompat.RECEIVER_EXPORTED,
|
||||||
false
|
)
|
||||||
}
|
|
||||||
} else {
|
initShizuku()
|
||||||
logcat(LogPriority.ERROR) { "Shizuku is not ready to use" }
|
|
||||||
service.toast(MR.strings.ext_installer_shizuku_stopped)
|
|
||||||
service.stopSelf()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val SHIZUKU_PERMISSION_REQUEST_CODE = 14045
|
private const val SHIZUKU_PERMISSION_REQUEST_CODE = 14045
|
||||||
private val SESSION_ID_REGEX = Regex("(?<=\\[).+?(?=])")
|
const val ACTION_INSTALL_RESULT = "${BuildConfig.APPLICATION_ID}.ACTION_INSTALL_RESULT"
|
||||||
|
|||||||
176
app/src/main/java/mihon/app/shizuku/ShellInterface.kt
Normal file
176
app/src/main/java/mihon/app/shizuku/ShellInterface.kt
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Mihon Open Source Project
|
||||||
|
* Copyright 2015-2024 Javier Tomás
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* The file contains code originally licensed under the MIT license:
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Zachary Wander
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
import android.os.Build
|
||||||
|
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) {
|
||||||
|
val pmInterface = Class.forName($$"android.content.pm.IPackageManager$Stub")
|
||||||
|
.getMethod("asInterface", IBinder::class.java)
|
||||||
|
.invoke(null, SystemServiceHelper.getSystemService("package"))
|
||||||
|
|
||||||
|
val packageInstaller = Class.forName("android.content.pm.IPackageManager")
|
||||||
|
.getMethod("getPackageInstaller")
|
||||||
|
.invoke(pmInterface)
|
||||||
|
|
||||||
|
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL).apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
setPackageSource(PackageInstaller.PACKAGE_SOURCE_STORE)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
setInstallerPackageName(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sessionId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
packageInstaller::class.java.getMethod(
|
||||||
|
"createSession",
|
||||||
|
PackageInstaller.SessionParams::class.java,
|
||||||
|
String::class.java,
|
||||||
|
String::class.java,
|
||||||
|
Int::class.java,
|
||||||
|
).invoke(packageInstaller, params, packageName, packageName, userId) as Int
|
||||||
|
} else {
|
||||||
|
packageInstaller::class.java.getMethod(
|
||||||
|
"createSession",
|
||||||
|
PackageInstaller.SessionParams::class.java,
|
||||||
|
String::class.java,
|
||||||
|
Int::class.java,
|
||||||
|
).invoke(packageInstaller, params, packageName, userId) as Int
|
||||||
|
}
|
||||||
|
|
||||||
|
val session = packageInstaller::class.java
|
||||||
|
.getMethod("openSession", Int::class.java)
|
||||||
|
.invoke(packageInstaller, sessionId)
|
||||||
|
|
||||||
|
(
|
||||||
|
session::class.java.getMethod(
|
||||||
|
"openWrite",
|
||||||
|
String::class.java,
|
||||||
|
Long::class.java,
|
||||||
|
Long::class.java,
|
||||||
|
).invoke(session, "extension", 0L, apk.length) as ParcelFileDescriptor
|
||||||
|
).let { fd ->
|
||||||
|
val revocable = Class.forName("android.os.SystemProperties")
|
||||||
|
.getMethod("getBoolean", String::class.java, Boolean::class.java)
|
||||||
|
.invoke(null, "fw.revocable_fd", false) as Boolean
|
||||||
|
|
||||||
|
if (revocable) {
|
||||||
|
ParcelFileDescriptor.AutoCloseOutputStream(fd)
|
||||||
|
} else {
|
||||||
|
Class.forName($$"android.os.FileBridge$FileBridgeOutputStream")
|
||||||
|
.getConstructor(ParcelFileDescriptor::class.java)
|
||||||
|
.newInstance(fd) as OutputStream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.use { output ->
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
session::class.java.getMethod("commit", IntentSender::class.java)
|
||||||
|
.invoke(session, statusIntent.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ aboutlib_version = "13.1.0"
|
|||||||
leakcanary = "2.14"
|
leakcanary = "2.14"
|
||||||
moko = "0.25.1"
|
moko = "0.25.1"
|
||||||
okhttp_version = "5.3.0"
|
okhttp_version = "5.3.0"
|
||||||
shizuku_version = "13.1.0"
|
shizuku_version = "13.1.5"
|
||||||
sqldelight = "2.1.0"
|
sqldelight = "2.1.0"
|
||||||
sqlite = "2.6.1"
|
sqlite = "2.6.1"
|
||||||
voyager = "1.1.0-beta03"
|
voyager = "1.1.0-beta03"
|
||||||
|
|||||||
Reference in New Issue
Block a user