33import android .app .Notification ;
44import android .app .NotificationChannel ;
55import android .app .NotificationManager ;
6+ import android .app .PendingIntent ;
67import android .app .Service ;
8+ import android .content .Context ;
79import android .content .Intent ;
810import android .os .Build ;
911import android .os .Bundle ;
1012import android .os .Handler ;
1113import android .os .IBinder ;
1214import android .os .Message ;
1315import android .os .Messenger ;
16+ import android .os .PowerManager ;
1417import android .os .RemoteException ;
1518import androidx .core .app .NotificationCompat ;
1619import com .foxdebug .acode .R ;
2225import java .util .Map ;
2326import java .util .concurrent .ConcurrentHashMap ;
2427import java .util .concurrent .Executors ;
28+ import java .lang .reflect .Field ;
29+
2530
2631public class TerminalService extends Service {
2732
@@ -32,19 +37,40 @@ public class TerminalService extends Service {
3237 public static final int MSG_EXEC = 5 ;
3338
3439 public static final String CHANNEL_ID = "terminal_exec_channel" ;
40+
41+ public static final String ACTION_EXIT_SERVICE = "com.foxdebug.acode.ACTION_EXIT_SERVICE" ;
42+ public static final String ACTION_TOGGLE_WAKE_LOCK = "com.foxdebug.acode.ACTION_TOGGLE_WAKE_LOCK" ;
3543
3644 private final Map <String , Process > processes = new ConcurrentHashMap <>();
3745 private final Map <String , OutputStream > processInputs = new ConcurrentHashMap <>();
3846 private final Map <String , Messenger > clientMessengers = new ConcurrentHashMap <>();
3947 private final java .util .concurrent .ExecutorService threadPool = Executors .newCachedThreadPool ();
4048
4149 private final Messenger serviceMessenger = new Messenger (new ServiceHandler ());
50+
51+ private PowerManager .WakeLock wakeLock ;
52+ private boolean isWakeLockHeld = false ;
4253
4354 @ Override
4455 public IBinder onBind (Intent intent ) {
4556 return serviceMessenger .getBinder ();
4657 }
4758
59+ @ Override
60+ public int onStartCommand (Intent intent , int flags , int startId ) {
61+ if (intent != null ) {
62+ String action = intent .getAction ();
63+ if (ACTION_EXIT_SERVICE .equals (action )) {
64+ stopForeground (true );
65+ stopSelf ();
66+ return START_NOT_STICKY ;
67+ } else if (ACTION_TOGGLE_WAKE_LOCK .equals (action )) {
68+ toggleWakeLock ();
69+ }
70+ }
71+ return START_STICKY ;
72+ }
73+
4874 private class ServiceHandler extends Handler {
4975 @ Override
5076 public void handleMessage (Message msg ) {
@@ -79,13 +105,40 @@ public void handleMessage(Message msg) {
79105 }
80106 }
81107
108+ private void toggleWakeLock () {
109+ if (isWakeLockHeld ) {
110+ releaseWakeLock ();
111+ } else {
112+ acquireWakeLock ();
113+ }
114+ updateNotification ();
115+ }
116+
117+ private void acquireWakeLock () {
118+ if (wakeLock == null ) {
119+ PowerManager powerManager = (PowerManager ) getSystemService (Context .POWER_SERVICE );
120+ wakeLock = powerManager .newWakeLock (PowerManager .PARTIAL_WAKE_LOCK , "AcodeTerminal:WakeLock" );
121+ }
122+
123+ if (!isWakeLockHeld ) {
124+ wakeLock .acquire ();
125+ isWakeLockHeld = true ;
126+ }
127+ }
128+
129+ private void releaseWakeLock () {
130+ if (wakeLock != null && isWakeLockHeld ) {
131+ wakeLock .release ();
132+ isWakeLockHeld = false ;
133+ }
134+ }
135+
82136 private void startProcess (String pid , String cmd , String alpine ) {
83137 threadPool .execute (() -> {
84138 try {
85139 String xcmd = alpine .equals ("true" ) ? "source $PREFIX/init-sandbox.sh " + cmd : cmd ;
86140 ProcessBuilder builder = new ProcessBuilder ("sh" , "-c" , xcmd );
87141
88- // Set environment variables
89142 Map <String , String > env = builder .environment ();
90143 env .put ("PREFIX" , getFilesDir ().getAbsolutePath ());
91144 env .put ("NATIVE_DIR" , getApplicationInfo ().nativeLibraryDir );
@@ -100,14 +153,8 @@ private void startProcess(String pid, String cmd, String alpine) {
100153 Process process = builder .start ();
101154 processes .put (pid , process );
102155 processInputs .put (pid , process .getOutputStream ());
103-
104- // Stream stdout
105156 threadPool .execute (() -> streamOutput (process .getInputStream (), pid , "stdout" ));
106-
107- // Stream stderr
108157 threadPool .execute (() -> streamOutput (process .getErrorStream (), pid , "stderr" ));
109-
110- // Wait for process to complete
111158 threadPool .execute (() -> {
112159 try {
113160 int exitCode = process .waitFor ();
@@ -131,8 +178,6 @@ private void exec(String execId, String cmd, String alpine) {
131178 try {
132179 String xcmd = alpine .equals ("true" ) ? "source $PREFIX/init-sandbox.sh " + cmd : cmd ;
133180 ProcessBuilder builder = new ProcessBuilder ("sh" , "-c" , xcmd );
134-
135- // Set environment variables
136181 Map <String , String > env = builder .environment ();
137182 env .put ("PREFIX" , getFilesDir ().getAbsolutePath ());
138183 env .put ("NATIVE_DIR" , getApplicationInfo ().nativeLibraryDir );
@@ -145,8 +190,6 @@ private void exec(String execId, String cmd, String alpine) {
145190 }
146191
147192 Process process = builder .start ();
148-
149- // Capture stdout
150193 BufferedReader stdOutReader = new BufferedReader (
151194 new InputStreamReader (process .getInputStream ()));
152195 StringBuilder stdOut = new StringBuilder ();
@@ -155,7 +198,6 @@ private void exec(String execId, String cmd, String alpine) {
155198 stdOut .append (line ).append ("\n " );
156199 }
157200
158- // Capture stderr
159201 BufferedReader stdErrReader = new BufferedReader (
160202 new InputStreamReader (process .getErrorStream ()));
161203 StringBuilder stdErr = new StringBuilder ();
@@ -205,7 +247,6 @@ private void sendMessageToClient(String id, String action, String data) {
205247 msg .setData (bundle );
206248 clientMessenger .send (msg );
207249 } catch (RemoteException e ) {
208- // Client is no longer available, clean up
209250 cleanup (id );
210251 }
211252 }
@@ -224,7 +265,6 @@ private void sendExecResultToClient(String id, boolean isSuccess, String data) {
224265 msg .setData (bundle );
225266 clientMessenger .send (msg );
226267 } catch (RemoteException e ) {
227- // Client is no longer available, clean up
228268 cleanup (id );
229269 }
230270 }
@@ -242,9 +282,23 @@ private void writeToProcess(String pid, String input) {
242282 }
243283 }
244284
285+ private long getPid (Process process ) {
286+ try {
287+ Field f = process .getClass ().getDeclaredField ("pid" );
288+ f .setAccessible (true );
289+ return f .getLong (process );
290+ } catch (Exception e ) {
291+ return -1 ;
292+ }
293+ }
294+
295+
245296 private void stopProcess (String pid ) {
246297 Process process = processes .get (pid );
247298 if (process != null ) {
299+ try {
300+ Runtime .getRuntime ().exec ("kill -9 -" + getPid (process ));
301+ } catch (Exception ignored ) {}
248302 process .destroy ();
249303 cleanup (pid );
250304 }
@@ -275,13 +329,7 @@ private void cleanup(String id) {
275329 public void onCreate () {
276330 super .onCreate ();
277331 createNotificationChannel ();
278- Notification notification = new NotificationCompat .Builder (this , CHANNEL_ID )
279- .setContentTitle ("Acode Service" )
280- .setContentText ("Executor service" )
281- .setSmallIcon (R .drawable .ic_launcher_foreground )
282- .setOngoing (true )
283- .build ();
284- startForeground (1 , notification );
332+ updateNotification ();
285333 }
286334
287335 private void createNotificationChannel () {
@@ -298,13 +346,44 @@ private void createNotificationChannel() {
298346 }
299347 }
300348
349+ private void updateNotification () {
350+ Intent exitIntent = new Intent (this , TerminalService .class );
351+ exitIntent .setAction (ACTION_EXIT_SERVICE );
352+ PendingIntent exitPendingIntent = PendingIntent .getService (this , 0 , exitIntent ,
353+ PendingIntent .FLAG_UPDATE_CURRENT | PendingIntent .FLAG_IMMUTABLE );
354+
355+ Intent wakeLockIntent = new Intent (this , TerminalService .class );
356+ wakeLockIntent .setAction (ACTION_TOGGLE_WAKE_LOCK );
357+ PendingIntent wakeLockPendingIntent = PendingIntent .getService (this , 1 , wakeLockIntent ,
358+ PendingIntent .FLAG_UPDATE_CURRENT | PendingIntent .FLAG_IMMUTABLE );
359+
360+ String contentText = "Executor service" + (isWakeLockHeld ? " (wakelock held)" : "" );
361+ String wakeLockButtonText = isWakeLockHeld ? "Release Wake Lock" : "Acquire Wake Lock" ;
362+
363+ Notification notification = new NotificationCompat .Builder (this , CHANNEL_ID )
364+ .setContentTitle ("Acode Service" )
365+ .setContentText (contentText )
366+ .setSmallIcon (R .drawable .ic_launcher_foreground )
367+ .setOngoing (true )
368+ .addAction (R .drawable .ic_launcher_foreground , wakeLockButtonText , wakeLockPendingIntent )
369+ .addAction (R .drawable .ic_launcher_foreground , "Exit" , exitPendingIntent )
370+ .build ();
371+
372+ startForeground (1 , notification );
373+ }
374+
301375 @ Override
302376 public void onDestroy () {
303377 super .onDestroy ();
304- // Clean up all processes when service is destroyed
378+ releaseWakeLock ();
379+
305380 for (Process process : processes .values ()) {
306- process .destroy ();
381+ try {
382+ Runtime .getRuntime ().exec ("kill -9 -" + getPid (process ));
383+ } catch (Exception ignored ) {}
384+ process .destroyForcibly ();
307385 }
386+
308387 processes .clear ();
309388 processInputs .clear ();
310389 clientMessengers .clear ();
0 commit comments