From b2ad0e70881316c3b0ccb9f5b3b86ee55742f3e7 Mon Sep 17 00:00:00 2001 From: Chumor <185651145+Chumor@users.noreply.github.com> Date: Sat, 19 Sep 2026 10:07:08 +0800 Subject: [PATCH 1/2] fix: deliver Termux RUN_COMMAND results via pending intent - Start RunCommandService as a foreground service from the background - Bind the result broadcast to the application context - Keep the result receiver alive until the plugin result bundle is applied --- .../zhousl/aether/termux/TermuxBashTool.kt | 16 ++++++++-- .../aether/termux/TermuxResultReceiver.kt | 29 +++++++++++-------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt b/app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt index e0fa62b3..7a91f11f 100644 --- a/app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt +++ b/app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt @@ -608,13 +608,14 @@ class TermuxBashTool( ): String { val executionId = TermuxPendingResults.nextExecutionId() val deferred = TermuxPendingResults.register(executionId) - val resultIntent = Intent(context, TermuxResultReceiver::class.java) + val appContext = context.applicationContext + val resultIntent = Intent(appContext, TermuxResultReceiver::class.java) .putExtra(TermuxContract.ExecutionIdExtra, executionId) val flags = PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0 val pendingIntent = PendingIntent.getBroadcast( - context, + appContext, executionId, resultIntent, flags, @@ -643,7 +644,7 @@ class TermuxBashTool( } return try { - val startResult = context.startService(request) + val startResult = startTermuxRunCommandService(request) if (startResult == null) { TermuxPendingResults.remove(executionId) logTermux( @@ -709,6 +710,15 @@ class TermuxBashTool( } } + private fun startTermuxRunCommandService(request: Intent): ComponentName? { + return try { + ContextCompat.startForegroundService(context, request) + } catch (throwable: Throwable) { + logTermux("foreground service start failed message=${throwable.message.orEmpty()}") + context.startService(request) + } + } + @Suppress("DEPRECATION") private fun isTermuxInstalled(): Boolean = runCatching { context.packageManager.getPackageInfo(TermuxContract.PackageName, 0) diff --git a/app/src/main/java/com/zhousl/aether/termux/TermuxResultReceiver.kt b/app/src/main/java/com/zhousl/aether/termux/TermuxResultReceiver.kt index 4173e4c0..b64f1215 100644 --- a/app/src/main/java/com/zhousl/aether/termux/TermuxResultReceiver.kt +++ b/app/src/main/java/com/zhousl/aether/termux/TermuxResultReceiver.kt @@ -10,21 +10,26 @@ class TermuxResultReceiver : BroadcastReceiver() { context: Context, intent: Intent, ) { - val executionId = intent.getIntExtra(TermuxContract.ExecutionIdExtra, -1) - if (executionId < 0) return + val pendingResult = goAsync() + try { + val executionId = intent.getIntExtra(TermuxContract.ExecutionIdExtra, -1) + if (executionId < 0) return - val resultBundle = intent.extras?.getBundle(TermuxContract.ResultBundleExtra) - ?: intent.extras?.findFirstBundle() + val resultBundle = intent.extras?.getBundle(TermuxContract.ResultBundleExtra) + ?: intent.extras?.findFirstBundle() - val result = TermuxCommandResult( - stdout = resultBundle?.getString(TermuxContract.ResultStdoutExtra).orEmpty(), - stderr = resultBundle?.getString(TermuxContract.ResultStderrExtra).orEmpty(), - exitCode = resultBundle?.getInt(TermuxContract.ResultExitCodeExtra, -1) ?: -1, - err = resultBundle?.getInt(TermuxContract.ResultErrExtra, -1) ?: -1, - errmsg = resultBundle?.getString(TermuxContract.ResultErrmsgExtra).orEmpty(), - ) + val result = TermuxCommandResult( + stdout = resultBundle?.getString(TermuxContract.ResultStdoutExtra).orEmpty(), + stderr = resultBundle?.getString(TermuxContract.ResultStderrExtra).orEmpty(), + exitCode = resultBundle?.getInt(TermuxContract.ResultExitCodeExtra, -1) ?: -1, + err = resultBundle?.getInt(TermuxContract.ResultErrExtra, -1) ?: -1, + errmsg = resultBundle?.getString(TermuxContract.ResultErrmsgExtra).orEmpty(), + ) - TermuxPendingResults.complete(executionId, result) + TermuxPendingResults.complete(executionId, result) + } finally { + pendingResult.finish() + } } } From b2e81d576584e997d4938c00fad74d28032a1eaf Mon Sep 17 00:00:00 2001 From: Chumor <185651145+Chumor@users.noreply.github.com> Date: Sat, 19 Sep 2026 11:03:35 +0800 Subject: [PATCH 2/2] fix: start Termux RunCommandService with Context.startForegroundService ContextCompat.startForegroundService() is void here, so the try/catch was inferred as Any? and failed Kotlin compile. --- app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt b/app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt index 7a91f11f..ce1eb692 100644 --- a/app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt +++ b/app/src/main/java/com/zhousl/aether/termux/TermuxBashTool.kt @@ -712,7 +712,7 @@ class TermuxBashTool( private fun startTermuxRunCommandService(request: Intent): ComponentName? { return try { - ContextCompat.startForegroundService(context, request) + context.startForegroundService(request) } catch (throwable: Throwable) { logTermux("foreground service start failed message=${throwable.message.orEmpty()}") context.startService(request)