Improve TermuxSession and TermuxTask

TermuxSession, TermuxTask and TermuxSessionClientBase have been moved to termux-shared. They are now not dependent on TermuxService anymore and have become abstract so that they can be called from anywhere. TermuxSession.TermuxSessionClient and TermuxTask.TermuxTaskClient interfaces have been created for callbacks, which the TermuxService now implements.

The TermuxTask now also supports synchronous command execution as well to run shell commands from anywhere for internal use by termux app and its plugins.

TermuxTask now also supports killing the process being run by sending it a SIGKILL. This is used by TermuxService to kill all the TermuxTasks (in addition to TermuxSessions) it manages when it is destroyed, either by user exiting it or by android killing it. Only the tasks that were started by a plugin which **expects** the result back via a pending intent will be killed, but the remaining background tasks will keep on running until the termux app process is killed by android, like by OOM. Check TermuxService.killAllTermuxExecutionCommands() for more details on how TermuxService kills TermuxTasks and TermuxSessions.

Fixed null pointer exception when getting terminal transcript if TerminalEmulator not initialized.
This commit is contained in:
agnostic-apollo
2021-04-12 14:26:53 +05:00
parent df4d8ac7e5
commit 0cd7cb8162
17 changed files with 747 additions and 330 deletions

View File

@@ -2,11 +2,16 @@ package com.termux.shared.shell;
import android.content.Context;
import androidx.annotation.NonNull;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.file.FileUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.packages.PackageUtils;
import com.termux.shared.termux.TermuxUtils;
import com.termux.terminal.TerminalBuffer;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import java.io.File;
import java.io.FileInputStream;
@@ -85,7 +90,7 @@ public class ShellUtils {
}
}
public static String[] setupProcessArgs(String fileToExecute, String[] arguments) {
public static String[] setupProcessArgs(@NonNull String fileToExecute, String[] arguments) {
// The file to execute may either be:
// - An elf file, in which we execute it directly.
// - A script file without shebang, which we execute with our standard shell $PREFIX/bin/sh instead of the
@@ -153,4 +158,28 @@ public class ShellUtils {
}
}
public static String getTerminalSessionTranscriptText(TerminalSession terminalSession, boolean linesJoined, boolean trim) {
if (terminalSession == null) return null;
TerminalEmulator terminalEmulator = terminalSession.getEmulator();
if (terminalEmulator == null) return null;
TerminalBuffer terminalBuffer = terminalEmulator.getScreen();
if (terminalBuffer == null) return null;
String transcriptText;
if(linesJoined)
transcriptText = terminalBuffer.getTranscriptTextWithFullLinesJoined();
else
transcriptText = terminalBuffer.getTranscriptTextWithoutJoinedLines();
if (transcriptText == null) return null;
if(trim)
transcriptText = transcriptText.trim();
return transcriptText;
}
}

View File

