package com.aero.control.helpers;
import android.os.Looper;
import android.util.Log;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by Alexander Christ on 11.11.14.
* Heavily based on the shell by Chainfire.
*/
public class Shell {
private static final String LOG_TAG = Shell.class.getName();
private List<String> mCommands;
private Process mProcess = null;
private DataOutputStream mShellOutput = null;
/**
* Starts a background thread which creates a new shell. Also
* queues up the first startup command for the shell (can be either
* sh or su).
* The main process is also initiated together with the output
* stream. There are no threads for the input/error stream from the
* process since it adds an unnecessary overhead to the shell.
*
* @param commands String, which can be either "sh" or "su"
* @param runOnOwnThread boolean, should run in its own thread?
*/
public Shell(final String commands, boolean runOnOwnThread) {
mCommands = new ArrayList<String>();
if (!runOnOwnThread) {
initInteractive(commands);
} else {
Runnable run = new Runnable() {
@Override
public void run() {
try {
initInteractive(commands);
} catch (ShellException e) {
Log.e(LOG_TAG, "No shell was created.", e);
}
}
};
Thread worker = new Thread(run);
worker.start();
}
}
private void checkUIThread() {
if (Looper.myLooper() == Looper.getMainLooper()) {
// We are on our main UI thread, crash!
throw new ShellException(ShellException.MAIN_UI_EXCEPTION);
}
}
private synchronized void initInteractive(String su) {
checkUIThread();
try {
mProcess = Runtime.getRuntime().exec(su);
mShellOutput = new DataOutputStream(mProcess.getOutputStream());
} catch (IOException e) {
throw new ShellException(ShellException.NO_INTERACTIVE_SHELL);
}
}
/**
* Adds a single command to the command list, which will be executed
* and cleaned in the next run.
*
* @param cmd String, which contains the full command
*/
public synchronized void addCommand(String cmd) {
this.mCommands.add(cmd);
}
/**
* Adds a string list to the command list, which will be executed and
* cleaned in the next run.
*
* @param cmds List<String>, which contains the full command(s).
*/
public synchronized void addCommand(List<String> cmds) {
this.mCommands.addAll(cmds);
}
/**
* Adds a string list to the command list, which will be executed and
* cleaned in the next run.
*
* @param cmds List<String>, which contains the full command(s).
*/
public synchronized void addCommand(String[] cmds) {
for (String cmd : cmds) {
if (cmd != null)
this.mCommands.add(cmd);
}
}
/**
* Executes the collected commands and cleans the command list afterwards.
* After Execution, the shell stays alive until it is killed.
*/
public void runInteractive() {
List<String> commands = Collections.synchronizedList(mCommands);
try {
for (String cmd : commands) {
mShellOutput.write((cmd + "\n").getBytes("UTF-8"));
mShellOutput.flush();
}
try {
mShellOutput.flush();
} catch (IOException e) {}
} catch (IOException e) {
Log.e(LOG_TAG, "Something interrupted our operations...", e);
}
mCommands.clear();
}
/**
* Actually "kills" the interactive shell because waitFor() might
* need some time until its "killed".
*/
public void closeInteractive() {
try {
mShellOutput.close();
} catch (IOException e) {}
mProcess.destroy();
}
/**
* Exception-Class
*/
public static class ShellException extends RuntimeException {
public static final String MAIN_UI_EXCEPTION = "You have tried to execute your commands in the" +
" main UI Thread. Consider using async-tasks or a thread instead.";
public static final String NO_INTERACTIVE_SHELL = "The interactive shell couldn't be created";
public ShellException(String message) {
super(message);
}
}
}