Skip to content

Commit

Permalink
Add the TermuxTask class for linking a Process to an ExecutionCommand.
Browse files Browse the repository at this point in the history
TermuxTask will maintain info for background Termux tasks. Each task started by TermuxService will now be linked to a ExecutionCommand that started it.

- StreamGobbler class has also been imported from https://github.com/Chainfire/libsuperuser and partially modified to read stdout and stderr of background commands. This should likely be much safer and efficient.
- Logging of every line has been disabled unless log level is set to verbose. This should have a performance increase and also prevent potentially private user data to be sent to logcat.
- This also solves the bug where Termux:Tasker would hang indefinitely if Runtime.getRuntime().exec raised an exception, like for invalid or missing interpreter errors and Termux:Tasker wasn't notified of it. Now the errmsg will be used to send any exceptions back to Termux:Tasker and other 3rd party calls.
- This also solves the bug where stdout or stderr were too large in size and TransactionTooLargeException exception was raised and result TERMUX_SERVICE.EXTRA_PENDING_INTENT pending intent failed to be sent to the caller. This would have also hung up Termux:Tasker. Now the stdout and stderr sent back in TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE bundle will be truncated from the start to max 100KB combined. The original size of stdout and stderr will be provided in TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT_ORIGINAL_LENGTH and TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_STDERR_ORIGINAL_LENGTH extras respectively so that the caller can check if either of them were truncated. The errmsg will also be truncated from end to max 25KB to preserve start of stacktraces.
- The PluginUtils.processPluginExecutionCommandResult() has been updated to fully handle the result of plugin execution intents.
  • Loading branch information