@@ -32,6 +32,9 @@ import com.termux.shared.logger.Logger;
/**
* Thread utility class continuously reading from an InputStream
*
* https://github.com/Chainfire/libsuperuser/blob/1.1.0.201907261845/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java#L141
* https://github.com/Chainfire/libsuperuser/blob/1.1.0.201907261845/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java
*/
@SuppressWarnings({"WeakerAccess"})
public class StreamGobbler extends Thread {
@@ -170,6 +173,7 @@ public class StreamGobbler extends Thread {
// optionally pausing when a command is executed that consumes the InputStream itself
int currentLogLevel = Logger.getLogLevel();
int logLevelVerbose = Logger.LOG_LEVEL_VERBOSE;
try {
String line;
while ((line = reader.readLine()) != null) {

View File

@@ -0,0 +1,258 @@
package com.termux.shared.shell;
import android.content.Context;
import android.system.OsConstants;
import androidx.annotation.NonNull;
import com.termux.shared.R;
import com.termux.shared.models.ExecutionCommand;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.logger.Logger;
import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSessionClient;
import java.io.File;
/**
* A class that maintains info for foreground Termux sessions.
* It also provides a way to link each {@link TerminalSession} with the {@link ExecutionCommand}
* that started it.
*/
public class TermuxSession {
private final TerminalSession mTerminalSession;
private final ExecutionCommand mExecutionCommand;
private final TermuxSessionClient mTermuxSessionClient;
private final boolean mSetStdoutOnExit;
private static final String LOG_TAG = "TermuxSession";
private TermuxSession(@NonNull final TerminalSession terminalSession, @NonNull final ExecutionCommand executionCommand,
final TermuxSessionClient termuxSessionClient, final boolean setStdoutOnExit) {
this.mTerminalSession = terminalSession;
this.mExecutionCommand = executionCommand;
this.mTermuxSessionClient = termuxSessionClient;
this.mSetStdoutOnExit = setStdoutOnExit;
}
/**
* Start execution of an {@link ExecutionCommand} with {@link Runtime#exec(String[], String[], File)}.
*
* The {@link ExecutionCommand#executable}, must be set, {@link ExecutionCommand#commandLabel},
* {@link ExecutionCommand#arguments} and {@link ExecutionCommand#workingDirectory} may optionally
* be set.
*
* If {@link ExecutionCommand#executable} is {@code null}, then a default shell is automatically
* chosen.
*
* @param context The {@link Context} for operations.
* @param executionCommand The {@link ExecutionCommand} containing the information for execution command.
* @param terminalSessionClient The {@link TerminalSessionClient} interface implementation.
* @param termuxSessionClient The {@link TermuxSessionClient} interface implementation.
* @param sessionName The optional {@link TerminalSession} name.
* @param setStdoutOnExit If set to {@code true}, then the {@link ExecutionCommand#stdout}
* available in the {@link TermuxSessionClient#onTermuxSessionExited(TermuxSession)}
* callback will be set to the {@link TerminalSession} transcript. The session
* transcript will contain both stdout and stderr combined, basically
* anything sent to the the pseudo terminal /dev/pts, including PS1 prefixes.
* Set this to {@code true} only if the session transcript is required,
* since this requires extra processing to get it.
* @return Returns the {@link TermuxSession}. This will be {@code null} if failed to start the execution command.
*/
public static TermuxSession execute(@NonNull final Context context, @NonNull ExecutionCommand executionCommand,
@NonNull final TerminalSessionClient terminalSessionClient, final TermuxSessionClient termuxSessionClient,
final String sessionName, final boolean setStdoutOnExit) {
if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty()) executionCommand.workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH;
String[] environment = ShellUtils.buildEnvironment(context, executionCommand.isFailsafe, executionCommand.workingDirectory);
boolean isLoginShell = false;
if (executionCommand.executable == null) {
if (!executionCommand.isFailsafe) {
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
File shellFile = new File(TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH, shellBinary);
if (shellFile.canExecute()) {
executionCommand.executable = shellFile.getAbsolutePath();
break;
}
}
}
if (executionCommand.executable == null) {
// Fall back to system shell as last resort:
executionCommand.executable = "/system/bin/sh";
}
isLoginShell = true;
}
String[] processArgs = ShellUtils.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
executionCommand.executable = processArgs[0];
String processName = (isLoginShell ? "-" : "") + ShellUtils.getExecutableBasename(executionCommand.executable);
String[] arguments = new String[processArgs.length];
arguments[0] = processName;
if (processArgs.length > 1) System.arraycopy(processArgs, 1, arguments, 1, processArgs.length - 1);
executionCommand.arguments = arguments;
if (executionCommand.commandLabel == null)
executionCommand.commandLabel = processName;
if (!executionCommand.setState(ExecutionCommand.ExecutionState.EXECUTING)) {
executionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, context.getString(R.string.error_failed_to_execute_termux_session_command, executionCommand.getCommandIdAndLabelLogString()), null);
TermuxSession.processTermuxSessionResult(null, executionCommand);
return null;
}
Logger.logDebug(LOG_TAG, executionCommand.toString());
Logger.logDebug(LOG_TAG, "Running \"" + executionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession");
TerminalSession terminalSession = new TerminalSession(executionCommand.executable, executionCommand.workingDirectory, executionCommand.arguments, environment, terminalSessionClient);
if (sessionName != null) {
terminalSession.mSessionName = sessionName;
}
return new TermuxSession(terminalSession, executionCommand, termuxSessionClient, setStdoutOnExit);
}
/**
* Signal that this {@link TermuxSession} has finished. This should be called when
* {@link TerminalSessionClient#onSessionFinished(TerminalSession)} callback is received by the caller.
*
* If the processes has finished, then sets {@link ExecutionCommand#stdout}, {@link ExecutionCommand#stderr}
* and {@link ExecutionCommand#exitCode} for the {@link #mExecutionCommand} of the {@code termuxTask}
* and then calls {@link #processTermuxSessionResult(TermuxSession, ExecutionCommand)} to process the result}.
*
*/
public void finish() {
// If process is still running, then ignore the call
if (mTerminalSession.isRunning()) return;
int exitCode = mTerminalSession.getExitStatus();
if (exitCode == 0)
Logger.logDebug(LOG_TAG, "The \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession with exited normally");
else
Logger.logDebug(LOG_TAG, "The \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession with exited with code: " + exitCode);
// If the execution command has already failed, like SIGKILL was sent, then don't continue
if (mExecutionCommand.isStateFailed()) {
Logger.logDebug(LOG_TAG, "Ignoring setting \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession state to ExecutionState.EXECUTED and processing results since it has already failed");
return;
}
if (this.mSetStdoutOnExit)
mExecutionCommand.stdout = ShellUtils.getTerminalSessionTranscriptText(mTerminalSession, true, false);
else
mExecutionCommand.stdout = null;
mExecutionCommand.stderr = null;
mExecutionCommand.exitCode = exitCode;
if (!mExecutionCommand.setState(ExecutionCommand.ExecutionState.EXECUTED))
return;
TermuxSession.processTermuxSessionResult(this, null);
}
/**
* Kill this {@link TermuxSession} by sending a {@link OsConstants#SIGILL} to its {@link #mTerminalSession}
* if its still executing.
*
* We process the results even if
*
* @param context The {@link Context} for operations.
* @param processResult If set to {@code true}, then the {@link #processTermuxSessionResult(TermuxSession, ExecutionCommand)}
* will be called to process the failure.
*/
public void killIfExecuting(@NonNull final Context context, boolean processResult) {
// If execution command has already finished executing, then no need to process results or send SIGKILL
if(mExecutionCommand.hasExecuted()) {
Logger.logDebug(LOG_TAG, "Ignoring sending SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession since it has already finished executing");
return;
}
Logger.logDebug(LOG_TAG, "Send SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession");
if (mExecutionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, context.getString(R.string.error_sending_sigkill_to_process), null)) {
if (processResult) {
// Get whatever output has been set till now in case its needed
if (this.mSetStdoutOnExit)
mExecutionCommand.stdout = ShellUtils.getTerminalSessionTranscriptText(mTerminalSession, true, false);
else
mExecutionCommand.stdout = null;
mExecutionCommand.stderr = null;
mExecutionCommand.exitCode = 137; // SIGKILL
TermuxSession.processTermuxSessionResult(this, null);
}
}
// Send SIGKILL to process
mTerminalSession.finishIfRunning();
}
/**
* Process the results of {@link TermuxSession} or {@link ExecutionCommand}.
*
* Only one of {@code termuxSession} and {@code executionCommand} must be set.
*
* If the {@code termuxSession} and its {@link #mTermuxSessionClient} are not {@code null},
* then the {@link TermuxSession.TermuxSessionClient#onTermuxSessionExited(TermuxSession)}
* callback will be called.
*
* @param termuxSession The {@link TermuxSession}, which should be set if
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, String, boolean)}
* successfully started the process.
* @param executionCommand The {@link ExecutionCommand}, which should be set if
* {@link #execute(Context, ExecutionCommand, TerminalSessionClient, TermuxSessionClient, String, boolean)}
* failed to start the process.
*/
private static void processTermuxSessionResult(final TermuxSession termuxSession, ExecutionCommand executionCommand) {
if (termuxSession != null)
executionCommand = termuxSession.mExecutionCommand;
if (executionCommand == null) return;
if (executionCommand.shouldNotProcessResults()) {
Logger.logDebug(LOG_TAG, "Ignoring duplicate call to process \"" + executionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession result");
return;
}
Logger.logDebug(LOG_TAG, "Processing \"" + executionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession result");
if (termuxSession != null && termuxSession.mTermuxSessionClient != null) {
termuxSession.mTermuxSessionClient.onTermuxSessionExited(termuxSession);
} else {
// If a callback is not set and execution command didn't fail, then we set success state now
// Otherwise, the callback host can set it himself when its done with the termuxSession
if (!executionCommand.isStateFailed())
executionCommand.setState(ExecutionCommand.ExecutionState.SUCCESS);
}
}
public TerminalSession getTerminalSession() {
return mTerminalSession;
}
public ExecutionCommand getExecutionCommand() {
return mExecutionCommand;
}
public interface TermuxSessionClient {
/**
* Callback function for when {@link TermuxSession} exits.
*
* @param termuxSession The {@link TermuxSession} that exited.
*/
void onTermuxSessionExited(TermuxSession termuxSession);
}
}

View File

@@ -0,0 +1,71 @@
package com.termux.shared.shell;
import com.termux.shared.logger.Logger;
import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSessionClient;
public class TermuxSessionClientBase implements TerminalSessionClient {
public TermuxSessionClientBase() {
}
@Override
public void onTextChanged(TerminalSession changedSession) {
}
@Override
public void onTitleChanged(TerminalSession updatedSession) {
}
@Override
public void onSessionFinished(final TerminalSession finishedSession) {
}
@Override
public void onClipboardText(TerminalSession session, String text) {
}
@Override
public void onBell(TerminalSession session) {
}
@Override
public void onColorsChanged(TerminalSession changedSession) {
}
@Override
public void logError(String tag, String message) {
Logger.logError(tag, message);
}
@Override
public void logWarn(String tag, String message) {
Logger.logWarn(tag, message);
}
@Override
public void logInfo(String tag, String message) {
Logger.logInfo(tag, message);
}
@Override
public void logDebug(String tag, String message) {
Logger.logDebug(tag, message);
}
@Override
public void logVerbose(String tag, String message) {
Logger.logVerbose(tag, message);
}
@Override
public void logStackTraceWithMessage(String tag, String message, Exception e) {
Logger.logStackTraceWithMessage(tag, message, e);
}
@Override
public void logStackTrace(String tag, Exception e) {
Logger.logStackTrace(tag, e);
}
}

View File

@@ -0,0 +1,273 @@
package com.termux.shared.shell;
import android.content.Context;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import androidx.annotation.NonNull;
import com.termux.shared.R;
import com.termux.shared.models.ExecutionCommand;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.logger.Logger;
import com.termux.shared.models.ExecutionCommand.ExecutionState;
import java.io.File;
import java.io.IOException;
/**
* A class that maintains info for background Termux tasks run with {@link Runtime#exec(String[], String[], File)}.
* It also provides a way to link each {@link Process} with the {@link ExecutionCommand}
* that started it.
*/
public final class TermuxTask {
private final Process mProcess;
private final ExecutionCommand mExecutionCommand;
private final TermuxTaskClient mTermuxTaskClient;
private final StringBuilder mStdout = new StringBuilder();
private final StringBuilder mStderr = new StringBuilder();
private static final String LOG_TAG = "TermuxTask";
private TermuxTask(@NonNull final Process process, @NonNull final ExecutionCommand executionCommand,
final TermuxTaskClient termuxTaskClient) {
this.mProcess = process;
this.mExecutionCommand = executionCommand;
this.mTermuxTaskClient = termuxTaskClient;
}
/**
* Start execution of an {@link ExecutionCommand} with {@link Runtime#exec(String[], String[], File)}.
*
* The {@link ExecutionCommand#executable}, must be set.
* The {@link ExecutionCommand#commandLabel}, {@link ExecutionCommand#arguments} and
* {@link ExecutionCommand#workingDirectory} may optionally be set.
*
* @param context The {@link Context} for operations.
* @param executionCommand The {@link ExecutionCommand} containing the information for execution command.
* @param termuxTaskClient The {@link TermuxTaskClient} interface implementation.
* The {@link TermuxTaskClient#onTermuxTaskExited(TermuxTask)} will
* be called regardless of {@code isSynchronous} value but not if
* {@code null} is returned by this method. This can
* optionally be {@code null}.
* @param isSynchronous If set to {@code true}, then the command will be executed in the
* caller thread and results returned synchronously in the {@link ExecutionCommand}
* sub object of the {@link TermuxTask} returned.
* If set to {@code false}, then a new thread is started run the commands
* asynchronously in the background and control is returned to the caller thread.
* @return Returns the {@link TermuxTask}. This will be {@code null} if failed to start the execution command.
*/
public static TermuxTask execute(@NonNull final Context context, @NonNull ExecutionCommand executionCommand,
final TermuxTaskClient termuxTaskClient, final boolean isSynchronous) {
if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty()) executionCommand.workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH;
String[] env = ShellUtils.buildEnvironment(context, false, executionCommand.workingDirectory);
final String[] commandArray = ShellUtils.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
if (!executionCommand.setState(ExecutionState.EXECUTING))
return null;
Logger.logDebug(LOG_TAG, executionCommand.toString());
String taskName = ShellUtils.getExecutableBasename(executionCommand.executable);
if (executionCommand.commandLabel == null)
executionCommand.commandLabel = taskName;
// Exec the process
final Process process;
try {
process = Runtime.getRuntime().exec(commandArray, env, new File(executionCommand.workingDirectory));
} catch (IOException e) {
executionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, context.getString(R.string.error_failed_to_execute_termux_task_command, executionCommand.getCommandIdAndLabelLogString()), e);
TermuxTask.processTermuxTaskResult(null, executionCommand);
return null;
}
final TermuxTask termuxTask = new TermuxTask(process, executionCommand, termuxTaskClient);
if (isSynchronous) {
try {
termuxTask.executeInner();
} catch (IllegalThreadStateException | InterruptedException e) {
// TODO: Should either of these be handled or returned?
}
} else {
new Thread() {
@Override
public void run() {
try {
termuxTask.executeInner();
} catch (IllegalThreadStateException | InterruptedException e) {
// TODO: Should either of these be handled or returned?
}
}
}.start();
}
return termuxTask;
}
/**
* Sets up stdout and stderr readers for the {@link #mProcess} and waits for the process to end.
*
* If the processes finishes, then sets {@link ExecutionCommand#stdout}, {@link ExecutionCommand#stderr}
* and {@link ExecutionCommand#exitCode} for the {@link #mExecutionCommand} of the {@code termuxTask}
* and then calls {@link #processTermuxTaskResult(TermuxTask, ExecutionCommand) to process the result}.
*/
private void executeInner() throws IllegalThreadStateException, InterruptedException {
final int pid = ShellUtils.getPid(mProcess);
Logger.logDebug(LOG_TAG, "Running \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid);
mExecutionCommand.stdout = null;
mExecutionCommand.stderr = null;
mExecutionCommand.exitCode = null;
// setup stdout and stderr gobblers
StreamGobbler STDOUT = new StreamGobbler(pid + "-stdout", mProcess.getInputStream(), mStdout);
StreamGobbler STDERR = new StreamGobbler(pid + "-stderr", mProcess.getErrorStream(), mStderr);
// start gobbling
STDOUT.start();
STDERR.start();
// wait for our process to finish, while we gobble away in the background
int exitCode = mProcess.waitFor();
// make sure our threads are done gobbling
// and the process is destroyed - while the latter shouldn't be
// needed in theory, and may even produce warnings, in "normal" Java
// they are required for guaranteed cleanup of resources, so lets be
// safe and do this on Android as well
STDOUT.join();
STDERR.join();
mProcess.destroy();
// Process result
if (exitCode == 0)
Logger.logDebug(LOG_TAG, "The \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid + " exited normally");
else
Logger.logDebug(LOG_TAG, "The \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid + " exited with code: " + exitCode);
// If the execution command has already failed, like SIGKILL was sent, then don't continue
if (mExecutionCommand.isStateFailed()) {
Logger.logDebug(LOG_TAG, "Ignoring setting \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask state to ExecutionState.EXECUTED and processing results since it has already failed");
return;
}
mExecutionCommand.stdout = mStdout.toString();
mExecutionCommand.stderr = mStderr.toString();
mExecutionCommand.exitCode = exitCode;
if (!mExecutionCommand.setState(ExecutionState.EXECUTED))
return;
TermuxTask.processTermuxTaskResult(this, null);
}
/**
* Kill this {@link TermuxTask} by sending a {@link OsConstants#SIGILL} to its {@link #mProcess}
* if its still executing.
*
* @param context The {@link Context} for operations.
* @param processResult If set to {@code true}, then the {@link #processTermuxTaskResult(TermuxTask, ExecutionCommand)}
* will be called to process the failure.
*/
public void killIfExecuting(@NonNull final Context context, boolean processResult) {
// If execution command has already finished executing, then no need to process results or send SIGKILL
if(mExecutionCommand.hasExecuted()) {
Logger.logDebug(LOG_TAG, "Ignoring sending SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask since it has already finished executing");
return;
}
Logger.logDebug(LOG_TAG, "Send SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask");
if (mExecutionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, context.getString(R.string.error_sending_sigkill_to_process), null)) {
if (processResult) {
// Get whatever output has been set till now in case its needed
mExecutionCommand.stdout = mStdout.toString();
mExecutionCommand.stderr = mStderr.toString();
mExecutionCommand.exitCode = 137; // SIGKILL
TermuxTask.processTermuxTaskResult(this, null);
}
}
if (mExecutionCommand.isExecuting()) {
int pid = ShellUtils.getPid(mProcess);
try {
// Send SIGKILL to process
Os.kill(pid, OsConstants.SIGKILL);
} catch (ErrnoException e) {
Logger.logWarn(LOG_TAG, "Failed to send SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid + ": " + e.getMessage());
}
}
}
/**
* Process the results of {@link TermuxTask} or {@link ExecutionCommand}.
*
* Only one of {@code termuxTask} and {@code executionCommand} must be set.
*
* If the {@code termuxTask} and its {@link #mTermuxTaskClient} are not {@code null},
* then the {@link TermuxTaskClient#onTermuxTaskExited(TermuxTask)} callback will be called.
*
* @param termuxTask The {@link TermuxTask}, which should be set if
* {@link #execute(Context, ExecutionCommand, TermuxTaskClient, boolean)}
* successfully started the process.
* @param executionCommand The {@link ExecutionCommand}, which should be set if
* {@link #execute(Context, ExecutionCommand, TermuxTaskClient, boolean)}
* failed to start the process.
*/
private static void processTermuxTaskResult(final TermuxTask termuxTask, ExecutionCommand executionCommand) {
if (termuxTask != null)
executionCommand = termuxTask.mExecutionCommand;
if (executionCommand == null) return;
if (executionCommand.shouldNotProcessResults()) {
Logger.logDebug(LOG_TAG, "Ignoring duplicate call to process \"" + executionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask result");
return;
}
Logger.logDebug(LOG_TAG, "Processing \"" + executionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask result");
if (termuxTask != null && termuxTask.mTermuxTaskClient != null) {
termuxTask.mTermuxTaskClient.onTermuxTaskExited(termuxTask);
} else {
// If a callback is not set and execution command didn't fail, then we set success state now
// Otherwise, the callback host can set it himself when its done with the termuxTask
if (!executionCommand.isStateFailed())
executionCommand.setState(ExecutionCommand.ExecutionState.SUCCESS);
}
}
public Process getProcess() {
return mProcess;
}
public ExecutionCommand getExecutionCommand() {
return mExecutionCommand;
}
public interface TermuxTaskClient {
/**
* Callback function for when {@link TermuxTask} exits.
*
* @param termuxTask The {@link TermuxTask} that exited.
*/
void onTermuxTaskExited(TermuxTask termuxTask);
}
}