playmusicexporter/superuser/src/main/java/rc/jcg/superuser/SuperUserCommand.java
2017-08-14 19:55:19 +02:00

384 lines
12 KiB
Java

/*
* Copyright (c) 2015 David Schulte
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rc.jcg.superuser;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/**
* This class executes superuser commands.
*/
public class SuperUserCommand {
/**
* The default timeout for each command in milliseconds
*/
private static final long DEFAULT_COMMAND_TIMEOUT = 30 * 1000; // 30 seconds
private String[] mCommands = new String[] {};
private String[] mOutputStandard = new String[] {};
private String[] mOutputError = new String[] {};
// If we want to get a binary return
private byte[] mOutputStandardBinary = new byte[] {};
/**
* Command failed?
*/
private boolean mSuperUserFailed;
/**
* If this value is set, the command will not store any input to the logger
*/
private boolean mHideInput = false;
/**
* If this value is set, the command will not store any standard output to the logger
*/
private boolean mHideStandardOutput = false;
/**
* If this value is set, the command will not store any error output to the logger
*/
private boolean mHideErrorOutput = false;
/**
* @return Gets whether the command hides the input log
*/
public boolean getHideInput() {
return mHideInput;
}
/**
* @param hideInput Set this to hide the input to the logger
*/
public void setHideInput(boolean hideInput) {
mHideInput = hideInput;
}
/**
* @return Gets whether the command hides the standard output log
*/
public boolean getHideStandardOutput() {
return mHideStandardOutput;
}
/**
* @param hideStandardOutput Set this to hide the standard output to the logger
*/
public void setHideStandardOutput(boolean hideStandardOutput) {
mHideStandardOutput = hideStandardOutput;
}
/**
* @return Gets whether the command hides the error output log
*/
public boolean getHideErrorOutput() {
return mHideErrorOutput;
}
/**
* @param hideErrorOutput Set this to hide the error output to the logger
*/
public void setHideErrorOutput(boolean hideErrorOutput) {
mHideErrorOutput = hideErrorOutput;
}
/**
* If this value is set the command will read the standard output as binary
*/
private boolean mBinaryStandardOutput = false;
/**
* @return Gets whether the output will be binary
*/
public boolean getBinaryStandardOutput() {
return mBinaryStandardOutput;
}
/**
* @param binaryStandardOutput Set this if you want a binary output
*/
public void setBinaryStandardOutput(boolean binaryStandardOutput) {
mBinaryStandardOutput = binaryStandardOutput;
}
/**
* The timeout for this command in milliseconds
*/
private long mTimeout;
/**
* @return Gets the timeout for this command in milliseconds
*/
public long getTimeout() {
return mTimeout;
}
/**
* Set the timeout for this command in milliseconds
* @param timeout Timeout
* @return Itself
*/
public SuperUserCommand setTimeout(long timeout) {
mTimeout = timeout;
return this;
}
/**
* @return Gets the executed commands
*/
public String[] getCommands() {
return mCommands;
}
/**
* @return Gets the standard output
*/
public String[] getStandardOutput() {
return mOutputStandard;
}
/**
* @return Gets the error output
*/
public String[] getErrorOutput() {
return mOutputError;
}
/**
* @return Gets the standard output as binary
*/
public byte[] getStandardOutputBinary() {
return mOutputStandardBinary;
}
/**
* @return Gets whether the command was executed without errors, even without error outputs from the command.
*/
public boolean commandWasSuccessful() {
return (!mSuperUserFailed && mOutputError.length == 0);
}
/**
* @return Gets whether the command was granted superuser permissions, but maybe has some error outputs.
*/
public boolean superuserWasSuccessful() {
return (!mSuperUserFailed);
}
/**
* The async execution thread
*/
private SuperUserCommandThread mThread;
/**
* The async callback
*/
private SuperUserCommandCallback mCallback;
/**
* Creates a command with one command line
* @param command The command
*/
public SuperUserCommand(String command) {
this(new String[] {command});
}
/**
* Creates a command with multiple command lines
* @param commands The command lines
*/
public SuperUserCommand(String[] commands) {
mCommands = commands;
// Default timeout
mTimeout = DEFAULT_COMMAND_TIMEOUT;
}
/**
* Execute the command asynchronously.
* Please notice that the commands will only executed one after another.
* The command will wait until the su process is free.
* @param callback The callback instance
*/
public void executeAsync(SuperUserCommandCallback callback) {
mCallback = callback;
// Thread is running
if (mThread != null) return;
// Create a new thread
mThread = new SuperUserCommandThread();
// Starts a thread
mThread.start();
}
/**
* Execute the command and return whether the command was executed.
* It will only return false if the app wasn't granted superuser permissions, like {@link #superuserWasSuccessful()}.
* It will also return true if the command itself returns error outputs. To check this case you should use {@link #commandWasSuccessful()} instead.
* Please consider to use {@link #executeAsync} instead of this and execute the command asynchronously.
* @return Gets whether the execution was successful.
*/
public boolean execute() {
String tmpLine;
List<String> tmpList = new ArrayList<>();
mSuperUserFailed = false;
// Opps, we don't have superuser permissions
// Did you run SuperUser.askForPermissions()?
if (!SuperUser.hasPermissions()) {
mSuperUserFailed = true;
return false;
}
// Thread safe
synchronized (SuperUser.getProcess()) {
try {
// Gets the streams
DataOutputStream dataOutputStream = new DataOutputStream(SuperUser.getProcess().getOutputStream());
BufferedReader bufferedInputReader = new BufferedReader(new InputStreamReader(SuperUser.getProcess().getInputStream()));
BufferedReader bufferedErrorReader = new BufferedReader(new InputStreamReader(SuperUser.getProcess().getErrorStream()));
// Sends the command
for (String command : mCommands) {
dataOutputStream.writeBytes(command + "\n");
}
dataOutputStream.flush();
// TODO: This class cannot execute commands without any output (standard and error). These commands will run until the timeout will kill them!
// Start waiting
long timeStarted = System.currentTimeMillis();
// Wait for first data
while (!bufferedInputReader.ready() && !bufferedErrorReader.ready()) {
try {
// Waiting
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
long timeNow = System.currentTimeMillis();
// TimeOut
if (timeNow - timeStarted >= mTimeout) break;
}
// We want to read the data as binary
if (mBinaryStandardOutput) {
int len;
byte[] buffer = new byte[1024];
// Byte buffer
ByteBuffer byteBuffer = new ByteBuffer();
// Need the direct input stream
InputStream inputStream = SuperUser.getProcess().getInputStream();
do {
while (bufferedInputReader.ready()) {
// Read to buffer
len = inputStream.read(buffer);
// Write to buffer
byteBuffer.append(buffer, 0, len);
}
// Fix: Wait for the buffer and try again
try {
// Sometimes cat is to slow.
// If there is no data anymore we will wait 100ms and check again.
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (bufferedInputReader.ready());
mOutputStandardBinary = byteBuffer.toByteArray();
} else {
// Reads the standard output as text
tmpList.clear();
while (bufferedInputReader.ready()) {
tmpLine = bufferedInputReader.readLine();
// End of data
if (tmpLine == null) break;
tmpList.add(tmpLine);
}
// Convert list to array
mOutputStandard = tmpList.toArray(new String[tmpList.size()]);
}
// Reads the error output
tmpList.clear();
while (bufferedErrorReader.ready()) {
tmpLine = bufferedErrorReader.readLine();
// End of data
if (tmpLine == null) break;
tmpList.add(tmpLine);
}
// Convert list to array
mOutputError = tmpList.toArray(new String[tmpList.size()]);
// Done
return true;
} catch (IOException e) {
e.printStackTrace();
mSuperUserFailed = true;
// Command failed
return false;
}
}
}
/**
* Thread to executes the command asynchronously
*/
private class SuperUserCommandThread extends Thread {
@Override
public void run() {
super.run();
// Executes the command
execute();
if (mCallback != null)
mCallback.onFinished(SuperUserCommand.this);
}
}
}