agnostic-apollo committed Mar 25, 2021
1 parent 7ca20fd commit f62febb
Show file tree
Hide file tree
Showing 4 changed files with 619 additions and 71 deletions.
100 changes: 75 additions & 25 deletions app/src/main/java/com/termux/app/TermuxService.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.termux.app.utils.TextDataUtils;
import com.termux.models.ExecutionCommand;
import com.termux.models.ExecutionCommand.ExecutionState;
import com.termux.app.terminal.TermuxTask;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSessionClient;
Expand Down Expand Up @@ -71,17 +72,17 @@ class LocalBinder extends Binder {
private final Handler mHandler = new Handler();

/**
* The termux sessions which this service manages.
* The foreground termux sessions which this service manages.
* Note that this list is observed by {@link TermuxActivity#mTermuxSessionListViewController},
* so any changes must be made on the UI thread and followed by a call to
* {@link ArrayAdapter#notifyDataSetChanged()} }.
*/
final List<TermuxSession> mTermuxSessions = new ArrayList<>();

/**
* The background jobs which this service manages.
* The background termux tasks which this service manages.
*/
final List<BackgroundJob> mBackgroundTasks = new ArrayList<>();
final List<TermuxTask> mTermuxTasks = new ArrayList<>();

/** The full implementation of the {@link TerminalSessionClient} interface to be used by {@link TerminalSession}
* that holds activity references for activity related functions.
Expand Down Expand Up @@ -204,15 +205,24 @@ private void requestStopService() {
stopSelf();
}



/** Process action to stop service. */
private void actionStopService() {
mWantsToStop = true;
finishAllTermuxSessions();
requestStopService();
}

/** Finish all termux sessions by sending SIGKILL to their shells. */
private synchronized void finishAllTermuxSessions() {
// TODO: Should SIGKILL also be send to background processes maintained by mTermuxTasks?
for (int i = 0; i < mTermuxSessions.size(); i++)
mTermuxSessions.get(i).getTerminalSession().finishIfRunning();
}





/** Process action to acquire Power and Wi-Fi WakeLocks. */
@SuppressLint({"WakelockTimeout", "BatteryLife"})
private void actionAcquireWakeLock() {
Expand Down Expand Up @@ -306,36 +316,72 @@ private void actionServiceExecute(Intent intent) {
executionCommand.pluginPendingIntent = intent.getParcelableExtra(TERMUX_SERVICE.EXTRA_PENDING_INTENT);

if (executionCommand.inBackground) {
executeBackgroundCommand(executionCommand);
executeTermuxTaskCommand(executionCommand);
} else {
executeTermuxSessionCommand(executionCommand);
}
}

/** Execute a shell command in background with {@link BackgroundJob}. */
private void executeBackgroundCommand(ExecutionCommand executionCommand) {




/** Execute a shell command in background {@link TermuxTask}. */
private void executeTermuxTaskCommand(ExecutionCommand executionCommand) {
if (executionCommand == null) return;

Logger.logDebug(LOG_TAG, "Starting background command");

Logger.logDebug(LOG_TAG, "Starting background termux task command");

TermuxTask newTermuxTask = createTermuxTask(executionCommand);
}

/** Create a {@link TermuxTask}. */
@Nullable
public TermuxTask createTermuxTask(String executablePath, String[] arguments, String workingDirectory) {
return createTermuxTask(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, true, false));
}

/** Create a {@link TermuxTask}. */
@Nullable
public synchronized TermuxTask createTermuxTask(ExecutionCommand executionCommand) {
if (executionCommand == null) return null;

Logger.logDebug(LOG_TAG, "Creating termux task");

if (!executionCommand.inBackground) {
Logger.logDebug(LOG_TAG, "Ignoring a foreground execution command passed to createTermuxTask()");
return null;
}

if(Logger.getLogLevel() >= Logger.LOG_LEVEL_VERBOSE)
Logger.logVerbose(LOG_TAG, executionCommand.toString());

BackgroundJob task = new BackgroundJob(executionCommand, this);
TermuxTask newTermuxTask = TermuxTask.create(this, executionCommand);
if (newTermuxTask == null) {
// Logger.logError(LOG_TAG, "Failed to execute new termux task command for:\n" + executionCommand.toString());
return null;
};

mTermuxTasks.add(newTermuxTask);

mBackgroundTasks.add(task);
updateNotification();

return newTermuxTask;
}

/** Callback received when a {@link BackgroundJob} finishes. */
public void onBackgroundJobExited(final BackgroundJob task) {
/** Callback received when a {@link TermuxTask} finishes. */
public synchronized void onTermuxTaskExited(final TermuxTask task) {
mHandler.post(() -> {
mBackgroundTasks.remove(task);
mTermuxTasks.remove(task);
updateNotification();
});
}

/** Execute a shell command in a foreground terminal session. */




/** Execute a shell command in a foreground {@link TermuxSession}. */
private void executeTermuxSessionCommand(ExecutionCommand executionCommand) {
if (executionCommand == null) return;

Expand All @@ -357,15 +403,15 @@ private void executeTermuxSessionCommand(ExecutionCommand executionCommand) {
}

/**
* Create a termux session.
* Create a {@link TermuxSession}.
* Currently called by {@link TermuxSessionClient#addNewSession(boolean, String)} to add a new termux session.
*/
@Nullable
public TermuxSession createTermuxSession(String executablePath, String[] arguments, String workingDirectory, boolean isFailSafe, String sessionName) {
return createTermuxSession(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, false, isFailSafe), sessionName);
}

/** Create a termux session. */
/** Create a {@link TermuxSession}. */
@Nullable
public synchronized TermuxSession createTermuxSession(ExecutionCommand executionCommand, String sessionName) {
if (executionCommand == null) return null;
Expand Down Expand Up @@ -428,11 +474,7 @@ public synchronized int removeTermuxSession(TerminalSession sessionToRemove) {
return index;
}

/** Finish all termux sessions by sending SIGKILL to their shells. */
private synchronized void finishAllTermuxSessions() {
for (int i = 0; i < mTermuxSessions.size(); i++)
mTermuxSessions.get(i).getTerminalSession().finishIfRunning();
}




Expand Down Expand Up @@ -473,6 +515,10 @@ private void startTermuxActivity() {
startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}





/** If {@link TermuxActivity} has not bound to the {@link TermuxService} yet or is destroyed, then
* interface functions requiring the activity should not be available to the terminal sessions,
* so we just return the {@link #mTermuxSessionClientBase}. Once {@link TermuxActivity} bind
Expand Down Expand Up @@ -519,6 +565,8 @@ public synchronized void unsetTermuxSessionClient() {





private Notification buildNotification() {
Intent notifyIntent = new Intent(this, TermuxActivity.class);
// PendingIntent#getActivity(): "Note that the activity will be started outside of the context of an existing
Expand All @@ -527,7 +575,7 @@ private Notification buildNotification() {
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);

int sessionCount = getTermuxSessionsSize();
int taskCount = mBackgroundTasks.size();
int taskCount = mTermuxTasks.size();
String contentText = sessionCount + " session" + (sessionCount == 1 ? "" : "s");
if (taskCount > 0) {
contentText += ", " + taskCount + " task" + (taskCount == 1 ? "" : "s");
Expand Down Expand Up @@ -587,7 +635,7 @@ private void setupNotificationChannel() {

/** Update the shown foreground service notification after making any changes that affect it. */
void updateNotification() {
if (mWakeLock == null && mTermuxSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
if (mWakeLock == null && mTermuxSessions.isEmpty() && mTermuxTasks.isEmpty()) {
// Exit if we are updating after the user disabled all locks with no sessions or tasks running.
requestStopService();
} else {
Expand All @@ -597,6 +645,8 @@ void updateNotification() {





private void setCurrentStoredTerminalSession(TerminalSession session) {
if(session == null) return;
// Make the newly created session the current one to be displayed:
Expand Down

0 comments on commit f62febb

Please sign in to comment.