package com.jjnford.android.util;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Shell {
// Define output streams.
public static enum OUTPUT {
NONE, STDOUT, STDERR
};
private static OUTPUT sOStream = OUTPUT.STDOUT;
// Define su commands.
private static enum SU_COMMAND {
SU("su"),
BIN("/system/bin/su"),
XBIN("/system/xbin/su");
private String mCmd;
SU_COMMAND(String cmd) {
mCmd = cmd;
}
/**
* @return Set su command.
*/
public String getCommand() {
return mCmd;
}
}
// Define uid commands.
private static enum UID_COMMAND {
ID("id"),
BIN("/system/bin/id"),
XBIN("/system/xbin/id");
private String mCmd;
UID_COMMAND(String cmd) {
mCmd = cmd;
}
public String getCommand() {
return mCmd;
}
}
private static String sShell;
private static final String EOL = System.getProperty("line.separator");
private static final String EXIT = "exit" + Shell.EOL;
/**
* Shell Interface Utility Exception is used to compress IOExcptions and InterruptedExceptions.
*
* @author JJ Ford
*
*/
public static class ShellException extends Exception {
private static final long serialVersionUID = 4820332926695755116L;
public ShellException() {
super();
}
public ShellException(String msg) {
super(msg);
}
}
/**
* Used to buffer shell output off of the main thread.
*
* @author JJ Ford
*
*/
private static class Buffer extends Thread {
private InputStream mInputStream;
private StringBuffer mBuffer;
/**
* @param inputStream Data stream to get shell output from.
*/
public Buffer(InputStream inputStream) {
mInputStream = inputStream;
mBuffer = new StringBuffer();
this.start();
}
public String getOutput() {
return mBuffer.toString();
}
/*
* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(mInputStream));
if((line = reader.readLine()) != null) {
mBuffer.append(line);
while((line = reader.readLine()) != null) {
mBuffer.append(Shell.EOL).append(line);
}
}
} catch(IOException e) {
e.printStackTrace();
}
}
}
/**
* Block instantiation of this object.
*/
private Shell() {}
/**
* Executes a command in the devices native shell.
*
* @param cmd The command to execute.
* @return Output of the command, null if there is no output.
* @throws ShellException
*/
private static String nativeExec(String cmd) throws ShellException {
Process proc = null;
Buffer buffer = null;
try {
proc = Runtime.getRuntime().exec(cmd);
buffer = getBuffer(proc);
proc.waitFor();
return buffer.getOutput();
} catch (Exception e) {
throw new ShellException();
}
}
/**
* Executes a command in the su shell.
*
* @param cmd The command to execute.
* @return Output of the command, null if there is no output.
* @throws ShellException
*/
private static String suExec(String cmd) throws ShellException {
try {
Process proc = Runtime.getRuntime().exec(sShell);
Buffer buffer = getBuffer(proc);
DataOutputStream shell = new DataOutputStream(proc.getOutputStream());
// Write su command to su shell.
shell.writeBytes(cmd + Shell.EOL);
shell.flush();
shell.writeBytes(Shell.EXIT);
shell.flush();
proc.waitFor();
return buffer.getOutput();
} catch (Exception e) {
throw new ShellException();
}
}
/**
* Finds and sets the su shell that has root privileges.
* @throws ShellException
*/
private static void setSuShell() throws ShellException {
for(SU_COMMAND cmd : SU_COMMAND.values()) {
sShell = cmd.getCommand();
if(Shell.isRootUid()) {
return;
}
}
sShell = null;
}
/**
* Determines if the su shell has root privileges.
*
* @return True if the su shell has root privileges, false if not.
* @throws ShellException
*/
private static boolean isRootUid() throws ShellException {
for(UID_COMMAND uid : UID_COMMAND.values()) {
String output = Shell.sudo(uid.getCommand());
if(output != null && output.length() > 0) {
Matcher regex = Pattern.compile("^uid=(\\d+).*?").matcher(output);
if(regex.matches()) {
if("0".equals(regex.group(1))) {
return true;
}
}
}
}
return false;
}
/**
* Gets the buffer for the shell output stream that is currently set.
*
* @param proc Process running the shell command.
* @return The buffer containing the shell output stream, NULL is none.
*/
private static Buffer getBuffer(Process proc) {
Buffer buffer = null;
switch(sOStream) {
case NONE:
new Buffer(proc.getInputStream());
new Buffer(proc.getErrorStream());
break;
case STDOUT:
buffer = new Buffer(proc.getInputStream());
new Buffer(proc.getErrorStream());
break;
case STDERR:
buffer = new Buffer(proc.getErrorStream());
new Buffer(proc.getInputStream());
break;
default:
return buffer;
}
return buffer;
}
/*
* API
*
*
*
*/
/**
* Sets the shell's {@link Shell.OUTPUT output stream}. Default value is STDOUT.
*
* @param ostream The output Stream to read shell from.
*/
synchronized public static void setOutputStream(Shell.OUTPUT ostream) {
sOStream = ostream;
}
/**
* Sets the su shell to be used.
*
* @param shell The shell to be used for sudo.
*/
synchronized public static void setShell(String shell) {
sShell = shell;
}
/**
* Gains privileges to root shell. Device must be rooted to use.
*
* @return True if root shell is obtained, false if not.
*/
synchronized public static boolean su() {
if(sShell == null) {
try {
Shell.setSuShell();
} catch (ShellException e) {
sShell = null;
}
}
return sShell != null;
}
/**
* Executes a command in the root shell. Devices must be rooted to use.
*
* @param cmd The command to execute in root shell.
* @return Output of the command, null if there is no output.
* @throws ShellException
* @throws InterruptedException
* @throws IOException
*/
synchronized public static String sudo(String cmd) throws ShellException {
if(Shell.su()) {
return Shell.suExec(cmd);
} else {
return null;
}
}
/**
* Executes a native shell command.
*
* @param cmd The command to execute in the native shell.
* @return Output of the command, null if there is no output.
* @throws ShellException
*/
synchronized public static String exec(String cmd) throws ShellException {
return Shell.nativeExec(cmd);
}
}