Skip to content

Commit 29ad3ca

Browse files
authored
Use Shizuku User Service for performDexOptMode on Android 14+ (#72)
- Implement IShizukuService AIDL to run shell commands with shell UID. - Replace deprecated/restricted IPackageManager calls with 'cmd package compile'. - Transition ShizukuApi to use bindUserService instead of direct binder calls. - Add CompletableDeferred to handle the asynchronous nature of service binding, fixing the race condition where the first dexopt call would fail. - Update performDexOptMode to a suspend function for better concurrency.
1 parent cf361b2 commit 29ad3ca

5 files changed

Lines changed: 111 additions & 8 deletions

File tree

manager/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ android {
3939
}
4040

4141
buildFeatures {
42+
aidl = true
4243
compose = true
4344
buildConfig = true
4445
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.lsposed.lspatch;
2+
3+
interface IShizukuService {
4+
// Executes a shell command and returns the output
5+
String runShellCommand(String cmd) = 1;
6+
7+
// Allows closing the service from the client side
8+
void destroy() = 2;
9+
}

manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class LSPApplication : Application() {
2828
filesDir.mkdir()
2929
tmpApkDir = cacheDir.resolve("apk").also { it.mkdir() }
3030
prefs = lspApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
31-
ShizukuApi.init()
31+
ShizukuApi.init(this)
3232
AppBroadcastReceiver.register(this)
3333
globalScope.launch { LSPPackageManager.fetchAppList() }
3434
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.lsposed.lspatch
2+
3+
import org.lsposed.lspatch.IShizukuService
4+
import kotlin.system.exitProcess
5+
6+
class ShizukuService : IShizukuService.Stub() {
7+
override fun runShellCommand(cmd: String): String {
8+
return try {
9+
val process = Runtime.getRuntime().exec(cmd)
10+
val output = process.inputStream.bufferedReader().readText()
11+
val error = process.errorStream.bufferedReader().readText()
12+
process.waitFor()
13+
output + error
14+
} catch (e: Exception) {
15+
e.stackTraceToString()
16+
}
17+
}
18+
19+
override fun destroy() {
20+
exitProcess(0)
21+
}
22+
}

manager/src/main/java/org/lsposed/lspatch/util/ShizukuApi.kt

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package org.lsposed.lspatch.util
22

3+
import android.content.ComponentName
4+
import android.content.Context
35
import android.content.IntentSender
6+
import android.content.ServiceConnection
47
import android.content.pm.*
58
import android.os.Build
69
import android.os.IBinder
@@ -11,12 +14,35 @@ import androidx.compose.runtime.getValue
1114
import androidx.compose.runtime.mutableStateOf
1215
import androidx.compose.runtime.setValue
1316
import dev.rikka.tools.refine.Refine
17+
import kotlinx.coroutines.CompletableDeferred
18+
import kotlinx.coroutines.withTimeoutOrNull
19+
import org.lsposed.lspatch.IShizukuService
20+
import org.lsposed.lspatch.ShizukuService
1421
import rikka.shizuku.Shizuku
1522
import rikka.shizuku.ShizukuBinderWrapper
1623
import rikka.shizuku.SystemServiceHelper
1724

1825
object ShizukuApi {
1926

27+
@Volatile
28+
private var userService: IShizukuService? = null
29+
30+
// This allows us to "await" the service connection
31+
private var userServiceDeferred = CompletableDeferred<IShizukuService>()
32+
33+
private val userServiceConnection = object : ServiceConnection {
34+
override fun onServiceConnected(name: ComponentName, service: IBinder) {
35+
val binder = IShizukuService.Stub.asInterface(service)
36+
userService = binder
37+
userServiceDeferred.complete(binder)
38+
}
39+
40+
override fun onServiceDisconnected(name: ComponentName) {
41+
userService = null
42+
userServiceDeferred = CompletableDeferred()
43+
}
44+
}
45+
2046
private fun IBinder.wrap() = ShizukuBinderWrapper(this)
2147
private fun IInterface.asShizukuBinder() = this.asBinder().wrap()
2248

@@ -40,14 +66,34 @@ object ShizukuApi {
4066
var isBinderAvailable = false
4167
var isPermissionGranted by mutableStateOf(false)
4268

43-
fun init() {
69+
fun init(context: Context) {
4470
Shizuku.addBinderReceivedListenerSticky {
4571
isBinderAvailable = true
4672
isPermissionGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
73+
if (isPermissionGranted) {
74+
// Trigger the service binding as soon as we have permission
75+
bindUserService(context)
76+
}
4777
}
4878
Shizuku.addBinderDeadListener {
4979
isBinderAvailable = false
5080
isPermissionGranted = false
81+
userService = null
82+
userServiceDeferred = CompletableDeferred()
83+
}
84+
}
85+
86+
private fun bindUserService(context: Context) {
87+
if (userService != null) return
88+
val args = Shizuku.UserServiceArgs(ComponentName(context.packageName, ShizukuService::class.java.name))
89+
.daemon(false)
90+
.processNameSuffix("service")
91+
.debuggable(true)
92+
93+
try {
94+
Shizuku.bindUserService(args, userServiceConnection)
95+
} catch (e: Exception) {
96+
e.printStackTrace()
5197
}
5298
}
5399

@@ -71,11 +117,36 @@ object ShizukuApi {
71117
packageInstaller.uninstall(packageName, intentSender)
72118
}
73119

74-
fun performDexOptMode(packageName: String): Boolean {
75-
return iPackageManager.performDexOptMode(
76-
packageName,
77-
SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false),
78-
"verify", true, true, null
79-
)
120+
suspend fun performDexOptMode(packageName: String): Boolean {
121+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // API 34+
122+
// Wait up to 3 seconds for the service to connect if it hasn't yet
123+
val service = userService ?: withTimeoutOrNull(3000) {
124+
userServiceDeferred.await()
125+
} ?: return false
126+
127+
return try {
128+
val command = "cmd package compile -m speed-profile -f $packageName"
129+
val output = service.runShellCommand(command)
130+
// Return true if output contains "Success"
131+
output.contains("Success")
132+
} catch (e: Exception) {
133+
e.printStackTrace()
134+
false
135+
}
136+
} else {
137+
// Legacy reflection-based method for older versions
138+
return try {
139+
iPackageManager.performDexOptMode(
140+
packageName,
141+
SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false),
142+
"verify",
143+
true,
144+
true,
145+
null
146+
)
147+
} catch (e: Exception) {
148+
false
149+
}
150+
}
80151
}
81152
}

0 commit comments

Comments
 (0)