11package 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
38import 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
712class 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