Skip to content

Commit 4b1a6ba

Browse files
feat: move service to background (#1754)
* feat: move service to background * feat: move to foreground * feat: added backgroudExecutor * feat: add missing source files * feat: add BackgroundExecutor as plugin * feat: use background executor for Terminal.isAxsRunning()
1 parent f302451 commit 4b1a6ba

File tree

12 files changed

+543
-209
lines changed

12 files changed

+543
-209
lines changed

package-lock.json

Lines changed: 5 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
"cordova-plugin-browser": {},
3838
"cordova-plugin-sftp": {},
3939
"cordova-plugin-system": {},
40-
"com.foxdebug.acode.rk.exec.terminal": {},
41-
"com.foxdebug.acode.rk.exec.proot": {}
40+
"com.foxdebug.acode.rk.exec.proot": {},
41+
"com.foxdebug.acode.rk.exec.terminal": {}
4242
},
4343
"platforms": [
4444
"android"
@@ -129,4 +129,4 @@
129129
"yargs": "^18.0.0"
130130
},
131131
"browserslist": "cover 100%,not android < 5"
132-
}
132+
}

src/plugins/terminal/plugin.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,19 @@
1616
<feature name="Executor">
1717
<param name="android-package" value="com.foxdebug.acode.rk.exec.terminal.Executor" />
1818
</feature>
19+
<feature name="BackgroundExecutor">
20+
<param name="android-package" value="com.foxdebug.acode.rk.exec.terminal.BackgroundExecutor" />
21+
</feature>
1922
</config-file>
2023
<config-file parent="/*" target="AndroidManifest.xml" />
2124

25+
<source-file src="src/android/BackgroundExecutor.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
26+
<source-file src="src/android/ProcessManager.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
27+
<source-file src="src/android/ProcessUtils.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
28+
<source-file src="src/android/StreamHandler.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
29+
2230
<source-file src="src/android/Executor.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
31+
2332
<source-file src="src/android/TerminalService.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
2433

2534
<source-file src="src/android/AlpineDocumentProvider.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />

src/plugins/terminal/src/android/AlpineDocumentProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.LinkedList;
2121
import java.util.Locale;
2222
import com.foxdebug.acode.R;
23+
import com.foxdebug.acode.rk.exec.terminal.*;
2324

