/* * This file is part of the RootShell Project: http://code.google.com/p/RootShell/ * * Copyright (c) 2014 Stephen Erickson, Chris Ravenscroft * * This code is dual-licensed under the terms of the Apache License Version 2.0 and * the terms of the General Public License (GPL) Version 2. * You may use this code according to either of these licenses as is most appropriate * for your project on a case-by-case basis. * * The terms of each license can be found in the root directory of this project's repository as well as at: * * * http://www.apache.org/licenses/LICENSE-2.0 * * http://www.gnu.org/licenses/gpl-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under these Licenses is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See each License for the specific language governing permissions and * limitations under that License. */ package com.stericson.rootshell.execution; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import com.stericson.rootshell.RootShell; import java.io.IOException; public class Command { //directly modified by JavaCommand protected boolean javaCommand = false; protected Context context = null; public int totalOutput = 0; public int totalOutputProcessed = 0; ExecutionMonitor executionMonitor = null; Handler mHandler = null; //Has this command already been used? protected boolean used = false; boolean executing = false; String[] command = {}; boolean finished = false; boolean terminated = false; boolean handlerEnabled = true; int exitCode = -1; int id = 0; int timeout = RootShell.defaultCommandTimeout; /** * Constructor for executing a normal shell command * * @param id the id of the command being executed * @param command the command, or commands, to be executed. */ public Command(int id, String... command) { this.command = command; this.id = id; createHandler(RootShell.handlerEnabled); } /** * Constructor for executing a normal shell command * * @param id the id of the command being executed * @param handlerEnabled when true the handler will be used to call the * callback methods if possible. * @param command the command, or commands, to be executed. */ public Command(int id, boolean handlerEnabled, String... command) { this.command = command; this.id = id; createHandler(handlerEnabled); } /** * Constructor for executing a normal shell command * * @param id the id of the command being executed * @param timeout the time allowed before the shell will give up executing the command * and throw a TimeoutException. * @param command the command, or commands, to be executed. */ public Command(int id, int timeout, String... command) { this.command = command; this.id = id; this.timeout = timeout; createHandler(RootShell.handlerEnabled); } //If you override this you MUST make a final call //to the super method. The super call should be the last line of this method. public void commandOutput(int id, String line) { RootShell.log("Command", "ID: " + id + ", " + line); totalOutputProcessed++; } public void commandTerminated(int id, String reason) { //pass } public void commandCompleted(int id, int exitcode) { //pass } protected final void commandFinished() { if (!terminated) { synchronized (this) { if (mHandler != null && handlerEnabled) { Message msg = mHandler.obtainMessage(); Bundle bundle = new Bundle(); bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_COMPLETED); msg.setData(bundle); mHandler.sendMessage(msg); } else { commandCompleted(id, exitCode); } RootShell.log("Command " + id + " finished."); finishCommand(); } } } private void createHandler(boolean handlerEnabled) { this.handlerEnabled = handlerEnabled; if (Looper.myLooper() != null && handlerEnabled) { RootShell.log("CommandHandler created"); mHandler = new CommandHandler(); } else { RootShell.log("CommandHandler not created"); } } public final void finish() { RootShell.log("Command finished at users request!"); commandFinished(); } protected final void finishCommand() { this.executing = false; this.finished = true; this.notifyAll(); } public final String getCommand() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < command.length; i++) { if (i > 0) { sb.append('\n'); } sb.append(command[i]); } return sb.toString(); } public final boolean isExecuting() { return executing; } public final boolean isHandlerEnabled() { return handlerEnabled; } public final boolean isFinished() { return finished; } public final int getExitCode() { return this.exitCode; } protected final void setExitCode(int code) { synchronized (this) { exitCode = code; } } protected final void startExecution() { this.used = true; executionMonitor = new ExecutionMonitor(this); executionMonitor.setPriority(Thread.MIN_PRIORITY); executionMonitor.start(); executing = true; } public final void terminate() { RootShell.log("Terminating command at users request!"); terminated("Terminated at users request!"); } protected final void terminate(String reason) { try { Shell.closeAll(); RootShell.log("Terminating all shells."); terminated(reason); } catch (IOException e) { } } protected final void terminated(String reason) { synchronized (Command.this) { if (mHandler != null && handlerEnabled) { Message msg = mHandler.obtainMessage(); Bundle bundle = new Bundle(); bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_TERMINATED); bundle.putString(CommandHandler.TEXT, reason); msg.setData(bundle); mHandler.sendMessage(msg); } else { commandTerminated(id, reason); } RootShell.log("Command " + id + " did not finish because it was terminated. Termination reason: " + reason); setExitCode(-1); terminated = true; finishCommand(); } } protected final void output(int id, String line) { totalOutput++; if (mHandler != null && handlerEnabled) { Message msg = mHandler.obtainMessage(); Bundle bundle = new Bundle(); bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_OUTPUT); bundle.putString(CommandHandler.TEXT, line); msg.setData(bundle); mHandler.sendMessage(msg); } else { commandOutput(id, line); } } private class ExecutionMonitor extends Thread { private final Command command; public ExecutionMonitor(Command command) { this.command = command; } public void run() { if(command.timeout > 0) { synchronized (command) { try { RootShell.log("Command " + command.id + " is waiting for: " + command.timeout); command.wait(command.timeout); } catch (InterruptedException e) { RootShell.log("Exception: " + e); } if (!command.isFinished()) { RootShell.log("Timeout Exception has occurred for command: " + command.id + "."); terminate("Timeout Exception"); } } } } } private class CommandHandler extends Handler { static final public String ACTION = "action"; static final public String TEXT = "text"; static final public int COMMAND_OUTPUT = 0x01; static final public int COMMAND_COMPLETED = 0x02; static final public int COMMAND_TERMINATED = 0x03; public final void handleMessage(Message msg) { int action = msg.getData().getInt(ACTION); String text = msg.getData().getString(TEXT); switch (action) { case COMMAND_OUTPUT: commandOutput(id, text); break; case COMMAND_COMPLETED: commandCompleted(id, exitCode); break; case COMMAND_TERMINATED: commandTerminated(id, text); break; } } } }