From 9316e0751c7a0cb47717c7b17ac95f0870196c23 Mon Sep 17 00:00:00 2001
From: Kevin Cernekee
Date: Sat, 7 Dec 2013 15:30:16 -0800
Subject: [PATCH 1/3] Create new postCallback() helper method
This just eliminates some repetition. No change to functionality.
---
.../src/eu/chainfire/libsuperuser/Shell.java | 62 ++++++++-----------
1 file changed, 27 insertions(+), 35 deletions(-)
diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
index 4bae0f0..e2db73d 100644
--- a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
+++ b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
@@ -797,19 +797,7 @@ private synchronized void handleWatchdog() {
}
if (handler != null) {
- final Command fCommand = command;
- final List fBuffer = buffer;
- startCallback();
- handler.post(new Runnable() {
- @Override
- public void run() {
- try {
- fCommand.onCommandResultListener.onCommandResult(fCommand.code, exitCode, fBuffer);
- } finally {
- endCallback();
- }
- }
- });
+ postCallback(command, exitCode, buffer);
}
// prevent multiple callbacks for the same command
@@ -906,28 +894,8 @@ private void runNextCommand(boolean notifyIdle) {
*/
private synchronized void processMarker() {
if (command.marker.equals(lastMarkerSTDOUT) && (command.marker.equals(lastMarkerSTDERR))) {
- if (command.onCommandResultListener != null) {
- if (buffer != null) {
- if (handler != null) {
- final List fBuffer = buffer;
- final int fExitCode = lastExitCode;
- final Command fCommand = command;
-
- startCallback();
- handler.post(new Runnable() {
- @Override
- public void run() {
- try {
- fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fBuffer);
- } finally {
- endCallback();
- }
- }
- });
- } else {
- command.onCommandResultListener.onCommandResult(command.code, lastExitCode, buffer);
- }
- }
+ if (buffer != null) {
+ postCallback(command, lastExitCode, buffer);
}
stopWatchdog();
@@ -986,6 +954,30 @@ private void startCallback() {
callbacks++;
}
}
+
+ /**
+ * Schedule a callback to run on the appropriate thread
+ */
+ private void postCallback(final Command fCommand, final int fExitCode, final List fOutput) {
+ if (fCommand.onCommandResultListener == null) {
+ return;
+ }
+ if (handler == null) {
+ fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fOutput);
+ return;
+ }
+ startCallback();
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fOutput);
+ } finally {
+ endCallback();
+ }
+ }
+ });
+ }
/**
* Decrease callback counter, signals callback complete state when dropped to 0
From fcef4d26bf608388da424ec9db43e6e60c2fd1aa Mon Sep 17 00:00:00 2001
From: Kevin Cernekee
Date: Sat, 7 Dec 2013 15:32:50 -0800
Subject: [PATCH 2/3] Missing callbacks if shell dies in Interactive mode
libsuperuser is currently able to handle failures in several different
stages:
1) If the shell cannot be started at all (e.g. missing "su" binary),
Runtime.exec() should throw an exception, causing our open() method to
return failure.
2) If the shell dies while a command is executing, and the watchdog timer
is enabled, the onCommandResult() callback will get invoked so that the
caller can handle the error.
3) If the device operator denies a Superuser request, this typically
falls under case #2 as the "su" executable will stay alive for a couple
of seconds until the operator clicks "deny."
But if the shell dies when there is no active command, any queued commands
will get stuck in limbo and the caller will never receive a status
indication. This will probably cause the app to freeze until the
operator kills it, resulting in a subpar user experience.
The easiest way to reproduce the problem is to try running
libsuperuser_example in interactive mode on the SDK emulator (which has
a trivial "su" implementation that instantly denies root access to all
apps). It will hang at "Requesting root privilege" forever.
So, we'll add an extra loop to send the bad news to the calling app if
libsuperuser isn't able to execute the requested commands.
---
libsuperuser/src/eu/chainfire/libsuperuser/Shell.java | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
index e2db73d..264af69 100644
--- a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
+++ b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
@@ -764,10 +764,8 @@ protected void finalize() throws Throwable {
* @param onCommandResultListener Callback to be called on completion (of all commands)
*/
public synchronized void addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) {
- if (running) {
- this.commands.add(new Command(commands, code, onCommandResultListener));
- runNextCommand();
- }
+ this.commands.add(new Command(commands, code, onCommandResultListener));
+ runNextCommand();
}
/**
@@ -880,6 +878,11 @@ private void runNextCommand(boolean notifyIdle) {
} else {
runNextCommand(false);
}
+ } else if (!running) {
+ // our shell died for unknown reasons - abort all submissions
+ while (commands.size() > 0) {
+ postCallback(commands.remove(0), OnCommandResultListener.SHELL_DIED, null);
+ }
}
if (idle && notifyIdle) {
From d3b07aa263bcf081d5447446fcb0affc76560256 Mon Sep 17 00:00:00 2001
From: Kevin Cernekee
Date: Sat, 7 Dec 2013 15:57:26 -0800
Subject: [PATCH 3/3] Run dos2unix on *.java
Currently these source files have a mix of CRLF and LF (even inside the
same file), which causes problems with patching/merging. Pick one format
for consistency's sake.
---
.../chainfire/libsuperuser/Application.java | 158 +-
.../src/eu/chainfire/libsuperuser/Debug.java | 432 ++--
.../src/eu/chainfire/libsuperuser/Shell.java | 2036 ++++++++---------
.../libsuperuser/ShellNotClosedException.java | 58 +-
.../ShellOnMainThreadException.java | 64 +-
.../chainfire/libsuperuser/StreamGobbler.java | 204 +-
.../BackgroundIntentService.java | 202 +-
.../BootCompleteReceiver.java | 86 +-
8 files changed, 1620 insertions(+), 1620 deletions(-)
diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Application.java b/libsuperuser/src/eu/chainfire/libsuperuser/Application.java
index 128f2d0..454740a 100644
--- a/libsuperuser/src/eu/chainfire/libsuperuser/Application.java
+++ b/libsuperuser/src/eu/chainfire/libsuperuser/Application.java
@@ -1,79 +1,79 @@
-/*
- * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.chainfire.libsuperuser;
-
-import android.content.Context;
-import android.os.Handler;
-import android.widget.Toast;
-
-/**
- * Base application class to extend from, solving some issues with
- * toasts and AsyncTasks you are likely to run into
- */
-public class Application extends android.app.Application {
- /**
- * Shows a toast message
- *
- * @param context Any context belonging to this application
- * @param message The message to show
- */
- public static void toast(Context context, String message) {
- // this is a static method so it is easier to call,
- // as the context checking and casting is done for you
-
- if (context == null) return;
-
- if (!(context instanceof Application)) {
- context = context.getApplicationContext();
- }
-
- if (context instanceof Application) {
- final Context c = context;
- final String m = message;
-
- ((Application)context).runInApplicationThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(c, m, Toast.LENGTH_LONG).show();
- }
- });
- }
- }
-
- private static Handler mApplicationHandler = new Handler();
-
- /**
- * Run a runnable in the main application thread
- *
- * @param r Runnable to run
- */
- public void runInApplicationThread(Runnable r) {
- mApplicationHandler.post(r);
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- try {
- // workaround bug in AsyncTask, can show up (for example) when you toast from a service
- // this makes sure AsyncTask's internal handler is created from the right (main) thread
- Class.forName("android.os.AsyncTask");
- } catch (ClassNotFoundException e) {
- }
- }
-}
+/*
+ * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.chainfire.libsuperuser;
+
+import android.content.Context;
+import android.os.Handler;
+import android.widget.Toast;
+
+/**
+ * Base application class to extend from, solving some issues with
+ * toasts and AsyncTasks you are likely to run into
+ */
+public class Application extends android.app.Application {
+ /**
+ * Shows a toast message
+ *
+ * @param context Any context belonging to this application
+ * @param message The message to show
+ */
+ public static void toast(Context context, String message) {
+ // this is a static method so it is easier to call,
+ // as the context checking and casting is done for you
+
+ if (context == null) return;
+
+ if (!(context instanceof Application)) {
+ context = context.getApplicationContext();
+ }
+
+ if (context instanceof Application) {
+ final Context c = context;
+ final String m = message;
+
+ ((Application)context).runInApplicationThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(c, m, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ }
+
+ private static Handler mApplicationHandler = new Handler();
+
+ /**
+ * Run a runnable in the main application thread
+ *
+ * @param r Runnable to run
+ */
+ public void runInApplicationThread(Runnable r) {
+ mApplicationHandler.post(r);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ try {
+ // workaround bug in AsyncTask, can show up (for example) when you toast from a service
+ // this makes sure AsyncTask's internal handler is created from the right (main) thread
+ Class.forName("android.os.AsyncTask");
+ } catch (ClassNotFoundException e) {
+ }
+ }
+}
diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java b/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java
index 6ae704e..2d059e3 100644
--- a/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java
+++ b/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java
@@ -1,102 +1,102 @@
-/*
- * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.chainfire.libsuperuser;
-
-import android.os.Looper;
-import android.util.Log;
-
-/**
- * Utility class for logging and debug features that (by default) does nothing when not in debug mode
- */
-public class Debug {
-
- // ----- DEBUGGING -----
-
- private static boolean debug = BuildConfig.DEBUG;
-
- /**
- * Enable or disable debug mode
- *
- * By default, debug mode is enabled for development
- * builds and disabled for exported APKs - see
- * BuildConfig.DEBUG
- *
- * @param enabled Enable debug mode ?
- */
- public static void setDebug(boolean enable) {
- debug = enable;
- }
-
- /**
- * Is debug mode enabled ?
- *
- * @return Debug mode enabled
- */
- public static boolean getDebug() {
- return debug;
- }
-
- // ----- LOGGING -----
-
- public interface OnLogListener {
- public void onLog(int type, String typeIndicator, String message);
- }
-
- public static final String TAG = "libsuperuser";
-
- public static final int LOG_GENERAL = 0x0001;
- public static final int LOG_COMMAND = 0x0002;
- public static final int LOG_OUTPUT = 0x0004;
-
- public static final int LOG_NONE = 0x0000;
- public static final int LOG_ALL = 0xFFFF;
-
- private static int logTypes = LOG_ALL;
-
- private static OnLogListener logListener = null;
-
- /**
- * Log a message (internal)
- *
- * Current debug and enabled logtypes decide what gets logged -
- * even if a custom callback is registered
- *
- * @param type Type of message to log
- * @param typeIndicator String indicator for message type
- * @param message The message to log
- */
- private static void logCommon(int type, String typeIndicator, String message) {
- if (debug && ((logTypes & type) == type)) {
- if (logListener != null) {
- logListener.onLog(type, typeIndicator, message);
- } else {
- Log.d(TAG, "[" + TAG + "][" + typeIndicator + "]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message);
- }
- }
- }
-
- /**
+/*
+ * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.chainfire.libsuperuser;
+
+import android.os.Looper;
+import android.util.Log;
+
+/**
+ * Utility class for logging and debug features that (by default) does nothing when not in debug mode
+ */
+public class Debug {
+
+ // ----- DEBUGGING -----
+
+ private static boolean debug = BuildConfig.DEBUG;
+
+ /**
+ * Enable or disable debug mode
+ *
+ * By default, debug mode is enabled for development
+ * builds and disabled for exported APKs - see
+ * BuildConfig.DEBUG
+ *
+ * @param enabled Enable debug mode ?
+ */
+ public static void setDebug(boolean enable) {
+ debug = enable;
+ }
+
+ /**
+ * Is debug mode enabled ?
+ *
+ * @return Debug mode enabled
+ */
+ public static boolean getDebug() {
+ return debug;
+ }
+
+ // ----- LOGGING -----
+
+ public interface OnLogListener {
+ public void onLog(int type, String typeIndicator, String message);
+ }
+
+ public static final String TAG = "libsuperuser";
+
+ public static final int LOG_GENERAL = 0x0001;
+ public static final int LOG_COMMAND = 0x0002;
+ public static final int LOG_OUTPUT = 0x0004;
+
+ public static final int LOG_NONE = 0x0000;
+ public static final int LOG_ALL = 0xFFFF;
+
+ private static int logTypes = LOG_ALL;
+
+ private static OnLogListener logListener = null;
+
+ /**
+ * Log a message (internal)
+ *
+ * Current debug and enabled logtypes decide what gets logged -
+ * even if a custom callback is registered
+ *
+ * @param type Type of message to log
+ * @param typeIndicator String indicator for message type
+ * @param message The message to log
+ */
+ private static void logCommon(int type, String typeIndicator, String message) {
+ if (debug && ((logTypes & type) == type)) {
+ if (logListener != null) {
+ logListener.onLog(type, typeIndicator, message);
+ } else {
+ Log.d(TAG, "[" + TAG + "][" + typeIndicator + "]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message);
+ }
+ }
+ }
+
+ /**
* Log a "general" message
*
* These messages are infrequent and mostly occur at startup/shutdown or on error
- *
- * @param message The message to log
- */
- public static void log(String message) {
+ *
+ * @param message The message to log
+ */
+ public static void log(String message) {
logCommon(LOG_GENERAL, "G", message);
}
@@ -107,7 +107,7 @@ public static void log(String message) {
*
* @param message The message to log
*/
- public static void logCommand(String message) {
+ public static void logCommand(String message) {
logCommon(LOG_COMMAND, "C", message);
}
@@ -118,123 +118,123 @@ public static void logCommand(String message) {
*
* @param message The message to log
*/
- public static void logOutput(String message) {
+ public static void logOutput(String message) {
logCommon(LOG_OUTPUT, "O", message);
- }
-
- /**
- * Enable or disable logging specific types of message
- *
- * You may | (or) LOG_* constants together. Note that
- * debug mode must also be enabled for actual logging to
- * occur.
- *
- * @param type LOG_* constants
- * @param enabled Enable or disable
- */
- public static void setLogTypeEnabled(int type, boolean enable) {
- if (enable) {
- logTypes |= type;
- } else {
- logTypes &= ~type;
- }
- }
-
- /**
- * Is logging for specific types of messages enabled ?
- *
- * You may | (or) LOG_* constants together, to learn if
- * all passed message types are enabled for logging. Note
- * that debug mode must also be enabled for actual logging
- * to occur.
- *
- * @param type LOG_* constants
- */
- public static boolean getLogTypeEnabled(int type) {
- return ((logTypes & type) == type);
- }
-
- /**
- * Is logging for specific types of messages enabled ?
- *
- * You may | (or) LOG_* constants together, to learn if
- * all message types are enabled for logging. Takes
- * debug mode into account for the result.
- *
- * @param type LOG_* constants
- */
- public static boolean getLogTypeEnabledEffective(int type) {
- return getDebug() && getLogTypeEnabled(type);
- }
-
- /**
- * Register a custom log handler
- *
- * Replaces the log method (write to logcat) with your own
- * handler. Whether your handler gets called is still dependent
- * on debug mode and message types being enabled for logging.
- *
- * @param onLogListener Custom log listener or NULL to revert to default
- */
- public static void setOnLogListener(OnLogListener onLogListener) {
- logListener = onLogListener;
- }
-
- /**
- * Get the currently registered custom log handler
- *
- * @return Current custom log handler or NULL if none is present
- */
- public static OnLogListener getOnLogListener() {
- return logListener;
- }
-
- // ----- SANITY CHECKS -----
-
- private static boolean sanityChecks = true;
-
- /**
- * Enable or disable sanity checks
- *
- * Enables or disables the library crashing when su is called
- * from the main thread.
- *
- * @param enabled Enable or disable
- */
- public static void setSanityChecksEnabled(boolean enable) {
- sanityChecks = enable;
- }
-
- /**
- * Are sanity checks enabled ?
- *
- * Note that debug mode must also be enabled for actual
- * sanity checks to occur.
- *
- * @return True if enabled
- */
- public static boolean getSanityChecksEnabled() {
- return sanityChecks;
- }
-
- /**
- * Are sanity checks enabled ?
- *
- * Takes debug mode into account for the result.
- *
- * @return True if enabled
- */
- public static boolean getSanityChecksEnabledEffective() {
- return getDebug() && getSanityChecksEnabled();
- }
-
- /**
- * Are we running on the main thread ?
- *
- * @return Running on main thread ?
- */
- public static boolean onMainThread() {
- return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper()));
- }
-
-}
+ }
+
+ /**
+ * Enable or disable logging specific types of message
+ *
+ * You may | (or) LOG_* constants together. Note that
+ * debug mode must also be enabled for actual logging to
+ * occur.
+ *
+ * @param type LOG_* constants
+ * @param enabled Enable or disable
+ */
+ public static void setLogTypeEnabled(int type, boolean enable) {
+ if (enable) {
+ logTypes |= type;
+ } else {
+ logTypes &= ~type;
+ }
+ }
+
+ /**
+ * Is logging for specific types of messages enabled ?
+ *
+ * You may | (or) LOG_* constants together, to learn if
+ * all passed message types are enabled for logging. Note
+ * that debug mode must also be enabled for actual logging
+ * to occur.
+ *
+ * @param type LOG_* constants
+ */
+ public static boolean getLogTypeEnabled(int type) {
+ return ((logTypes & type) == type);
+ }
+
+ /**
+ * Is logging for specific types of messages enabled ?
+ *
+ * You may | (or) LOG_* constants together, to learn if
+ * all message types are enabled for logging. Takes
+ * debug mode into account for the result.
+ *
+ * @param type LOG_* constants
+ */
+ public static boolean getLogTypeEnabledEffective(int type) {
+ return getDebug() && getLogTypeEnabled(type);
+ }
+
+ /**
+ * Register a custom log handler
+ *
+ * Replaces the log method (write to logcat) with your own
+ * handler. Whether your handler gets called is still dependent
+ * on debug mode and message types being enabled for logging.
+ *
+ * @param onLogListener Custom log listener or NULL to revert to default
+ */
+ public static void setOnLogListener(OnLogListener onLogListener) {
+ logListener = onLogListener;
+ }
+
+ /**
+ * Get the currently registered custom log handler
+ *
+ * @return Current custom log handler or NULL if none is present
+ */
+ public static OnLogListener getOnLogListener() {
+ return logListener;
+ }
+
+ // ----- SANITY CHECKS -----
+
+ private static boolean sanityChecks = true;
+
+ /**
+ * Enable or disable sanity checks
+ *
+ * Enables or disables the library crashing when su is called
+ * from the main thread.
+ *
+ * @param enabled Enable or disable
+ */
+ public static void setSanityChecksEnabled(boolean enable) {
+ sanityChecks = enable;
+ }
+
+ /**
+ * Are sanity checks enabled ?
+ *
+ * Note that debug mode must also be enabled for actual
+ * sanity checks to occur.
+ *
+ * @return True if enabled
+ */
+ public static boolean getSanityChecksEnabled() {
+ return sanityChecks;
+ }
+
+ /**
+ * Are sanity checks enabled ?
+ *
+ * Takes debug mode into account for the result.
+ *
+ * @return True if enabled
+ */
+ public static boolean getSanityChecksEnabledEffective() {
+ return getDebug() && getSanityChecksEnabled();
+ }
+
+ /**
+ * Are we running on the main thread ?
+ *
+ * @return Running on main thread ?
+ */
+ public static boolean onMainThread() {
+ return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper()));
+ }
+
+}
diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
index 264af69..37baac3 100644
--- a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
+++ b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
@@ -1,168 +1,168 @@
-/*
- * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.chainfire.libsuperuser;
-
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
+/*
+ * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.chainfire.libsuperuser;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.UUID;
+import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import eu.chainfire.libsuperuser.StreamGobbler.OnLineListener;
-
-/**
- * Class providing functionality to execute commands in a (root) shell
- */
-public class Shell {
- /**
- * Runs commands using the supplied shell, and returns the output, or null in
- * case of errors.
- *
- * This method is deprecated and only provided for backwards compatibility.
- * Use {@link #run(String, String[], String[], boolean)} instead, and see that
- * same method for usage notes.
- *
- * @param shell The shell to use for executing the commands
- * @param commands The commands to execute
- * @param wantSTDERR Return STDERR in the output ?
- * @return Output of the commands, or null in case of an error
- */
- @Deprecated
- public static List run(String shell, String[] commands, boolean wantSTDERR) {
- return run(shell, commands, null, wantSTDERR);
- }
-
- /**
- * Runs commands using the supplied shell, and returns the output, or null in
- * case of errors.
- *
- * Note that due to compatibility with older Android versions,
- * wantSTDERR is not implemented using redirectErrorStream, but rather appended
- * to the output. STDOUT and STDERR are thus not guaranteed to be in the correct
- * order in the output.
- *
- * Note as well that this code will intentionally crash when run in debug mode
- * from the main thread of the application. You should always execute shell
- * commands from a background thread.
- *
- * When in debug mode, the code will also excessively log the commands passed to
- * and the output returned from the shell.
- *
- * Though this function uses background threads to gobble STDOUT and STDERR so
- * a deadlock does not occur if the shell produces massive output, the output is
- * still stored in a List<String>, and as such doing something like 'ls -lR /'
- * will probably have you run out of memory.
- *
- * @param shell The shell to use for executing the commands
- * @param commands The commands to execute
- * @param environment List of all environment variables (in 'key=value' format) or null for defaults
- * @param wantSTDERR Return STDERR in the output ?
- * @return Output of the commands, or null in case of an error
- */
- public static List run(String shell, String[] commands, String[] environment, boolean wantSTDERR) {
+
+import android.os.Handler;
+import android.os.Looper;
+
+import eu.chainfire.libsuperuser.StreamGobbler.OnLineListener;
+
+/**
+ * Class providing functionality to execute commands in a (root) shell
+ */
+public class Shell {
+ /**
+ * Runs commands using the supplied shell, and returns the output, or null in
+ * case of errors.
+ *
+ * This method is deprecated and only provided for backwards compatibility.
+ * Use {@link #run(String, String[], String[], boolean)} instead, and see that
+ * same method for usage notes.
+ *
+ * @param shell The shell to use for executing the commands
+ * @param commands The commands to execute
+ * @param wantSTDERR Return STDERR in the output ?
+ * @return Output of the commands, or null in case of an error
+ */
+ @Deprecated
+ public static List run(String shell, String[] commands, boolean wantSTDERR) {
+ return run(shell, commands, null, wantSTDERR);
+ }
+
+ /**
+ * Runs commands using the supplied shell, and returns the output, or null in
+ * case of errors.
+ *
+ * Note that due to compatibility with older Android versions,
+ * wantSTDERR is not implemented using redirectErrorStream, but rather appended
+ * to the output. STDOUT and STDERR are thus not guaranteed to be in the correct
+ * order in the output.
+ *
+ * Note as well that this code will intentionally crash when run in debug mode
+ * from the main thread of the application. You should always execute shell
+ * commands from a background thread.
+ *
+ * When in debug mode, the code will also excessively log the commands passed to
+ * and the output returned from the shell.
+ *
+ * Though this function uses background threads to gobble STDOUT and STDERR so
+ * a deadlock does not occur if the shell produces massive output, the output is
+ * still stored in a List<String>, and as such doing something like 'ls -lR /'
+ * will probably have you run out of memory.
+ *
+ * @param shell The shell to use for executing the commands
+ * @param commands The commands to execute
+ * @param environment List of all environment variables (in 'key=value' format) or null for defaults
+ * @param wantSTDERR Return STDERR in the output ?
+ * @return Output of the commands, or null in case of an error
+ */
+ public static List run(String shell, String[] commands, String[] environment, boolean wantSTDERR) {
String shellUpper = shell.toUpperCase(Locale.ENGLISH);
-
- if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) {
- // check if we're running in the main thread, and if so, crash if we're in debug mode,
- // to let the developer know attention is needed here.
-
- Debug.log(ShellOnMainThreadException.EXCEPTION_COMMAND);
- throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_COMMAND);
- }
+
+ if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) {
+ // check if we're running in the main thread, and if so, crash if we're in debug mode,
+ // to let the developer know attention is needed here.
+
+ Debug.log(ShellOnMainThreadException.EXCEPTION_COMMAND);
+ throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_COMMAND);
+ }
Debug.logCommand(String.format("[%s%%] START", shellUpper));
-
- List res = Collections.synchronizedList(new ArrayList());
-
- try {
- // Combine passed environment with system environment
- if (environment != null) {
- Map newEnvironment = new HashMap();
- newEnvironment.putAll(System.getenv());
- int split;
- for (String entry : environment) {
- if ((split = entry.indexOf("=")) >= 0) {
- newEnvironment.put(entry.substring(0, split), entry.substring(split + 1));
- }
- }
- int i = 0;
- environment = new String[newEnvironment.size()];
- for (Map.Entry entry : newEnvironment.entrySet()) {
- environment[i] = entry.getKey() + "=" + entry.getValue();
- i++;
- }
- }
-
- // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers
- Process process = Runtime.getRuntime().exec(shell, environment);
- DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
- StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), res);
- StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), wantSTDERR ? res : null);
-
- // start gobbling and write our commands to the shell
- STDOUT.start();
- STDERR.start();
- for (String write : commands) {
+
+ List res = Collections.synchronizedList(new ArrayList());
+
+ try {
+ // Combine passed environment with system environment
+ if (environment != null) {
+ Map newEnvironment = new HashMap();
+ newEnvironment.putAll(System.getenv());
+ int split;
+ for (String entry : environment) {
+ if ((split = entry.indexOf("=")) >= 0) {
+ newEnvironment.put(entry.substring(0, split), entry.substring(split + 1));
+ }
+ }
+ int i = 0;
+ environment = new String[newEnvironment.size()];
+ for (Map.Entry entry : newEnvironment.entrySet()) {
+ environment[i] = entry.getKey() + "=" + entry.getValue();
+ i++;
+ }
+ }
+
+ // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers
+ Process process = Runtime.getRuntime().exec(shell, environment);
+ DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
+ StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), res);
+ StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), wantSTDERR ? res : null);
+
+ // start gobbling and write our commands to the shell
+ STDOUT.start();
+ STDERR.start();
+ for (String write : commands) {
Debug.logCommand(String.format("[%s+] %s", shellUpper, write));
- STDIN.write((write + "\n").getBytes("UTF-8"));
- STDIN.flush();
- }
- STDIN.write("exit\n".getBytes("UTF-8"));
- STDIN.flush();
-
- // wait for our process to finish, while we gobble away in the background
- process.waitFor();
-
- // make sure our threads are done gobbling, our streams are closed, and the process is
- // destroyed - while the latter two 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
- try {
- STDIN.close();
- } catch (IOException e) {
- }
- STDOUT.join();
- STDERR.join();
- process.destroy();
-
- // in case of su, 255 usually indicates access denied
- if (shell.equals("su") && (process.exitValue() == 255)) {
- res = null;
- }
- } catch (IOException e) {
- // shell probably not found
- res = null;
- } catch (InterruptedException e) {
- // this should really be re-thrown
- res = null;
- }
-
+ STDIN.write((write + "\n").getBytes("UTF-8"));
+ STDIN.flush();
+ }
+ STDIN.write("exit\n".getBytes("UTF-8"));
+ STDIN.flush();
+
+ // wait for our process to finish, while we gobble away in the background
+ process.waitFor();
+
+ // make sure our threads are done gobbling, our streams are closed, and the process is
+ // destroyed - while the latter two 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
+ try {
+ STDIN.close();
+ } catch (IOException e) {
+ }
+ STDOUT.join();
+ STDERR.join();
+ process.destroy();
+
+ // in case of su, 255 usually indicates access denied
+ if (shell.equals("su") && (process.exitValue() == 255)) {
+ res = null;
+ }
+ } catch (IOException e) {
+ // shell probably not found
+ res = null;
+ } catch (InterruptedException e) {
+ // this should really be re-thrown
+ res = null;
+ }
+
Debug.logCommand(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH)));
- return res;
- }
+ return res;
+ }
protected static String[] availableTestCommands = new String[] {
"echo -BOC-",
@@ -197,146 +197,146 @@ protected static boolean parseAvailableResult(List ret, boolean checkFor
return echo_seen;
}
- /**
- * This class provides utility functions to easily execute commands using SH
- */
- public static class SH {
- /**
- * Runs command and return output
- *
- * @param command The command to run
- * @return Output of the command, or null in case of an error
- */
- public static List run(String command) {
- return Shell.run("sh", new String[] { command }, null, false);
- }
-
- /**
- * Runs commands and return output
- *
- * @param commands The commands to run
- * @return Output of the commands, or null in case of an error
- */
- public static List run(List commands) {
- return Shell.run("sh", commands.toArray(new String[commands.size()]), null, false);
- }
-
- /**
- * Runs commands and return output
- *
- * @param commands The commands to run
- * @return Output of the commands, or null in case of an error
- */
- public static List run(String[] commands) {
- return Shell.run("sh", commands, null, false);
- }
- }
-
- /**
- * This class provides utility functions to easily execute commands using SU
- * (root shell), as well as detecting whether or not root is available, and
- * if so which version.
- */
- public static class SU {
- /**
- * Runs command as root (if available) and return output
- *
- * @param command The command to run
- * @return Output of the command, or null if root isn't available or in case of an error
- */
- public static List run(String command) {
- return Shell.run("su", new String[] { command }, null, false);
- }
-
- /**
- * Runs commands as root (if available) and return output
- *
- * @param commands The commands to run
- * @return Output of the commands, or null if root isn't available or in case of an error
- */
- public static List run(List commands) {
- return Shell.run("su", commands.toArray(new String[commands.size()]), null, false);
- }
-
- /**
- * Runs commands as root (if available) and return output
- *
- * @param commands The commands to run
- * @return Output of the commands, or null if root isn't available or in case of an error
- */
- public static List run(String[] commands) {
- return Shell.run("su", commands, null, false);
- }
-
- /**
- * Detects whether or not superuser access is available, by checking the output
- * of the "id" command if available, checking if a shell runs at all otherwise
- *
- * @return True if superuser access available
- */
- public static boolean available() {
- // this is only one of many ways this can be done
-
+ /**
+ * This class provides utility functions to easily execute commands using SH
+ */
+ public static class SH {
+ /**
+ * Runs command and return output
+ *
+ * @param command The command to run
+ * @return Output of the command, or null in case of an error
+ */
+ public static List run(String command) {
+ return Shell.run("sh", new String[] { command }, null, false);
+ }
+
+ /**
+ * Runs commands and return output
+ *
+ * @param commands The commands to run
+ * @return Output of the commands, or null in case of an error
+ */
+ public static List run(List commands) {
+ return Shell.run("sh", commands.toArray(new String[commands.size()]), null, false);
+ }
+
+ /**
+ * Runs commands and return output
+ *
+ * @param commands The commands to run
+ * @return Output of the commands, or null in case of an error
+ */
+ public static List run(String[] commands) {
+ return Shell.run("sh", commands, null, false);
+ }
+ }
+
+ /**
+ * This class provides utility functions to easily execute commands using SU
+ * (root shell), as well as detecting whether or not root is available, and
+ * if so which version.
+ */
+ public static class SU {
+ /**
+ * Runs command as root (if available) and return output
+ *
+ * @param command The command to run
+ * @return Output of the command, or null if root isn't available or in case of an error
+ */
+ public static List run(String command) {
+ return Shell.run("su", new String[] { command }, null, false);
+ }
+
+ /**
+ * Runs commands as root (if available) and return output
+ *
+ * @param commands The commands to run
+ * @return Output of the commands, or null if root isn't available or in case of an error
+ */
+ public static List run(List commands) {
+ return Shell.run("su", commands.toArray(new String[commands.size()]), null, false);
+ }
+
+ /**
+ * Runs commands as root (if available) and return output
+ *
+ * @param commands The commands to run
+ * @return Output of the commands, or null if root isn't available or in case of an error
+ */
+ public static List run(String[] commands) {
+ return Shell.run("su", commands, null, false);
+ }
+
+ /**
+ * Detects whether or not superuser access is available, by checking the output
+ * of the "id" command if available, checking if a shell runs at all otherwise
+ *
+ * @return True if superuser access available
+ */
+ public static boolean available() {
+ // this is only one of many ways this can be done
+
List ret = run(Shell.availableTestCommands);
return Shell.parseAvailableResult(ret, true);
- }
-
- /**
- * Detects the version of the su binary installed (if any), if supported by the binary.
- * Most binaries support two different version numbers, the public version that is
- * displayed to users, and an internal version number that is used for version number
- * comparisons. Returns null if su not available or retrieving the version isn't supported.
- *
- * Note that su binary version and GUI (APK) version can be completely different.
- *
- * @param internal Request human-readable version or application internal version
- * @return String containing the su version or null
- */
- public static String version(boolean internal) {
- // we add an additional exit call, because the command
- // line options are not available in all su versions,
- // thus potentially launching a shell instead
-
- List ret = Shell.run("sh", new String[] {
- internal ? "su -V" : "su -v",
- "exit"
- }, null, false);
- if (ret == null) return null;
-
- for (String line : ret) {
- if (!internal) {
- if (line.contains(".")) return line;
- } else {
- try {
- if (Integer.parseInt(line) > 0) return line;
- } catch(NumberFormatException e) {
- }
- }
- }
- return null;
- }
- }
-
- /**
- * Command result callback, notifies the recipient of the completion of a command
- * block, including the (last) exit code, and the full output
- */
- public interface OnCommandResultListener {
- /**
- * Command result callback
- *
- * Depending on how and on which thread the shell was created, this callback
- * may be executed on one of the gobbler threads. In that case, it is important
- * the callback returns as quickly as possible, as delays in this callback may
- * pause the native process or even result in a deadlock
- *
- * See {@link Shell.Interactive} for threading details
- *
- * @param commandCode Value previously supplied to addCommand
- * @param exitCode Exit code of the last command in the block
- * @param output All output generated by the command block
- */
- public void onCommandResult(int commandCode, int exitCode, List output);
+ }
+
+ /**
+ * Detects the version of the su binary installed (if any), if supported by the binary.
+ * Most binaries support two different version numbers, the public version that is
+ * displayed to users, and an internal version number that is used for version number
+ * comparisons. Returns null if su not available or retrieving the version isn't supported.
+ *
+ * Note that su binary version and GUI (APK) version can be completely different.
+ *
+ * @param internal Request human-readable version or application internal version
+ * @return String containing the su version or null
+ */
+ public static String version(boolean internal) {
+ // we add an additional exit call, because the command
+ // line options are not available in all su versions,
+ // thus potentially launching a shell instead
+
+ List ret = Shell.run("sh", new String[] {
+ internal ? "su -V" : "su -v",
+ "exit"
+ }, null, false);
+ if (ret == null) return null;
+
+ for (String line : ret) {
+ if (!internal) {
+ if (line.contains(".")) return line;
+ } else {
+ try {
+ if (Integer.parseInt(line) > 0) return line;
+ } catch(NumberFormatException e) {
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Command result callback, notifies the recipient of the completion of a command
+ * block, including the (last) exit code, and the full output
+ */
+ public interface OnCommandResultListener {
+ /**
+ * Command result callback
+ *
+ * Depending on how and on which thread the shell was created, this callback
+ * may be executed on one of the gobbler threads. In that case, it is important
+ * the callback returns as quickly as possible, as delays in this callback may
+ * pause the native process or even result in a deadlock
+ *
+ * See {@link Shell.Interactive} for threading details
+ *
+ * @param commandCode Value previously supplied to addCommand
+ * @param exitCode Exit code of the last command in the block
+ * @param output All output generated by the command block
+ */
+ public void onCommandResult(int commandCode, int exitCode, List output);
// for any onCommandResult callback
public static final int WATCHDOG_EXIT = -1;
@@ -346,190 +346,190 @@ public interface OnCommandResultListener {
public static final int SHELL_EXEC_FAILED = -3;
public static final int SHELL_WRONG_UID = -4;
public static final int SHELL_RUNNING = 0;
- }
-
- /**
+ }
+
+ /**
* Internal class to store command block properties
- */
- private static class Command {
- private static int commandCounter = 0;
-
- private final String[] commands;
- private final int code;
- private final OnCommandResultListener onCommandResultListener;
- private final String marker;
-
- public Command(String[] commands, int code, OnCommandResultListener onCommandResultListener) {
- this.commands = commands;
- this.code = code;
- this.onCommandResultListener = onCommandResultListener;
- this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter);
- }
- }
-
- /**
- * Builder class for {@link Shell.Interactive}
- */
- public static class Builder {
- private Handler handler = null;
- private boolean autoHandler = true;
- private String shell = "sh";
- private boolean wantSTDERR = false;
- private List commands = new LinkedList();
- private Map environment = new HashMap();
- private OnLineListener onSTDOUTLineListener = null;
- private OnLineListener onSTDERRLineListener = null;
+ */
+ private static class Command {
+ private static int commandCounter = 0;
+
+ private final String[] commands;
+ private final int code;
+ private final OnCommandResultListener onCommandResultListener;
+ private final String marker;
+
+ public Command(String[] commands, int code, OnCommandResultListener onCommandResultListener) {
+ this.commands = commands;
+ this.code = code;
+ this.onCommandResultListener = onCommandResultListener;
+ this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter);
+ }
+ }
+
+ /**
+ * Builder class for {@link Shell.Interactive}
+ */
+ public static class Builder {
+ private Handler handler = null;
+ private boolean autoHandler = true;
+ private String shell = "sh";
+ private boolean wantSTDERR = false;
+ private List commands = new LinkedList();
+ private Map environment = new HashMap();
+ private OnLineListener onSTDOUTLineListener = null;
+ private OnLineListener onSTDERRLineListener = null;
private int watchdogTimeout = 0;
-
- /**
- * Set a custom handler that will be used to post all callbacks to
- *
- * See {@link Shell.Interactive} for further details on threading and handlers
- *
- * @param handler Handler to use
- * @return This Builder object for method chaining
- */
- public Builder setHandler(Handler handler) { this.handler = handler; return this; }
-
- /**
- * Automatically create a handler if possible ? Default to true
- *
- * See {@link Shell.Interactive} for further details on threading and handlers
- *
- * @param autoHandler Auto-create handler ?
- * @return This Builder object for method chaining
- */
- public Builder setAutoHandler(boolean autoHandler) { this.autoHandler = autoHandler; return this; }
-
- /**
- * Set shell binary to use. Usually "sh" or "su", do not use a full path
- * unless you have a good reason to
- *
- * @param shell Shell to use
- * @return This Builder object for method chaining
- */
- public Builder setShell(String shell) { this.shell = shell; return this; }
-
- /**
- * Convenience function to set "sh" as used shell
- *
- * @return This Builder object for method chaining
- */
- public Builder useSH() { return setShell("sh"); }
-
- /**
- * Convenience function to set "su" as used shell
- *
- * @return This Builder object for method chaining
- */
- public Builder useSU() { return setShell("su"); }
-
- /**
- * Set if error output should be appended to command block result output
- *
- * @param wantSTDERR Want error output ?
- * @return This Builder object for method chaining
- */
- public Builder setWantSTDERR(boolean wantSTDERR) { this.wantSTDERR = wantSTDERR; return this; }
-
- /**
- * Add or update an environment variable
- *
- * @param key Key of the environment variable
- * @param value Value of the environment variable
- * @return This Builder object for method chaining
- */
- public Builder addEnvironment(String key, String value) { environment.put(key, value); return this; }
-
- /**
- * Add or update environment variables
- *
- * @param addEnvironment Map of environment variables
- * @return This Builder object for method chaining
- */
- public Builder addEnvironment(Map addEnvironment) { environment.putAll(addEnvironment); return this; }
-
- /**
- * Add a command to execute
- *
- * @param command Command to execute
- * @return This Builder object for method chaining
- */
- public Builder addCommand(String command) { return addCommand(command, 0, null); }
-
- /**
- * Add a command to execute, with a callback to be called on completion
- *
- * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
- *
- * @param command Command to execute
- * @param code User-defined value passed back to the callback
- * @param onCommandResultListener Callback to be called on completion
- * @return This Builder object for method chaining
- */
- public Builder addCommand(String command, int code, OnCommandResultListener onCommandResultListener) { return addCommand(new String[] { command }, code, onCommandResultListener); }
-
- /**
- * Add commands to execute
- *
- * @param commands Commands to execute
- * @return This Builder object for method chaining
- */
- public Builder addCommand(List commands) { return addCommand(commands, 0, null); }
-
- /**
- * Add commands to execute, with a callback to be called on completion (of all commands)
- *
- * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
- *
- * @param commands Commands to execute
- * @param code User-defined value passed back to the callback
- * @param onCommandResultListener Callback to be called on completion (of all commands)
- * @return This Builder object for method chaining
- */
- public Builder addCommand(List commands, int code, OnCommandResultListener onCommandResultListener) { return addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); }
-
- /**
- * Add commands to execute
- *
- * @param commands Commands to execute
- * @return This Builder object for method chaining
- */
- public Builder addCommand(String[] commands) { return addCommand(commands, 0, null); }
-
- /**
- * Add commands to execute, with a callback to be called on completion (of all commands)
- *
- * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
- *
- * @param commands Commands to execute
- * @param code User-defined value passed back to the callback
- * @param onCommandResultListener Callback to be called on completion (of all commands)
- * @return This Builder object for method chaining
- */
- public Builder addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) { this.commands.add(new Command(commands, code, onCommandResultListener)); return this; }
-
- /**
- * Set a callback called for every line output to STDOUT by the shell
- *
- * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
- *
- * @param onLineListener Callback to be called for each line
- * @return This Builder object for method chaining
- */
- public Builder setOnSTDOUTLineListener(OnLineListener onLineListener) { this.onSTDOUTLineListener = onLineListener; return this; }
-
- /**
- * Set a callback called for every line output to STDERR by the shell
- *
- * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
- *
- * @param onLineListener Callback to be called for each line
- * @return This Builder object for method chaining
- */
- public Builder setOnSTDERRLineListener(OnLineListener onLineListener) { this.onSTDERRLineListener = onLineListener; return this; }
-
- /**
+
+ /**
+ * Set a custom handler that will be used to post all callbacks to
+ *
+ * See {@link Shell.Interactive} for further details on threading and handlers
+ *
+ * @param handler Handler to use
+ * @return This Builder object for method chaining
+ */
+ public Builder setHandler(Handler handler) { this.handler = handler; return this; }
+
+ /**
+ * Automatically create a handler if possible ? Default to true
+ *
+ * See {@link Shell.Interactive} for further details on threading and handlers
+ *
+ * @param autoHandler Auto-create handler ?
+ * @return This Builder object for method chaining
+ */
+ public Builder setAutoHandler(boolean autoHandler) { this.autoHandler = autoHandler; return this; }
+
+ /**
+ * Set shell binary to use. Usually "sh" or "su", do not use a full path
+ * unless you have a good reason to
+ *
+ * @param shell Shell to use
+ * @return This Builder object for method chaining
+ */
+ public Builder setShell(String shell) { this.shell = shell; return this; }
+
+ /**
+ * Convenience function to set "sh" as used shell
+ *
+ * @return This Builder object for method chaining
+ */
+ public Builder useSH() { return setShell("sh"); }
+
+ /**
+ * Convenience function to set "su" as used shell
+ *
+ * @return This Builder object for method chaining
+ */
+ public Builder useSU() { return setShell("su"); }
+
+ /**
+ * Set if error output should be appended to command block result output
+ *
+ * @param wantSTDERR Want error output ?
+ * @return This Builder object for method chaining
+ */
+ public Builder setWantSTDERR(boolean wantSTDERR) { this.wantSTDERR = wantSTDERR; return this; }
+
+ /**
+ * Add or update an environment variable
+ *
+ * @param key Key of the environment variable
+ * @param value Value of the environment variable
+ * @return This Builder object for method chaining
+ */
+ public Builder addEnvironment(String key, String value) { environment.put(key, value); return this; }
+
+ /**
+ * Add or update environment variables
+ *
+ * @param addEnvironment Map of environment variables
+ * @return This Builder object for method chaining
+ */
+ public Builder addEnvironment(Map addEnvironment) { environment.putAll(addEnvironment); return this; }
+
+ /**
+ * Add a command to execute
+ *
+ * @param command Command to execute
+ * @return This Builder object for method chaining
+ */
+ public Builder addCommand(String command) { return addCommand(command, 0, null); }
+
+ /**
+ * Add a command to execute, with a callback to be called on completion
+ *
+ * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
+ *
+ * @param command Command to execute
+ * @param code User-defined value passed back to the callback
+ * @param onCommandResultListener Callback to be called on completion
+ * @return This Builder object for method chaining
+ */
+ public Builder addCommand(String command, int code, OnCommandResultListener onCommandResultListener) { return addCommand(new String[] { command }, code, onCommandResultListener); }
+
+ /**
+ * Add commands to execute
+ *
+ * @param commands Commands to execute
+ * @return This Builder object for method chaining
+ */
+ public Builder addCommand(List commands) { return addCommand(commands, 0, null); }
+
+ /**
+ * Add commands to execute, with a callback to be called on completion (of all commands)
+ *
+ * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
+ *
+ * @param commands Commands to execute
+ * @param code User-defined value passed back to the callback
+ * @param onCommandResultListener Callback to be called on completion (of all commands)
+ * @return This Builder object for method chaining
+ */
+ public Builder addCommand(List commands, int code, OnCommandResultListener onCommandResultListener) { return addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); }
+
+ /**
+ * Add commands to execute
+ *
+ * @param commands Commands to execute
+ * @return This Builder object for method chaining
+ */
+ public Builder addCommand(String[] commands) { return addCommand(commands, 0, null); }
+
+ /**
+ * Add commands to execute, with a callback to be called on completion (of all commands)
+ *
+ * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
+ *
+ * @param commands Commands to execute
+ * @param code User-defined value passed back to the callback
+ * @param onCommandResultListener Callback to be called on completion (of all commands)
+ * @return This Builder object for method chaining
+ */
+ public Builder addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) { this.commands.add(new Command(commands, code, onCommandResultListener)); return this; }
+
+ /**
+ * Set a callback called for every line output to STDOUT by the shell
+ *
+ * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
+ *
+ * @param onLineListener Callback to be called for each line
+ * @return This Builder object for method chaining
+ */
+ public Builder setOnSTDOUTLineListener(OnLineListener onLineListener) { this.onSTDOUTLineListener = onLineListener; return this; }
+
+ /**
+ * Set a callback called for every line output to STDERR by the shell
+ *
+ * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
+ *
+ * @param onLineListener Callback to be called for each line
+ * @return This Builder object for method chaining
+ */
+ public Builder setOnSTDERRLineListener(OnLineListener onLineListener) { this.onSTDERRLineListener = onLineListener; return this; }
+
+ /**
* Enable command timeout callback
*
* This will invoke the onCommandResult() callback with exitCode WATCHDOG_EXIT if a command takes longer than watchdogTimeout
@@ -551,14 +551,14 @@ public static class Builder {
* @param useMinimal true for reduced output, false for full output
* @return This Builder object for method chaining
*/
- public Builder setMinimalLogging(boolean useMinimal) {
- Debug.setLogTypeEnabled(Debug.LOG_COMMAND | Debug.LOG_OUTPUT, !useMinimal);
+ public Builder setMinimalLogging(boolean useMinimal) {
+ Debug.setLogTypeEnabled(Debug.LOG_COMMAND | Debug.LOG_OUTPUT, !useMinimal);
return this;
}
/**
- * Construct a {@link Shell.Interactive} instance, and start the shell
- */
+ * Construct a {@link Shell.Interactive} instance, and start the shell
+ */
public Interactive open() { return new Interactive(this, null); }
/**
@@ -570,112 +570,112 @@ public Builder setMinimalLogging(boolean useMinimal) {
public Interactive open(OnCommandResultListener onCommandResultListener) {
return new Interactive(this, onCommandResultListener);
}
- }
-
- /**
- *
An interactive shell - initially created with {@link Shell.Builder} - that
- * executes blocks of commands you supply in the background, optionally calling
- * callbacks as each block completes.
- *
- * STDERR output can be supplied as well, but due to compatibility with older
- * Android versions, wantSTDERR is not implemented using redirectErrorStream,
- * but rather appended to the output. STDOUT and STDERR are thus not guaranteed to
- * be in the correct order in the output.
- *
+ }
+
+ /**
+ * An interactive shell - initially created with {@link Shell.Builder} - that
+ * executes blocks of commands you supply in the background, optionally calling
+ * callbacks as each block completes.
+ *
+ * STDERR output can be supplied as well, but due to compatibility with older
+ * Android versions, wantSTDERR is not implemented using redirectErrorStream,
+ * but rather appended to the output. STDOUT and STDERR are thus not guaranteed to
+ * be in the correct order in the output.
+ *
* Note as well that the close() and waitForIdle() methods will intentionally
* crash when run in debug mode from the main thread of the application. Any blocking
* call should be run from a background thread.
- *
- * When in debug mode, the code will also excessively log the commands passed to
- * and the output returned from the shell.
- *
- * Though this function uses background threads to gobble STDOUT and STDERR so
- * a deadlock does not occur if the shell produces massive output, the output is
- * still stored in a List<String>, and as such doing something like 'ls -lR /'
- * will probably have you run out of memory when using a
- * {@link Shell.OnCommandResultListener}. A work-around is to not supply this callback,
- * but using (only) {@link Shell.Builder#setOnSTDOUTLineListener(OnLineListener)}. This
- * way, an internal buffer will not be created and wasting your memory.
- *
- * Callbacks, threads and handlers
- *
- * On which thread the callbacks execute is dependent on your initialization. You can
- * supply a custom Handler using {@link Shell.Builder#setHandler(Handler)} if needed.
- * If you do not supply a custom Handler - unless you set {@link Shell.Builder#setAutoHandler(boolean)}
- * to false - a Handler will be auto-created if the thread used for instantiation
- * of the object has a Looper.
- *
- * If no Handler was supplied and it was also not auto-created, all callbacks will
- * be called from either the STDOUT or STDERR gobbler threads. These are important
- * threads that should be blocked as little as possible, as blocking them may in rare
- * cases pause the native process or even create a deadlock.
- *
+ *
+ * When in debug mode, the code will also excessively log the commands passed to
+ * and the output returned from the shell.
+ *
+ * Though this function uses background threads to gobble STDOUT and STDERR so
+ * a deadlock does not occur if the shell produces massive output, the output is
+ * still stored in a List<String>, and as such doing something like 'ls -lR /'
+ * will probably have you run out of memory when using a
+ * {@link Shell.OnCommandResultListener}. A work-around is to not supply this callback,
+ * but using (only) {@link Shell.Builder#setOnSTDOUTLineListener(OnLineListener)}. This
+ * way, an internal buffer will not be created and wasting your memory.
+ *
+ * Callbacks, threads and handlers
+ *
+ * On which thread the callbacks execute is dependent on your initialization. You can
+ * supply a custom Handler using {@link Shell.Builder#setHandler(Handler)} if needed.
+ * If you do not supply a custom Handler - unless you set {@link Shell.Builder#setAutoHandler(boolean)}
+ * to false - a Handler will be auto-created if the thread used for instantiation
+ * of the object has a Looper.
+ *
+ * If no Handler was supplied and it was also not auto-created, all callbacks will
+ * be called from either the STDOUT or STDERR gobbler threads. These are important
+ * threads that should be blocked as little as possible, as blocking them may in rare
+ * cases pause the native process or even create a deadlock.
+ *
* The main thread must certainly have a Looper, thus if you call {@link Shell.Builder#open()}
- * from the main thread, a handler will (by default) be auto-created, and all the callbacks
- * will be called on the main thread. While this is often convenient and easy to code with,
- * you should be aware that if your callbacks are 'expensive' to execute, this may negatively
- * impact UI performance.
- *
- * Background threads usually do not have a Looper, so calling {@link Shell.Builder#open()}
- * from such a background thread will (by default) result in all the callbacks being executed
- * in one of the gobbler threads. You will have to make sure the code you execute in these callbacks
- * is thread-safe.
- */
- public static class Interactive {
- private final Handler handler;
- private final boolean autoHandler;
- private final String shell;
- private final boolean wantSTDERR;
- private final List commands;
- private final Map environment;
- private final OnLineListener onSTDOUTLineListener;
- private final OnLineListener onSTDERRLineListener;
+ * from the main thread, a handler will (by default) be auto-created, and all the callbacks
+ * will be called on the main thread. While this is often convenient and easy to code with,
+ * you should be aware that if your callbacks are 'expensive' to execute, this may negatively
+ * impact UI performance.
+ *
+ * Background threads usually do not have a Looper, so calling {@link Shell.Builder#open()}
+ * from such a background thread will (by default) result in all the callbacks being executed
+ * in one of the gobbler threads. You will have to make sure the code you execute in these callbacks
+ * is thread-safe.
+ */
+ public static class Interactive {
+ private final Handler handler;
+ private final boolean autoHandler;
+ private final String shell;
+ private final boolean wantSTDERR;
+ private final List commands;
+ private final Map environment;
+ private final OnLineListener onSTDOUTLineListener;
+ private final OnLineListener onSTDERRLineListener;
private int watchdogTimeout;
-
- private Process process = null;
- private DataOutputStream STDIN = null;
- private StreamGobbler STDOUT = null;
- private StreamGobbler STDERR = null;
+
+ private Process process = null;
+ private DataOutputStream STDIN = null;
+ private StreamGobbler STDOUT = null;
+ private StreamGobbler STDERR = null;
private ScheduledThreadPoolExecutor watchdog = null;
-
- private volatile boolean running = false;
- private volatile boolean idle = true; // read/write only synchronized
- private volatile boolean closed = true;
- private volatile int callbacks = 0;
+
+ private volatile boolean running = false;
+ private volatile boolean idle = true; // read/write only synchronized
+ private volatile boolean closed = true;
+ private volatile int callbacks = 0;
private volatile int watchdogCount;
-
- private Object idleSync = new Object();
- private Object callbackSync = new Object();
-
- private volatile int lastExitCode = 0;
- private volatile String lastMarkerSTDOUT = null;
- private volatile String lastMarkerSTDERR = null;
- private volatile Command command = null;
- private volatile List buffer = null;
-
- /**
- * The only way to create an instance: Shell.Builder::open()
- *
- * @param builder Builder class to take values from
- */
+
+ private Object idleSync = new Object();
+ private Object callbackSync = new Object();
+
+ private volatile int lastExitCode = 0;
+ private volatile String lastMarkerSTDOUT = null;
+ private volatile String lastMarkerSTDERR = null;
+ private volatile Command command = null;
+ private volatile List buffer = null;
+
+ /**
+ * The only way to create an instance: Shell.Builder::open()
+ *
+ * @param builder Builder class to take values from
+ */
private Interactive(final Builder builder, final OnCommandResultListener onCommandResultListener) {
- autoHandler = builder.autoHandler;
- shell = builder.shell;
- wantSTDERR = builder.wantSTDERR;
- commands = builder.commands;
- environment = builder.environment;
- onSTDOUTLineListener = builder.onSTDOUTLineListener;
- onSTDERRLineListener = builder.onSTDERRLineListener;
+ autoHandler = builder.autoHandler;
+ shell = builder.shell;
+ wantSTDERR = builder.wantSTDERR;
+ commands = builder.commands;
+ environment = builder.environment;
+ onSTDOUTLineListener = builder.onSTDOUTLineListener;
+ onSTDERRLineListener = builder.onSTDERRLineListener;
watchdogTimeout = builder.watchdogTimeout;
-
- // If a looper is available, we offload the callbacks from the gobbling threads
- // to whichever thread created us. Would normally do this in open(),
- // but then we could not declare handler as final
- if ((Looper.myLooper() != null) && (builder.handler == null) && autoHandler) {
- handler = new Handler();
- } else {
- handler = builder.handler;
- }
+
+ // If a looper is available, we offload the callbacks from the gobbling threads
+ // to whichever thread created us. Would normally do this in open(),
+ // but then we could not declare handler as final
+ if ((Looper.myLooper() != null) && (builder.handler == null) && autoHandler) {
+ handler = new Handler();
+ } else {
+ handler = builder.handler;
+ }
boolean ret = open();
if (onCommandResultListener == null) {
@@ -699,81 +699,81 @@ public void onCommandResult(int commandCode, int exitCode, List output)
onCommandResultListener.onCommandResult(0, exitCode, output);
}
});
- }
-
- @Override
- protected void finalize() throws Throwable {
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
if (!closed && Debug.getSanityChecksEnabledEffective()) {
- // waste of resources
- Debug.log(ShellNotClosedException.EXCEPTION_NOT_CLOSED);
- throw new ShellNotClosedException();
- }
- super.finalize();
- }
-
- /**
- * Add a command to execute
- *
- * @param command Command to execute
- */
- public void addCommand(String command) { addCommand(command, 0, null); }
-
- /**
- * Add a command to execute, with a callback to be called on completion
- *
- * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
- *
- * @param command Command to execute
- * @param code User-defined value passed back to the callback
- * @param onCommandResultListener Callback to be called on completion
- */
- public void addCommand(String command, int code, OnCommandResultListener onCommandResultListener) { addCommand(new String[] { command }, code, onCommandResultListener); }
-
- /**
- * Add commands to execute
- *
- * @param commands Commands to execute
- */
- public void addCommand(List commands) { addCommand(commands, 0, null); }
-
- /**
- * Add commands to execute, with a callback to be called on completion (of all commands)
- *
- * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
- *
- * @param commands Commands to execute
- * @param code User-defined value passed back to the callback
- * @param onCommandResultListener Callback to be called on completion (of all commands)
- */
- public void addCommand(List commands, int code, OnCommandResultListener onCommandResultListener) { addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); }
-
- /**
- * Add commands to execute
- *
- * @param commands Commands to execute
- */
- public void addCommand(String[] commands) { addCommand(commands, 0, null); }
-
- /**
- * Add commands to execute, with a callback to be called on completion (of all commands)
- *
- * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
- *
- * @param commands Commands to execute
- * @param code User-defined value passed back to the callback
- * @param onCommandResultListener Callback to be called on completion (of all commands)
- */
- public synchronized void addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) {
- this.commands.add(new Command(commands, code, onCommandResultListener));
- runNextCommand();
- }
-
- /**
- * Run the next command if any and if ready, signals idle state if no commands left
- */
- private void runNextCommand() {
- runNextCommand(true);
- }
+ // waste of resources
+ Debug.log(ShellNotClosedException.EXCEPTION_NOT_CLOSED);
+ throw new ShellNotClosedException();
+ }
+ super.finalize();
+ }
+
+ /**
+ * Add a command to execute
+ *
+ * @param command Command to execute
+ */
+ public void addCommand(String command) { addCommand(command, 0, null); }
+
+ /**
+ * Add a command to execute, with a callback to be called on completion
+ *
+ * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
+ *
+ * @param command Command to execute
+ * @param code User-defined value passed back to the callback
+ * @param onCommandResultListener Callback to be called on completion
+ */
+ public void addCommand(String command, int code, OnCommandResultListener onCommandResultListener) { addCommand(new String[] { command }, code, onCommandResultListener); }
+
+ /**
+ * Add commands to execute
+ *
+ * @param commands Commands to execute
+ */
+ public void addCommand(List commands) { addCommand(commands, 0, null); }
+
+ /**
+ * Add commands to execute, with a callback to be called on completion (of all commands)
+ *
+ * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
+ *
+ * @param commands Commands to execute
+ * @param code User-defined value passed back to the callback
+ * @param onCommandResultListener Callback to be called on completion (of all commands)
+ */
+ public void addCommand(List commands, int code, OnCommandResultListener onCommandResultListener) { addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); }
+
+ /**
+ * Add commands to execute
+ *
+ * @param commands Commands to execute
+ */
+ public void addCommand(String[] commands) { addCommand(commands, 0, null); }
+
+ /**
+ * Add commands to execute, with a callback to be called on completion (of all commands)
+ *
+ * The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details
+ *
+ * @param commands Commands to execute
+ * @param code User-defined value passed back to the callback
+ * @param onCommandResultListener Callback to be called on completion (of all commands)
+ */
+ public synchronized void addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) {
+ this.commands.add(new Command(commands, code, onCommandResultListener));
+ runNextCommand();
+ }
+
+ /**
+ * Run the next command if any and if ready, signals idle state if no commands left
+ */
+ private void runNextCommand() {
+ runNextCommand(true);
+ }
/**
* Called from a ScheduledThreadPoolExecutor timer thread every second when there is an outstanding command
@@ -781,9 +781,9 @@ private void runNextCommand() {
private synchronized void handleWatchdog() {
final int exitCode;
- if (watchdog == null) return;
- if (watchdogTimeout == 0) return;
-
+ if (watchdog == null) return;
+ if (watchdogTimeout == 0) return;
+
if (!isRunning()) {
exitCode = OnCommandResultListener.SHELL_DIED;
Debug.log(String.format("[%s%%] SHELL_DIED", shell.toUpperCase(Locale.ENGLISH)));
@@ -834,196 +834,196 @@ private void stopWatchdog() {
watchdog = null;
}
}
-
- /**
- * Run the next command if any and if ready
- *
- * @param notifyIdle signals idle state if no commands left ?
- */
- private void runNextCommand(boolean notifyIdle) {
- // must always be called from a synchronized method
-
- boolean running = isRunning();
- if (!running) idle = true;
-
- if (running && idle && (commands.size() > 0)) {
- Command command = commands.get(0);
- commands.remove(0);
-
- buffer = null;
- lastExitCode = 0;
- lastMarkerSTDOUT = null;
- lastMarkerSTDERR = null;
-
- if (command.commands.length > 0) {
- try {
- if (command.onCommandResultListener != null) {
- // no reason to store the output if we don't have an OnCommandResultListener
- // user should catch the output with an OnLineListener in this case
- buffer = Collections.synchronizedList(new ArrayList());
- }
-
- idle = false;
- this.command = command;
+
+ /**
+ * Run the next command if any and if ready
+ *
+ * @param notifyIdle signals idle state if no commands left ?
+ */
+ private void runNextCommand(boolean notifyIdle) {
+ // must always be called from a synchronized method
+
+ boolean running = isRunning();
+ if (!running) idle = true;
+
+ if (running && idle && (commands.size() > 0)) {
+ Command command = commands.get(0);
+ commands.remove(0);
+
+ buffer = null;
+ lastExitCode = 0;
+ lastMarkerSTDOUT = null;
+ lastMarkerSTDERR = null;
+
+ if (command.commands.length > 0) {
+ try {
+ if (command.onCommandResultListener != null) {
+ // no reason to store the output if we don't have an OnCommandResultListener
+ // user should catch the output with an OnLineListener in this case
+ buffer = Collections.synchronizedList(new ArrayList());
+ }
+
+ idle = false;
+ this.command = command;
startWatchdog();
- for (String write : command.commands) {
+ for (String write : command.commands) {
Debug.logCommand(String.format("[%s+] %s", shell.toUpperCase(Locale.ENGLISH), write));
- STDIN.write((write + "\n").getBytes("UTF-8"));
- }
- STDIN.write(("echo " + command.marker + " $?\n").getBytes("UTF-8"));
- STDIN.write(("echo " + command.marker + " >&2\n").getBytes("UTF-8"));
- STDIN.flush();
- } catch (IOException e) {
- }
- } else {
- runNextCommand(false);
- }
- } else if (!running) {
- // our shell died for unknown reasons - abort all submissions
- while (commands.size() > 0) {
- postCallback(commands.remove(0), OnCommandResultListener.SHELL_DIED, null);
- }
- }
-
- if (idle && notifyIdle) {
- synchronized(idleSync) {
- idleSync.notifyAll();
- }
- }
- }
-
- /**
- * Processes a STDOUT/STDERR line containing an end/exitCode marker
- */
- private synchronized void processMarker() {
- if (command.marker.equals(lastMarkerSTDOUT) && (command.marker.equals(lastMarkerSTDERR))) {
- if (buffer != null) {
- postCallback(command, lastExitCode, buffer);
- }
-
+ STDIN.write((write + "\n").getBytes("UTF-8"));
+ }
+ STDIN.write(("echo " + command.marker + " $?\n").getBytes("UTF-8"));
+ STDIN.write(("echo " + command.marker + " >&2\n").getBytes("UTF-8"));
+ STDIN.flush();
+ } catch (IOException e) {
+ }
+ } else {
+ runNextCommand(false);
+ }
+ } else if (!running) {
+ // our shell died for unknown reasons - abort all submissions
+ while (commands.size() > 0) {
+ postCallback(commands.remove(0), OnCommandResultListener.SHELL_DIED, null);
+ }
+ }
+
+ if (idle && notifyIdle) {
+ synchronized(idleSync) {
+ idleSync.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Processes a STDOUT/STDERR line containing an end/exitCode marker
+ */
+ private synchronized void processMarker() {
+ if (command.marker.equals(lastMarkerSTDOUT) && (command.marker.equals(lastMarkerSTDERR))) {
+ if (buffer != null) {
+ postCallback(command, lastExitCode, buffer);
+ }
+
stopWatchdog();
- command = null;
- buffer = null;
- idle = true;
- runNextCommand();
- }
- }
-
- /**
- * Process a normal STDOUT/STDERR line
- *
- * @param line Line to process
- * @param listener Callback to call or null
- */
- private synchronized void processLine(String line, OnLineListener listener) {
- if (listener != null) {
- if (handler != null) {
- final String fLine = line;
- final OnLineListener fListener = listener;
-
- startCallback();
- handler.post(new Runnable() {
- @Override
- public void run() {
- try {
- fListener.onLine(fLine);
- } finally {
- endCallback();
- }
- }
- });
- } else {
- listener.onLine(line);
- }
- }
- }
-
- /**
- * Add line to internal buffer
- *
- * @param line Line to add
- */
- private synchronized void addBuffer(String line) {
- if (buffer != null) {
- buffer.add(line);
- }
- }
-
- /**
- * Increase callback counter
- */
- private void startCallback() {
- synchronized (callbackSync) {
- callbacks++;
- }
- }
-
- /**
- * Schedule a callback to run on the appropriate thread
- */
- private void postCallback(final Command fCommand, final int fExitCode, final List fOutput) {
- if (fCommand.onCommandResultListener == null) {
- return;
- }
- if (handler == null) {
- fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fOutput);
- return;
- }
- startCallback();
- handler.post(new Runnable() {
- @Override
- public void run() {
- try {
- fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fOutput);
- } finally {
- endCallback();
- }
- }
- });
- }
-
- /**
- * Decrease callback counter, signals callback complete state when dropped to 0
- */
- private void endCallback() {
- synchronized (callbackSync) {
- callbacks--;
- if (callbacks == 0) {
- callbackSync.notifyAll();
- }
- }
- }
-
- /**
+ command = null;
+ buffer = null;
+ idle = true;
+ runNextCommand();
+ }
+ }
+
+ /**
+ * Process a normal STDOUT/STDERR line
+ *
+ * @param line Line to process
+ * @param listener Callback to call or null
+ */
+ private synchronized void processLine(String line, OnLineListener listener) {
+ if (listener != null) {
+ if (handler != null) {
+ final String fLine = line;
+ final OnLineListener fListener = listener;
+
+ startCallback();
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ fListener.onLine(fLine);
+ } finally {
+ endCallback();
+ }
+ }
+ });
+ } else {
+ listener.onLine(line);
+ }
+ }
+ }
+
+ /**
+ * Add line to internal buffer
+ *
+ * @param line Line to add
+ */
+ private synchronized void addBuffer(String line) {
+ if (buffer != null) {
+ buffer.add(line);
+ }
+ }
+
+ /**
+ * Increase callback counter
+ */
+ private void startCallback() {
+ synchronized (callbackSync) {
+ callbacks++;
+ }
+ }
+
+ /**
+ * Schedule a callback to run on the appropriate thread
+ */
+ private void postCallback(final Command fCommand, final int fExitCode, final List fOutput) {
+ if (fCommand.onCommandResultListener == null) {
+ return;
+ }
+ if (handler == null) {
+ fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fOutput);
+ return;
+ }
+ startCallback();
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fOutput);
+ } finally {
+ endCallback();
+ }
+ }
+ });
+ }
+
+ /**
+ * Decrease callback counter, signals callback complete state when dropped to 0
+ */
+ private void endCallback() {
+ synchronized (callbackSync) {
+ callbacks--;
+ if (callbacks == 0) {
+ callbackSync.notifyAll();
+ }
+ }
+ }
+
+ /**
* Internal call that launches the shell, starts gobbling, and starts executing commands.
- * See {@link Shell.Interactive}
- *
- * @return Opened successfully ?
- */
- private synchronized boolean open() {
+ * See {@link Shell.Interactive}
+ *
+ * @return Opened successfully ?
+ */
+ private synchronized boolean open() {
Debug.log(String.format("[%s%%] START", shell.toUpperCase(Locale.ENGLISH)));
-
- try {
- // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers
- if (environment.size() == 0) {
- process = Runtime.getRuntime().exec(shell);
- } else {
- Map newEnvironment = new HashMap();
- newEnvironment.putAll(System.getenv());
- newEnvironment.putAll(environment);
- int i = 0;
- String[] env = new String[newEnvironment.size()];
- for (Map.Entry entry : newEnvironment.entrySet()) {
- env[i] = entry.getKey() + "=" + entry.getValue();
- i++;
- }
- process = Runtime.getRuntime().exec(shell, env);
- }
-
- STDIN = new DataOutputStream(process.getOutputStream());
+
+ try {
+ // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers
+ if (environment.size() == 0) {
+ process = Runtime.getRuntime().exec(shell);
+ } else {
+ Map newEnvironment = new HashMap();
+ newEnvironment.putAll(System.getenv());
+ newEnvironment.putAll(environment);
+ int i = 0;
+ String[] env = new String[newEnvironment.size()];
+ for (Map.Entry entry : newEnvironment.entrySet()) {
+ env[i] = entry.getKey() + "=" + entry.getValue();
+ i++;
+ }
+ process = Runtime.getRuntime().exec(shell, env);
+ }
+
+ STDIN = new DataOutputStream(process.getOutputStream());
STDOUT = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "-", process.getInputStream(), new OnLineListener() {
- @Override
- public void onLine(String line) {
+ @Override
+ public void onLine(String line) {
synchronized (Interactive.this) {
if (command == null) {
return;
@@ -1038,13 +1038,13 @@ public void onLine(String line) {
} else {
addBuffer(line);
processLine(line, onSTDOUTLineListener);
- }
- }
- }
- });
+ }
+ }
+ }
+ });
STDERR = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "*", process.getErrorStream(), new OnLineListener() {
- @Override
- public void onLine(String line) {
+ @Override
+ public void onLine(String line) {
synchronized (Interactive.this) {
if (command == null) {
return;
@@ -1056,77 +1056,77 @@ public void onLine(String line) {
if (wantSTDERR) addBuffer(line);
processLine(line, onSTDERRLineListener);
}
- }
- }
- });
-
- // start gobbling and write our commands to the shell
- STDOUT.start();
- STDERR.start();
-
- running = true;
- closed = false;
-
- runNextCommand();
-
- return true;
- } catch (IOException e) {
- // shell probably not found
- return false;
- }
- }
-
- /**
- * Close shell and clean up all resources. Call this when you are done with the shell.
- * If the shell is not idle (all commands completed) you should not call this method
- * from the main UI thread because it may block for a long time. This method will
- * intentionally crash your app (if in debug mode) if you try to do this anyway.
- */
- public void close() {
- boolean _idle = isIdle(); // idle must be checked synchronized
-
- synchronized (this) {
- if (!running) return;
- running = false;
- closed = true;
- }
-
- // This method should not be called from the main thread unless the shell is idle
- // and can be cleaned up with (minimal) waiting. Only throw in debug mode.
+ }
+ }
+ });
+
+ // start gobbling and write our commands to the shell
+ STDOUT.start();
+ STDERR.start();
+
+ running = true;
+ closed = false;
+
+ runNextCommand();
+
+ return true;
+ } catch (IOException e) {
+ // shell probably not found
+ return false;
+ }
+ }
+
+ /**
+ * Close shell and clean up all resources. Call this when you are done with the shell.
+ * If the shell is not idle (all commands completed) you should not call this method
+ * from the main UI thread because it may block for a long time. This method will
+ * intentionally crash your app (if in debug mode) if you try to do this anyway.
+ */
+ public void close() {
+ boolean _idle = isIdle(); // idle must be checked synchronized
+
+ synchronized (this) {
+ if (!running) return;
+ running = false;
+ closed = true;
+ }
+
+ // This method should not be called from the main thread unless the shell is idle
+ // and can be cleaned up with (minimal) waiting. Only throw in debug mode.
if (!_idle && Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) {
- Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE);
- throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE);
- }
-
- if (!_idle) waitForIdle();
-
- try {
- STDIN.write(("exit\n").getBytes("UTF-8"));
- STDIN.flush();
-
- // wait for our process to finish, while we gobble away in the background
- process.waitFor();
-
- // make sure our threads are done gobbling, our streams are closed, and the process is
- // destroyed - while the latter two 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
- try {
- STDIN.close();
- } catch (IOException e) {
- }
- STDOUT.join();
- STDERR.join();
+ Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE);
+ throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE);
+ }
+
+ if (!_idle) waitForIdle();
+
+ try {
+ STDIN.write(("exit\n").getBytes("UTF-8"));
+ STDIN.flush();
+
+ // wait for our process to finish, while we gobble away in the background
+ process.waitFor();
+
+ // make sure our threads are done gobbling, our streams are closed, and the process is
+ // destroyed - while the latter two 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
+ try {
+ STDIN.close();
+ } catch (IOException e) {
+ }
+ STDOUT.join();
+ STDERR.join();
stopWatchdog();
- process.destroy();
- } catch (IOException e) {
- // shell probably not found
- } catch (InterruptedException e) {
- // this should really be re-thrown
- }
-
+ process.destroy();
+ } catch (IOException e) {
+ // shell probably not found
+ } catch (InterruptedException e) {
+ // this should really be re-thrown
+ }
+
Debug.log(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH)));
- }
+ }
/**
* Try to clean up as much as possible from a shell that's gotten itself wedged.
@@ -1140,114 +1140,114 @@ public synchronized void kill() {
try {
STDIN.close();
} catch (IOException e) {
- }
+ }
try {
- process.destroy();
- } catch (Exception e) {
+ process.destroy();
+ } catch (Exception e) {
}
}
- /**
+ /**
* Is our shell still running ?
- *
- * @return Shell running ?
- */
- public boolean isRunning() {
- try {
- // if this throws, we're still running
- process.exitValue();
- return false;
- } catch (IllegalThreadStateException e) {
- }
- return true;
- }
-
- /**
- * Have all commands completed executing ?
- *
- * @return Shell idle ?
- */
- public synchronized boolean isIdle() {
- if (!isRunning()) {
- idle = true;
- synchronized(idleSync) {
- idleSync.notifyAll();
- }
- }
- return idle;
- }
-
- /**
- * Wait for idle state. As this is a blocking call, you should not call it from the main UI thread.
- * If you do so and debug mode is enabled, this method will intentionally crash your app.
- *
- * If not interrupted, this method will not return until all commands have finished executing.
- * Note that this does not necessarily mean that all the callbacks have fired yet.
- *
- * If no Handler is used, all callbacks will have been executed when this method returns. If
- * a Handler is used, and this method is called from a different thread than associated with the
- * Handler's Looper, all callbacks will have been executed when this method returns as well.
- * If however a Handler is used but this method is called from the same thread as associated
- * with the Handler's Looper, there is no way to know.
- *
+ *
+ * @return Shell running ?
+ */
+ public boolean isRunning() {
+ try {
+ // if this throws, we're still running
+ process.exitValue();
+ return false;
+ } catch (IllegalThreadStateException e) {
+ }
+ return true;
+ }
+
+ /**
+ * Have all commands completed executing ?
+ *
+ * @return Shell idle ?
+ */
+ public synchronized boolean isIdle() {
+ if (!isRunning()) {
+ idle = true;
+ synchronized(idleSync) {
+ idleSync.notifyAll();
+ }
+ }
+ return idle;
+ }
+
+ /**
+ * Wait for idle state. As this is a blocking call, you should not call it from the main UI thread.
+ * If you do so and debug mode is enabled, this method will intentionally crash your app.
+ *
+ * If not interrupted, this method will not return until all commands have finished executing.
+ * Note that this does not necessarily mean that all the callbacks have fired yet.
+ *
+ * If no Handler is used, all callbacks will have been executed when this method returns. If
+ * a Handler is used, and this method is called from a different thread than associated with the
+ * Handler's Looper, all callbacks will have been executed when this method returns as well.
+ * If however a Handler is used but this method is called from the same thread as associated
+ * with the Handler's Looper, there is no way to know.
+ *
* In practice this means that in most simple cases all callbacks will have completed when this
- * method returns, but if you actually depend on this behavior, you should make certain this is
- * indeed the case.
- *
- * See {@link Shell.Interactive} for further details on threading and handlers
- *
- * @return True if wait complete, false if wait interrupted
- */
- public boolean waitForIdle() {
+ * method returns, but if you actually depend on this behavior, you should make certain this is
+ * indeed the case.
+ *
+ * See {@link Shell.Interactive} for further details on threading and handlers
+ *
+ * @return True if wait complete, false if wait interrupted
+ */
+ public boolean waitForIdle() {
if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) {
- Debug.log(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE);
- throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE);
- }
-
- if (isRunning()) {
- synchronized (idleSync) {
- while (!idle) {
- try {
- idleSync.wait();
- } catch (InterruptedException e) {
- return false;
- }
- }
- }
-
- if (
- (handler != null) &&
- (handler.getLooper() != null) &&
- (handler.getLooper() != Looper.myLooper())
- ) {
- // If the callbacks are posted to a different thread than this one, we can wait until
- // all callbacks have called before returning. If we don't use a Handler at all,
- // the callbacks are already called before we get here. If we do use a Handler but
- // we use the same Looper, waiting here would actually block the callbacks from being
- // called
-
- synchronized (callbackSync) {
- while (callbacks > 0) {
- try {
- callbackSync.wait();
- } catch (InterruptedException e) {
- return false;
- }
- }
- }
- }
- }
-
- return true;
- }
-
- /**
- * Are we using a Handler to post callbacks ?
- *
- * @return Handler used ?
- */
- public boolean hasHandler() {
- return (handler != null);
- }
- }
-}
+ Debug.log(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE);
+ throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE);
+ }
+
+ if (isRunning()) {
+ synchronized (idleSync) {
+ while (!idle) {
+ try {
+ idleSync.wait();
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+ }
+
+ if (
+ (handler != null) &&
+ (handler.getLooper() != null) &&
+ (handler.getLooper() != Looper.myLooper())
+ ) {
+ // If the callbacks are posted to a different thread than this one, we can wait until
+ // all callbacks have called before returning. If we don't use a Handler at all,
+ // the callbacks are already called before we get here. If we do use a Handler but
+ // we use the same Looper, waiting here would actually block the callbacks from being
+ // called
+
+ synchronized (callbackSync) {
+ while (callbacks > 0) {
+ try {
+ callbackSync.wait();
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Are we using a Handler to post callbacks ?
+ *
+ * @return Handler used ?
+ */
+ public boolean hasHandler() {
+ return (handler != null);
+ }
+ }
+}
diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java b/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java
index ec34ff8..6b2d7ae 100644
--- a/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java
+++ b/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java
@@ -1,29 +1,29 @@
-/*
- * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.chainfire.libsuperuser;
-
-/**
- * Exception class used to notify developer that a shell was not close()d
- */
-@SuppressWarnings("serial")
-public class ShellNotClosedException extends RuntimeException {
- public static final String EXCEPTION_NOT_CLOSED = "Application did not close() interactive shell";
-
- public ShellNotClosedException() {
- super(EXCEPTION_NOT_CLOSED);
- }
-}
+/*
+ * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.chainfire.libsuperuser;
+
+/**
+ * Exception class used to notify developer that a shell was not close()d
+ */
+@SuppressWarnings("serial")
+public class ShellNotClosedException extends RuntimeException {
+ public static final String EXCEPTION_NOT_CLOSED = "Application did not close() interactive shell";
+
+ public ShellNotClosedException() {
+ super(EXCEPTION_NOT_CLOSED);
+ }
+}
diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java b/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java
index 91bc57a..70cf23d 100644
--- a/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java
+++ b/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java
@@ -1,32 +1,32 @@
-/*
- * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.chainfire.libsuperuser;
-
-/**
- * Exception class used to crash application when shell commands are executed
- * from the main thread, and we are in debug mode.
- */
-@SuppressWarnings("serial")
-public class ShellOnMainThreadException extends RuntimeException {
- public static final String EXCEPTION_COMMAND = "Application attempted to run a shell command from the main thread";
- public static final String EXCEPTION_NOT_IDLE = "Application attempted to wait for a non-idle shell to close on the main thread";
- public static final String EXCEPTION_WAIT_IDLE = "Application attempted to wait for a shell to become idle on the main thread";
-
- public ShellOnMainThreadException(String message) {
- super(message);
- }
-}
+/*
+ * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.chainfire.libsuperuser;
+
+/**
+ * Exception class used to crash application when shell commands are executed
+ * from the main thread, and we are in debug mode.
+ */
+@SuppressWarnings("serial")
+public class ShellOnMainThreadException extends RuntimeException {
+ public static final String EXCEPTION_COMMAND = "Application attempted to run a shell command from the main thread";
+ public static final String EXCEPTION_NOT_IDLE = "Application attempted to wait for a non-idle shell to close on the main thread";
+ public static final String EXCEPTION_WAIT_IDLE = "Application attempted to wait for a shell to become idle on the main thread";
+
+ public ShellOnMainThreadException(String message) {
+ super(message);
+ }
+}
diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java b/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java
index 87ad3e4..893f75e 100644
--- a/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java
+++ b/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java
@@ -1,103 +1,103 @@
-/*
- * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.chainfire.libsuperuser;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.List;
-
-/**
- * Thread utility class continuously reading from an InputStream
- */
-public class StreamGobbler extends Thread {
- /**
- * Line callback interface
- */
- public interface OnLineListener {
- /**
- * Line callback
- *
- * This callback should process the line as quickly as possible.
- * Delays in this callback may pause the native process or even
- * result in a deadlock
- *
- * @param line String that was gobbled
- */
- public void onLine(String line);
- }
-
- private String shell = null;
- private BufferedReader reader = null;
- private List writer = null;
- private OnLineListener listener = null;
-
- /**
- * StreamGobbler constructor
- *
- * We use this class because shell STDOUT and STDERR should be read as quickly as
- * possible to prevent a deadlock from occurring, or Process.waitFor() never
- * returning (as the buffer is full, pausing the native process)
- *
- * @param shell Name of the shell
- * @param inputStream InputStream to read from
- * @param outputList List to write to, or null
- */
- public StreamGobbler(String shell, InputStream inputStream, List outputList) {
- this.shell = shell;
- reader = new BufferedReader(new InputStreamReader(inputStream));
- writer = outputList;
- }
-
- /**
- * StreamGobbler constructor
- *
- * We use this class because shell STDOUT and STDERR should be read as quickly as
- * possible to prevent a deadlock from occurring, or Process.waitFor() never
- * returning (as the buffer is full, pausing the native process)
- *
- * @param shell Name of the shell
- * @param inputStream InputStream to read from
- * @param onLineListener OnLineListener callback
- */
- public StreamGobbler(String shell, InputStream inputStream, OnLineListener onLineListener) {
- this.shell = shell;
- reader = new BufferedReader(new InputStreamReader(inputStream));
- listener = onLineListener;
- }
-
- @Override
- public void run() {
- // keep reading the InputStream until it ends (or an error occurs)
- try {
- String line = null;
- while ((line = reader.readLine()) != null) {
+/*
+ * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.chainfire.libsuperuser;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+
+/**
+ * Thread utility class continuously reading from an InputStream
+ */
+public class StreamGobbler extends Thread {
+ /**
+ * Line callback interface
+ */
+ public interface OnLineListener {
+ /**
+ * Line callback
+ *
+ * This callback should process the line as quickly as possible.
+ * Delays in this callback may pause the native process or even
+ * result in a deadlock
+ *
+ * @param line String that was gobbled
+ */
+ public void onLine(String line);
+ }
+
+ private String shell = null;
+ private BufferedReader reader = null;
+ private List writer = null;
+ private OnLineListener listener = null;
+
+ /**
+ * StreamGobbler constructor
+ *
+ * We use this class because shell STDOUT and STDERR should be read as quickly as
+ * possible to prevent a deadlock from occurring, or Process.waitFor() never
+ * returning (as the buffer is full, pausing the native process)
+ *
+ * @param shell Name of the shell
+ * @param inputStream InputStream to read from
+ * @param outputList List to write to, or null
+ */
+ public StreamGobbler(String shell, InputStream inputStream, List outputList) {
+ this.shell = shell;
+ reader = new BufferedReader(new InputStreamReader(inputStream));
+ writer = outputList;
+ }
+
+ /**
+ * StreamGobbler constructor
+ *
+ * We use this class because shell STDOUT and STDERR should be read as quickly as
+ * possible to prevent a deadlock from occurring, or Process.waitFor() never
+ * returning (as the buffer is full, pausing the native process)
+ *
+ * @param shell Name of the shell
+ * @param inputStream InputStream to read from
+ * @param onLineListener OnLineListener callback
+ */
+ public StreamGobbler(String shell, InputStream inputStream, OnLineListener onLineListener) {
+ this.shell = shell;
+ reader = new BufferedReader(new InputStreamReader(inputStream));
+ listener = onLineListener;
+ }
+
+ @Override
+ public void run() {
+ // keep reading the InputStream until it ends (or an error occurs)
+ try {
+ String line = null;
+ while ((line = reader.readLine()) != null) {
Debug.logOutput(String.format("[%s] %s", shell, line));
- if (writer != null) writer.add(line);
- if (listener != null) listener.onLine(line);
- }
- } catch (IOException e) {
- }
-
- // make sure our stream is closed and resources will be freed
- try {
- reader.close();
- } catch (IOException e) {
- }
- }
-}
+ if (writer != null) writer.add(line);
+ if (listener != null) listener.onLine(line);
+ }
+ } catch (IOException e) {
+ }
+
+ // make sure our stream is closed and resources will be freed
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+}
diff --git a/libsuperuser_example/src/eu/chainfire/libsuperuser_example/BackgroundIntentService.java b/libsuperuser_example/src/eu/chainfire/libsuperuser_example/BackgroundIntentService.java
index 4cafcca..79a6812 100644
--- a/libsuperuser_example/src/eu/chainfire/libsuperuser_example/BackgroundIntentService.java
+++ b/libsuperuser_example/src/eu/chainfire/libsuperuser_example/BackgroundIntentService.java
@@ -1,101 +1,101 @@
-/*
- * Copyright (C) 2012 Jorrit "Chainfire" Jongma
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.chainfire.libsuperuser_example;
-
-import eu.chainfire.libsuperuser.Application;
-import eu.chainfire.libsuperuser.Shell;
-import android.app.IntentService;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * Example IntentService based class, to execute tasks in a background
- * thread. This would typically be used by BroadcastReceivers and other
- * fire-and-forget type calls.
- *
- * For most background calls that would occur when the UI is visible, in
- * response to some user action and/or something you are waiting for,
- * you would typically use an AsyncTask instead of a service like this.
- * (See MainActivity.java for that example)
- *
- * Note that the IntentService's onHandleIntent call runs in a background
- * thread, while a normal service's calls would run in the main thread,
- * unless you put in the extra work. This is an important distinction
- * that is often overlooked by beginners.
- *
- * This service starts running when needed, and stops running when the
- * task is done, automagically.
- *
- * Please also see BootCompleteReceiver.java, and AndroidManifest.xml for
- * how and when this service is instantiated.
- *
- * This code leaves some room for extension - if you really wanted to
- * respond only to a single event that always does the same, this code
- * could have been a lot shorter.
- */
-public class BackgroundIntentService extends IntentService {
- // you could provide more options here, should you need them
- public static final String ACTION_BOOT_COMPLETE = "boot_complete";
-
- public static void performAction(Context context, String action) {
- performAction(context, action, null);
- }
-
- public static void performAction(Context context, String action, Bundle extras) {
- // this is utility call to easy starting the service and performing a task
- // pass parameters in an bundle to be added to the intent as extras
- // See BootCompleteReceiver.java
-
- if ((context == null) || (action == null) || action.equals("")) return;
-
- Intent svc = new Intent(context, BackgroundIntentService.class);
- svc.setAction(action);
- if (extras != null) svc.putExtras(extras);
- context.startService(svc);
- }
-
- public BackgroundIntentService() {
- // If you forget this one, the app will crash
- super("BackgroundIntentService");
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- String action = intent.getAction();
- if ((action == null) || (action.equals(""))) return;
-
- if (action.equals(ACTION_BOOT_COMPLETE)) {
- onBootComplete();
- }
- // you can define more options here... pass parameters through the "extra" values
- }
-
- protected void onBootComplete() {
- // We are running in a background thread here!
-
- // This would crash (when debugging) if it was called from the main thread:
- Shell.SU.run("ls -l /");
-
- // Let's toast that we're done, using the work-arounds and utility function in
- // out Application class. Without those modifications there would be a very high
- // chance of crashing the app in various Android versions. The modifications are
- // simple and easily ported to your own Application class, if you can't use the
- // one from libsuperuser.
- Application.toast(this, "This toast will self-destruct in five seconds");
- }
-}
+/*
+ * Copyright (C) 2012 Jorrit "Chainfire" Jongma
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.chainfire.libsuperuser_example;
+
+import eu.chainfire.libsuperuser.Application;
+import eu.chainfire.libsuperuser.Shell;
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Example IntentService based class, to execute tasks in a background
+ * thread. This would typically be used by BroadcastReceivers and other
+ * fire-and-forget type calls.
+ *
+ * For most background calls that would occur when the UI is visible, in
+ * response to some user action and/or something you are waiting for,
+ * you would typically use an AsyncTask instead of a service like this.
+ * (See MainActivity.java for that example)
+ *
+ * Note that the IntentService's onHandleIntent call runs in a background
+ * thread, while a normal service's calls would run in the main thread,
+ * unless you put in the extra work. This is an important distinction
+ * that is often overlooked by beginners.
+ *
+ * This service starts running when needed, and stops running when the
+ * task is done, automagically.
+ *
+ * Please also see BootCompleteReceiver.java, and AndroidManifest.xml for
+ * how and when this service is instantiated.
+ *
+ * This code leaves some room for extension - if you really wanted to
+ * respond only to a single event that always does the same, this code
+ * could have been a lot shorter.
+ */
+public class BackgroundIntentService extends IntentService {
+ // you could provide more options here, should you need them
+ public static final String ACTION_BOOT_COMPLETE = "boot_complete";
+
+ public static void performAction(Context context, String action) {
+ performAction(context, action, null);
+ }
+
+ public static void performAction(Context context, String action, Bundle extras) {
+ // this is utility call to easy starting the service and performing a task
+ // pass parameters in an bundle to be added to the intent as extras
+ // See BootCompleteReceiver.java
+
+ if ((context == null) || (action == null) || action.equals("")) return;
+
+ Intent svc = new Intent(context, BackgroundIntentService.class);
+ svc.setAction(action);
+ if (extras != null) svc.putExtras(extras);
+ context.startService(svc);
+ }
+
+ public BackgroundIntentService() {
+ // If you forget this one, the app will crash
+ super("BackgroundIntentService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ String action = intent.getAction();
+ if ((action == null) || (action.equals(""))) return;
+
+ if (action.equals(ACTION_BOOT_COMPLETE)) {
+ onBootComplete();
+ }
+ // you can define more options here... pass parameters through the "extra" values
+ }
+
+ protected void onBootComplete() {
+ // We are running in a background thread here!
+
+ // This would crash (when debugging) if it was called from the main thread:
+ Shell.SU.run("ls -l /");
+
+ // Let's toast that we're done, using the work-arounds and utility function in
+ // out Application class. Without those modifications there would be a very high
+ // chance of crashing the app in various Android versions. The modifications are
+ // simple and easily ported to your own Application class, if you can't use the
+ // one from libsuperuser.
+ Application.toast(this, "This toast will self-destruct in five seconds");
+ }
+}
diff --git a/libsuperuser_example/src/eu/chainfire/libsuperuser_example/BootCompleteReceiver.java b/libsuperuser_example/src/eu/chainfire/libsuperuser_example/BootCompleteReceiver.java
index f85a159..8277125 100644
--- a/libsuperuser_example/src/eu/chainfire/libsuperuser_example/BootCompleteReceiver.java
+++ b/libsuperuser_example/src/eu/chainfire/libsuperuser_example/BootCompleteReceiver.java
@@ -1,44 +1,44 @@
-/*
- * Copyright (C) 2012 Jorrit "Chainfire" Jongma
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.chainfire.libsuperuser_example;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Example BootCompleteReceiver that starts MyIntentService
- * (please see MyIntentService.java) to handle the task
- * in a background thread
- */
-public class BootCompleteReceiver extends BroadcastReceiver{
- @Override
- public void onReceive(Context context, Intent intent) {
- // What many beginners don't realize is that BroadcastReceivers like these
- // usually run in the application's main thread, and can thus generate
- // ANRs. This is increasingly likely with the BOOT_COMPLETED receiver, as
- // the system is likely very busy when this receiver is called.
-
- // In this example we are starting our MyIntentService to actually do the
- // work we want to happen, not only because "su" should specifically NEVER
- // be called from a BroadcastReceiver, but also because you should be doing
- // this even if you aren't calling "su". It's a good practise, and using
- // IntentService is really easy.
-
- BackgroundIntentService.performAction(context, BackgroundIntentService.ACTION_BOOT_COMPLETE);
- }
+/*
+ * Copyright (C) 2012 Jorrit "Chainfire" Jongma
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.chainfire.libsuperuser_example;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Example BootCompleteReceiver that starts MyIntentService
+ * (please see MyIntentService.java) to handle the task
+ * in a background thread
+ */
+public class BootCompleteReceiver extends BroadcastReceiver{
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // What many beginners don't realize is that BroadcastReceivers like these
+ // usually run in the application's main thread, and can thus generate
+ // ANRs. This is increasingly likely with the BOOT_COMPLETED receiver, as
+ // the system is likely very busy when this receiver is called.
+
+ // In this example we are starting our MyIntentService to actually do the
+ // work we want to happen, not only because "su" should specifically NEVER
+ // be called from a BroadcastReceiver, but also because you should be doing
+ // this even if you aren't calling "su". It's a good practise, and using
+ // IntentService is really easy.
+
+ BackgroundIntentService.performAction(context, BackgroundIntentService.ACTION_BOOT_COMPLETE);
+ }
}
\ No newline at end of file