2425
public class AlpineDocumentProvider extends DocumentsProvider {
2526

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package com.foxdebug.acode.rk.exec.terminal;
2+
3+
import org.apache.cordova.*;
4+
import org.json.*;
5+
import java.io.*;
6+
import java.util.*;
7+
import java.util.concurrent.*;
8+
import com.foxdebug.acode.rk.exec.terminal.*;
9+
10+
public class BackgroundExecutor extends CordovaPlugin {
11+
12+
private final Map<String, Process> processes = new ConcurrentHashMap<>();
13+
private final Map<String, OutputStream> processInputs = new ConcurrentHashMap<>();
14+
private final Map<String, CallbackContext> processCallbacks = new ConcurrentHashMap<>();
15+
private ProcessManager processManager;
16+
17+
@Override
18+
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
19+
super.initialize(cordova, webView);
20+
this.processManager = new ProcessManager(cordova.getContext());
21+
}
22+
23+
@Override
24+
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
25+
switch (action) {
26+
case "start":
27+
String pid = UUID.randomUUID().toString();
28+
startProcess(pid, args.getString(0), args.getString(1).equals("true"), callbackContext);
29+
return true;
30+
case "write":
31+
writeToProcess(args.getString(0), args.getString(1), callbackContext);
32+
return true;
33+
case "stop":
34+
stopProcess(args.getString(0), callbackContext);
35+
return true;
36+
case "exec":
37+
exec(args.getString(0), args.getString(1).equals("true"), callbackContext);
38+
return true;
39+
case "isRunning":
40+
isProcessRunning(args.getString(0), callbackContext);
41+
return true;
42+
case "loadLibrary":
43+
loadLibrary(args.getString(0), callbackContext);
44+
return true;
45+
default:
46+
callbackContext.error("Unknown action: " + action);
47+
return false;
48+
}
49+
}
50+
51+
private void exec(String cmd, boolean useAlpine, CallbackContext callbackContext) {
52+
cordova.getThreadPool().execute(() -> {
53+
try {
54+
ProcessManager.ExecResult result = processManager.executeCommand(cmd, useAlpine);
55+
56+
if (result.isSuccess()) {
57+
callbackContext.success(result.stdout);
58+
} else {
59+
callbackContext.error(result.getErrorMessage());
60+
}
61+
} catch (Exception e) {
62+
callbackContext.error("Exception: " + e.getMessage());
63+
}
64+
});
65+
}
66+
67+
private void startProcess(String pid, String cmd, boolean useAlpine, CallbackContext callbackContext) {
68+
cordova.getThreadPool().execute(() -> {
69+
try {
70+
ProcessBuilder builder = processManager.createProcessBuilder(cmd, useAlpine);
71+
Process process = builder.start();
72+
73+
processes.put(pid, process);
74+
processInputs.put(pid, process.getOutputStream());
75+
processCallbacks.put(pid, callbackContext);
76+
77+
sendPluginResult(callbackContext, pid, true);
78+
79+
// Stream stdout
80+
new Thread(() -> StreamHandler.streamOutput(
81+
process.getInputStream(),
82+
line -> sendPluginMessage(pid, "stdout:" + line)
83+
)).start();
84+
85+
// Stream stderr
86+
new Thread(() -> StreamHandler.streamOutput(
87+
process.getErrorStream(),
88+
line -> sendPluginMessage(pid, "stderr:" + line)
89+
)).start();
90+
91+
int exitCode = process.waitFor();
92+
sendPluginMessage(pid, "exit:" + exitCode);
93+
cleanup(pid);
94+
} catch (Exception e) {
95+
callbackContext.error("Failed to start process: " + e.getMessage());
96+
}
97+
});
98+
}
99+
100+
private void writeToProcess(String pid, String input, CallbackContext callbackContext) {
101+
try {
102+
OutputStream os = processInputs.get(pid);
103+
if (os != null) {
104+
StreamHandler.writeToStream(os, input);
105+
callbackContext.success("Written to process");
106+
} else {
107+
callbackContext.error("Process not found or closed");
108+
}
109+
} catch (IOException e) {
110+
callbackContext.error("Write error: " + e.getMessage());
111+
}
112+
}
113+
114+
private void stopProcess(String pid, CallbackContext callbackContext) {
115+
Process process = processes.get(pid);
116+
if (process != null) {
117+
ProcessUtils.killProcessTree(process);
118+
cleanup(pid);
119+
callbackContext.success("Process terminated");
120+
} else {
121+
callbackContext.error("No such process");
122+
}
123+
}
124+
125+
private void isProcessRunning(String pid, CallbackContext callbackContext) {
126+
Process process = processes.get(pid);
127+
128+
if (process != null) {
129+
String status = ProcessUtils.isAlive(process) ? "running" : "exited";
130+
if (status.equals("exited")) cleanup(pid);
131+
callbackContext.success(status);
132+
} else {
133+
callbackContext.success("not_found");
134+
}
135+
}
136+
137+
private void loadLibrary(String path, CallbackContext callbackContext) {
138+
try {
139+
System.load(path);
140+
callbackContext.success("Library loaded successfully.");
141+
} catch (Exception e) {
142+
callbackContext.error("Failed to load library: " + e.getMessage());
143+
}
144+
}
145+
146+
private void sendPluginResult(CallbackContext ctx, String message, boolean keepCallback) {
147+
PluginResult result = new PluginResult(PluginResult.Status.OK, message);
148+
result.setKeepCallback(keepCallback);
149+
ctx.sendPluginResult(result);
150+
}
151+
152+
private void sendPluginMessage(String pid, String message) {
153+
CallbackContext ctx = processCallbacks.get(pid);
154+
if (ctx != null) {
155+
sendPluginResult(ctx, message, true);
156+
}
157+
}
158+
159+
private void cleanup(String pid) {
160+
processes.remove(pid);
161+
processInputs.remove(pid);
162+
processCallbacks.remove(pid);
163+
}
164+
}

src/plugins/terminal/src/android/Executor.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import androidx.core.app.ActivityCompat;
2828
import androidx.core.content.ContextCompat;
2929
import android.app.Activity;
30+
import com.foxdebug.acode.rk.exec.terminal.*;
3031

3132
public class Executor extends CordovaPlugin {
3233

@@ -235,6 +236,22 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
235236
return true;
236237
}
237238

239+
if (action.equals("moveToBackground")) {
240+
Intent intent = new Intent(context, TerminalService.class);
241+
intent.setAction(TerminalService.MOVE_TO_BACKGROUND);
242+
context.startService(intent);
243+
callbackContext.success("Service moved to background mode");
244+
return true;
245+
}
246+
247+
if (action.equals("moveToForeground")) {
248+
Intent intent = new Intent(context, TerminalService.class);
249+
intent.setAction(TerminalService.MOVE_TO_FOREGROUND);
250+
context.startService(intent);
251+
callbackContext.success("Service moved to foreground mode");
252+
return true;
253+
}
254+
238255
// For all other actions, ensure service is bound first
239256
if (!ensureServiceBound(callbackContext)) {
240257
// Error already sent by ensureServiceBound

0 commit comments

Comments
 (0)