Skip to content

Commit ae0c983

Browse files
committed
fix: implement proper Shizuku UserService for shell execution
1 parent bdced08 commit ae0c983

File tree

2 files changed

+96
-21
lines changed

2 files changed

+96
-21
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.appcontrolx.executor
2+
3+
import com.appcontrolx.IShellService
4+
import java.io.BufferedReader
5+
6+
class ShellService : IShellService.Stub() {
7+
8+
override fun exec(command: String): String {
9+
return try {
10+
val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", command))
11+
val output = process.inputStream.bufferedReader().use(BufferedReader::readText)
12+
val error = process.errorStream.bufferedReader().use(BufferedReader::readText)
13+
val exitCode = process.waitFor()
14+
15+
if (exitCode == 0) {
16+
output.trim()
17+
} else {
18+
val errorMsg = error.ifBlank { output }.trim()
19+
"ERROR:${errorMsg.ifBlank { "Exit code $exitCode" }}"
20+
}
21+
} catch (e: Exception) {
22+
"ERROR:${e.message}"
23+
}
24+
}
25+
26+
override fun execReturnCode(command: String): Int {
27+
return try {
28+
val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", command))
29+
process.waitFor()
30+
} catch (e: Exception) {
31+
-1
32+
}
33+
}
34+
}

app/src/main/java/com/appcontrolx/executor/ShizukuExecutor.kt

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,75 @@
11
package com.appcontrolx.executor
22

3+
import android.content.ComponentName
4+
import android.content.ServiceConnection
5+
import android.os.IBinder
6+
import com.appcontrolx.BuildConfig
7+
import com.appcontrolx.IShellService
38
import rikka.shizuku.Shizuku
4-
import java.io.BufferedReader
5-
import java.io.DataOutputStream
9+
import java.util.concurrent.CountDownLatch
10+
import java.util.concurrent.TimeUnit
611

712
class ShizukuExecutor : CommandExecutor {
813

14+
private var shellService: IShellService? = null
15+
private val serviceLatch = CountDownLatch(1)
16+
17+
private val userServiceArgs = Shizuku.UserServiceArgs(
18+
ComponentName(BuildConfig.APPLICATION_ID, ShellService::class.java.name)
19+
)
20+
.daemon(false)
21+
.processNameSuffix("shell")
22+
.debuggable(BuildConfig.DEBUG)
23+
.version(BuildConfig.VERSION_CODE)
24+
25+
private val serviceConnection = object : ServiceConnection {
26+
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
27+
shellService = IShellService.Stub.asInterface(service)
28+
serviceLatch.countDown()
29+
}
30+
31+
override fun onServiceDisconnected(name: ComponentName?) {
32+
shellService = null
33+
}
34+
}
35+
36+
init {
37+
bindService()
38+
}
39+
40+
private fun bindService() {
41+
if (!Shizuku.pingBinder()) return
42+
try {
43+
Shizuku.bindUserService(userServiceArgs, serviceConnection)
44+
} catch (e: Exception) {
45+
// Shizuku not ready
46+
}
47+
}
48+
49+
fun unbindService() {
50+
try {
51+
Shizuku.unbindUserService(userServiceArgs, serviceConnection, true)
52+
} catch (e: Exception) {
53+
// Ignore
54+
}
55+
shellService = null
56+
}
57+
958
override fun execute(command: String): Result<String> {
10-
if (!Shizuku.pingBinder()) {
11-
return Result.failure(Exception("Shizuku not available"))
59+
// Wait for service to connect (max 3 seconds)
60+
if (shellService == null) {
61+
serviceLatch.await(3, TimeUnit.SECONDS)
1262
}
1363

64+
val service = shellService
65+
?: return Result.failure(Exception("Shizuku service not available"))
66+
1467
return try {
15-
// Use Shizuku's remote process to execute shell commands
16-
val process = Shizuku.newProcess(arrayOf("sh", "-c", command), null, null)
17-
18-
val output = process.inputStream.bufferedReader().use(BufferedReader::readText)
19-
val error = process.errorStream.bufferedReader().use(BufferedReader::readText)
20-
val exitCode = process.waitFor()
21-
22-
if (exitCode == 0) {
23-
Result.success(output.trim())
68+
val output = service.exec(command)
69+
if (output.startsWith("ERROR:")) {
70+
Result.failure(Exception(output.removePrefix("ERROR:")))
2471
} else {
25-
val errorMsg = error.ifBlank { output }.trim()
26-
if (errorMsg.isNotBlank()) {
27-
Result.failure(Exception(errorMsg))
28-
} else {
29-
Result.failure(Exception("Command failed with exit code $exitCode"))
30-
}
72+
Result.success(output)
3173
}
3274
} catch (e: Exception) {
3375
Result.failure(e)
@@ -36,8 +78,7 @@ class ShizukuExecutor : CommandExecutor {
3678

3779
override fun executeBatch(commands: List<String>): Result<Unit> {
3880
for (cmd in commands) {
39-
val result = execute(cmd)
40-
// Continue even if some commands fail (best effort)
81+
execute(cmd) // Best effort, continue on failure
4182
}
4283
return Result.success(Unit)
4384
}

0 commit comments

Comments
 (0)