From c64c1a19b7603e651912de742db69af969d05027 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Mart=C3=AD?= <mvdan@mvdan.cc>
Date: Tue, 31 Mar 2015 19:36:51 +0200
Subject: [PATCH] Replace libsuperuser submodule by checked in code

---
 .gitmodules                                   |    4 -
 extern/libsuperuser                           |    1 -
 extern/libsuperuser/LICENSE                   |  177 ++
 extern/libsuperuser/build.gradle              |   17 +
 extern/libsuperuser/libsuperuser/.classpath   |    9 +
 extern/libsuperuser/libsuperuser/.project     |   38 +
 .../libsuperuser/AndroidManifest.xml          |    8 +
 extern/libsuperuser/libsuperuser/build.gradle |  105 +
 .../libsuperuser/project.properties           |   15 +
 .../chainfire/libsuperuser/Application.java   |   79 +
 .../src/eu/chainfire/libsuperuser/Debug.java  |  240 +++
 .../libsuperuser/HideOverlaysReceiver.java    |   59 +
 .../src/eu/chainfire/libsuperuser/Shell.java  | 1750 +++++++++++++++++
 .../libsuperuser/ShellNotClosedException.java |   29 +
 .../ShellOnMainThreadException.java           |   32 +
 .../chainfire/libsuperuser/StreamGobbler.java |  103 +
 extern/libsuperuser/settings.gradle           |    1 +
 17 files changed, 2662 insertions(+), 5 deletions(-)
 delete mode 160000 extern/libsuperuser
 create mode 100644 extern/libsuperuser/LICENSE
 create mode 100644 extern/libsuperuser/build.gradle
 create mode 100644 extern/libsuperuser/libsuperuser/.classpath
 create mode 100644 extern/libsuperuser/libsuperuser/.project
 create mode 100644 extern/libsuperuser/libsuperuser/AndroidManifest.xml
 create mode 100644 extern/libsuperuser/libsuperuser/build.gradle
 create mode 100644 extern/libsuperuser/libsuperuser/project.properties
 create mode 100644 extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Application.java
 create mode 100644 extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java
 create mode 100644 extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/HideOverlaysReceiver.java
 create mode 100644 extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
 create mode 100644 extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java
 create mode 100644 extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java
 create mode 100644 extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java
 create mode 100644 extern/libsuperuser/settings.gradle

diff --git a/.gitmodules b/.gitmodules
index e593b54d7..8e46a35a3 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -6,10 +6,6 @@
 	path = extern/MemorizingTrustManager
 	url = https://github.com/ge0rg/MemorizingTrustManager.git
 	ignore = dirty
-[submodule "extern/libsuperuser"]
-	path = extern/libsuperuser
-	url = https://github.com/Chainfire/libsuperuser
-	ignore = dirty
 [submodule "extern/jmdns"]
 	path = extern/jmdns
 	url = https://gitlab.com/fdroid/jmdns.git
diff --git a/extern/libsuperuser b/extern/libsuperuser
deleted file mode 160000
index 7fb28c154..000000000
--- a/extern/libsuperuser
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 7fb28c15440c5ea42662b0bebdb3295ecd3cff44
diff --git a/extern/libsuperuser/LICENSE b/extern/libsuperuser/LICENSE
new file mode 100644
index 000000000..ade750b16
--- /dev/null
+++ b/extern/libsuperuser/LICENSE
@@ -0,0 +1,177 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
diff --git a/extern/libsuperuser/build.gradle b/extern/libsuperuser/build.gradle
new file mode 100644
index 000000000..ebc04a4e2
--- /dev/null
+++ b/extern/libsuperuser/build.gradle
@@ -0,0 +1,17 @@
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.0.0'
+        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
+        classpath 'com.github.dcendents:android-maven-plugin:1.2'
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+    }
+}
diff --git a/extern/libsuperuser/libsuperuser/.classpath b/extern/libsuperuser/libsuperuser/.classpath
new file mode 100644
index 000000000..b76ec6cd4
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/extern/libsuperuser/libsuperuser/.project b/extern/libsuperuser/libsuperuser/.project
new file mode 100644
index 000000000..8f20a24e9
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/.project
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>libsuperuser</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.saikoa.dexguard.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/extern/libsuperuser/libsuperuser/AndroidManifest.xml b/extern/libsuperuser/libsuperuser/AndroidManifest.xml
new file mode 100644
index 000000000..2a7f3830e
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="eu.chainfire.libsuperuser"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" />
+</manifest>
+
diff --git a/extern/libsuperuser/libsuperuser/build.gradle b/extern/libsuperuser/libsuperuser/build.gradle
new file mode 100644
index 000000000..c3d7d2479
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/build.gradle
@@ -0,0 +1,105 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.github.dcendents.android-maven'
+apply plugin: 'com.jfrog.bintray'
+
+android {
+    compileSdkVersion 21
+    buildToolsVersion "21.1.0"
+
+    defaultConfig {
+        minSdkVersion 4
+        targetSdkVersion 21
+    }
+    
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            resources.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+            assets.srcDirs = ['assets']
+        }
+    }
+}
+
+version = "1.0.0." + (new Date()).format('yyyyMMddHHmm')
+group = "eu.chainfire"
+
+bintray {
+    // in global gradle.properties: "systemProp.bintrayUser=chainfire", etc
+    user = System.properties['bintrayUser']
+    key = System.properties['bintrayApiKey']
+
+    configurations = ['archives']
+    dryRun = false
+    publish = true
+    pkg {
+        repo = 'maven'
+        name = 'libsuperuser'
+        desc = 'libsuperuser'
+        websiteUrl = 'https://github.com/Chainfire/libsuperuser'
+        issueTrackerUrl = 'https://github.com/Chainfire/libsuperuser/issues'
+        vcsUrl = 'https://github.com/Chainfire/libsuperuser.git'
+        licenses = ['Apache-2.0']
+        publicDownloadNumbers = true
+    }
+
+}
+
+install {
+    repositories.mavenInstaller {
+        pom {
+            project {
+                packaging 'aar'
+                name 'libsuperuser'
+                url 'https://github.com/Chainfire/libsuperuser'
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                    }
+                }
+                developers {
+                    developer {
+                        id 'Chainfire'
+                        name 'Jorrit Jongma'
+                    }
+                }
+                scm {
+                    connection 'https://github.com/Chainfire/libsuperuser.git'
+                    developerConnection 'https://github.com/Chainfire/libsuperuser.git'
+                    url 'https://github.com/Chainfire/libsuperuser.git'
+                }
+            }
+        }
+    }
+}
+
+dependencies {
+}
+
+task sourcesJar(type: Jar) {
+    from android.sourceSets.main.java.srcDirs
+    classifier = 'sources'
+}
+
+task javadoc(type: Javadoc) {
+    source = android.sourceSets.main.java.srcDirs
+    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+    classifier = 'javadoc'
+    from javadoc.destinationDir
+}
+
+artifacts {
+    archives javadocJar
+    archives sourcesJar
+}
+
+task findConventions << {
+    println project.getConvention()
+}
\ No newline at end of file
diff --git a/extern/libsuperuser/libsuperuser/project.properties b/extern/libsuperuser/libsuperuser/project.properties
new file mode 100644
index 000000000..93c8c3c08
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-21
+android.library=true
diff --git a/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Application.java b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Application.java
new file mode 100644
index 000000000..a439517fb
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Application.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012-2014 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/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java
new file mode 100644
index 000000000..c6f695ca0
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2012-2014 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;
+
+    /**
+     * <p>Enable or disable debug mode</p>
+     * 
+     * <p>By default, debug mode is enabled for development
+     * builds and disabled for exported APKs - see
+     * BuildConfig.DEBUG</p>
+     * 
+     * @param enabled Enable debug mode ?
+     */	
+    public static void setDebug(boolean enable) { 
+        debug = enable; 
+    }
+
+    /**
+     * <p>Is debug mode enabled ?</p>
+     * 
+     * @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;
+
+    /**
+     * <p>Log a message (internal)</p>
+     * 
+     * <p>Current debug and enabled logtypes decide what gets logged - 
+     * even if a custom callback is registered</p>  
+     * 
+     * @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);
+            }
+        }
+    }	
+
+    /**
+     * <p>Log a "general" message</p>
+     * 
+     * <p>These messages are infrequent and mostly occur at startup/shutdown or on error</p>
+     * 
+     * @param message The message to log
+     */
+    public static void log(String message) {
+        logCommon(LOG_GENERAL, "G", message);
+    }
+
+    /**
+     * <p>Log a "per-command" message</p>
+     * 
+     * <p>This could produce a lot of output if the client runs many commands in the session</p>
+     * 
+     * @param message The message to log
+     */
+    public static void logCommand(String message) {
+        logCommon(LOG_COMMAND, "C", message);
+    }
+
+    /**
+     * <p>Log a line of stdout/stderr output</p>
+     * 
+     * <p>This could produce a lot of output if the shell commands are noisy</p>
+     * 
+     * @param message The message to log
+     */
+    public static void logOutput(String message) {
+        logCommon(LOG_OUTPUT, "O", message);
+    }
+
+    /**
+     * <p>Enable or disable logging specific types of message</p>
+     * 
+     * <p>You may | (or) LOG_* constants together. Note that
+     * debug mode must also be enabled for actual logging to
+     * occur.</p>
+     * 
+     * @param type LOG_* constants
+     * @param enabled Enable or disable
+     */
+    public static void setLogTypeEnabled(int type, boolean enable) { 
+        if (enable) {
+            logTypes |= type;
+        } else {
+            logTypes &= ~type;
+        }
+    }
+
+    /**
+     * <p>Is logging for specific types of messages enabled ?</p>
+     * 
+     * <p>You may | (or) LOG_* constants together, to learn if
+     * <b>all</b> passed message types are enabled for logging. Note
+     * that debug mode must also be enabled for actual logging
+     * to occur.</p>
+     * 
+     * @param type LOG_* constants
+     */
+    public static boolean getLogTypeEnabled(int type) { 
+        return ((logTypes & type) == type); 
+    }
+
+    /**
+     * <p>Is logging for specific types of messages enabled ?</p>
+     * 
+     * <p>You may | (or) LOG_* constants together, to learn if
+     * <b>all</b> message types are enabled for logging. Takes
+     * debug mode into account for the result.</p>
+     * 
+     * @param type LOG_* constants
+     */
+    public static boolean getLogTypeEnabledEffective(int type) {
+        return getDebug() && getLogTypeEnabled(type);
+    }
+
+    /**
+     * <p>Register a custom log handler</p>
+     * 
+     * <p>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.</p>
+     * 
+     * @param onLogListener Custom log listener or NULL to revert to default
+     */
+    public static void setOnLogListener(OnLogListener onLogListener) {
+        logListener = onLogListener;
+    }
+
+    /**
+     * <p>Get the currently registered custom log handler</p>
+     * 
+     * @return Current custom log handler or NULL if none is present 
+     */
+    public static OnLogListener getOnLogListener() {
+        return logListener;
+    }
+
+    // ----- SANITY CHECKS -----
+
+    private static boolean sanityChecks = true;
+
+    /**
+     * <p>Enable or disable sanity checks</p>
+     * 
+     * <p>Enables or disables the library crashing when su is called 
+     * from the main thread.</p>
+     * 
+     * @param enabled Enable or disable
+     */
+    public static void setSanityChecksEnabled(boolean enable) {
+        sanityChecks = enable;
+    }
+
+    /**
+     * <p>Are sanity checks enabled ?</p>
+     * 
+     * <p>Note that debug mode must also be enabled for actual
+     * sanity checks to occur.</p> 
+     * 
+     * @return True if enabled
+     */
+    public static boolean getSanityChecksEnabled() {
+        return sanityChecks;
+    }
+
+    /**
+     * <p>Are sanity checks enabled ?</p>
+     * 
+     * <p>Takes debug mode into account for the result.</p> 
+     * 
+     * @return True if enabled
+     */
+    public static boolean getSanityChecksEnabledEffective() {
+        return getDebug() && getSanityChecksEnabled();
+    }
+
+    /**
+     * <p>Are we running on the main thread ?</p>
+     * 
+     * @return Running on main thread ?
+     */	
+    public static boolean onMainThread() {
+        return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper()));
+    }
+
+}
diff --git a/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/HideOverlaysReceiver.java b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/HideOverlaysReceiver.java
new file mode 100644
index 000000000..4b4ce5a8d
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/HideOverlaysReceiver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012-2014 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * <p>
+ * Base receiver to extend to catch notifications when overlays should be
+ * hidden.
+ * </p>
+ * <p>
+ * Tapjacking protection in SuperSU prevents some dialogs from receiving user
+ * input when overlays are present. For security reasons this notification is
+ * only sent to apps that have previously been granted root access, so even if
+ * your app does not <em>require</em> root, you still need to <em>request</em>
+ * it, and the user must grant it.
+ * </p>
+ * <p>
+ * Note that the word overlay as used here should be interpreted as "any view or
+ * window possibly obscuring SuperSU dialogs".
+ * </p>
+ */
+public abstract class HideOverlaysReceiver extends BroadcastReceiver {
+    public static final String ACTION_HIDE_OVERLAYS = "eu.chainfire.supersu.action.HIDE_OVERLAYS";
+    public static final String CATEGORY_HIDE_OVERLAYS = Intent.CATEGORY_INFO;
+    public static final String EXTRA_HIDE_OVERLAYS = "eu.chainfire.supersu.extra.HIDE";
+
+    @Override
+    public final void onReceive(Context context, Intent intent) {
+        if (intent.hasExtra(EXTRA_HIDE_OVERLAYS)) {
+            onHideOverlays(intent.getBooleanExtra(EXTRA_HIDE_OVERLAYS, false));
+        }
+    }
+
+    /**
+     * Called when overlays <em>should</em> be hidden or <em>may</em> be shown
+     * again.
+     * 
+     * @param hide Should overlays be hidden?
+     */
+    public abstract void onHideOverlays(boolean hide);
+}
diff --git a/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
new file mode 100644
index 000000000..ebd03cff9
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java
@@ -0,0 +1,1750 @@
+/*
+ * Copyright (C) 2012-2014 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.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+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.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 {
+    /**
+     * <p>
+     * Runs commands using the supplied shell, and returns the output, or null
+     * in case of errors.
+     * </p>
+     * <p>
+     * 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.
+     * </p>
+     * 
+     * @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<String> run(String shell, String[] commands, boolean wantSTDERR) {
+        return run(shell, commands, null, wantSTDERR);
+    }
+
+    /**
+     * <p>
+     * Runs commands using the supplied shell, and returns the output, or null
+     * in case of errors.
+     * </p>
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * When in debug mode, the code will also excessively log the commands
+     * passed to and the output returned from the shell.
+     * </p>
+     * <p>
+     * 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&lt;String&gt;, and as such doing
+     * something like <em>'ls -lR /'</em> will probably have you run out of
+     * memory.
+     * </p>
+     * 
+     * @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<String> 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);
+        }
+        Debug.logCommand(String.format("[%s%%] START", shellUpper));
+
+        List<String> res = Collections.synchronizedList(new ArrayList<String>());
+
+        try {
+            // Combine passed environment with system environment
+            if (environment != null) {
+                Map<String, String> newEnvironment = new HashMap<String, String>();
+                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<String, String> 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();
+            try {
+                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();
+            } catch (IOException e) {
+                if (e.getMessage().contains("EPIPE")) {
+                    // method most horrid to catch broken pipe, in which case we
+                    // do nothing. the command is not a shell, the shell closed
+                    // STDIN, the script already contained the exit command, etc.
+                    // these cases we want the output instead of returning null
+                } else {
+                    // other issues we don't know how to handle, leads to
+                    // returning null
+                    throw e;
+                }
+            }
+
+            // 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 (SU.isSU(shell) && (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;
+    }
+
+    protected static String[] availableTestCommands = new String[] {
+            "echo -BOC-",
+            "id"
+    };
+
+    /**
+     * See if the shell is alive, and if so, check the UID
+     * 
+     * @param ret Standard output from running availableTestCommands
+     * @param checkForRoot true if we are expecting this shell to be running as
+     *            root
+     * @return true on success, false on error
+     */
+    protected static boolean parseAvailableResult(List<String> ret, boolean checkForRoot) {
+        if (ret == null)
+            return false;
+
+        // this is only one of many ways this can be done
+        boolean echo_seen = false;
+
+        for (String line : ret) {
+            if (line.contains("uid=")) {
+                // id command is working, let's see if we are actually root
+                return !checkForRoot || line.contains("uid=0");
+            } else if (line.contains("-BOC-")) {
+                // if we end up here, at least the su command starts some kind
+                // of shell,
+                // let's hope it has root privileges - no way to know without
+                // additional
+                // native binaries
+                echo_seen = true;
+            }
+        }
+
+        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<String> 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<String> run(List<String> 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<String> 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 {
+        private static Boolean isSELinuxEnforcing = null;
+        private static String[] suVersion = new String[] {
+                null, null
+        };
+
+        /**
+         * 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<String> 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<String> run(List<String> 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<String> 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<String> ret = run(Shell.availableTestCommands);
+            return Shell.parseAvailableResult(ret, true);
+        }
+
+        /**
+         * <p>
+         * 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.
+         * </p>
+         * <p>
+         * Note that su binary version and GUI (APK) version can be completely
+         * different.
+         * </p>
+         * <p>
+         * This function caches its result to improve performance on multiple
+         * calls
+         * </p>
+         * 
+         * @param internal Request human-readable version or application
+         *            internal version
+         * @return String containing the su version or null
+         */
+        public static synchronized String version(boolean internal) {
+            int idx = internal ? 0 : 1;
+            if (suVersion[idx] == null) {
+                String version = null;
+
+                List<String> ret = Shell.run(
+                        internal ? "su -V" : "su -v",
+                        new String[] { "exit" },
+                        null,
+                        false
+                        );
+
+                if (ret != null) {
+                    for (String line : ret) {
+                        if (!internal) {
+                            if (line.contains(".")) {
+                                version = line;
+                                break;
+                            }
+                        } else {
+                            try {
+                                if (Integer.parseInt(line) > 0) {
+                                    version = line;
+                                    break;
+                                }
+                            } catch (NumberFormatException e) {
+                            }
+                        }
+                    }
+                }
+
+                suVersion[idx] = version;
+            }
+            return suVersion[idx];
+        }
+
+        /**
+         * Attempts to deduce if the shell command refers to a su shell
+         * 
+         * @param shell Shell command to run
+         * @return Shell command appears to be su
+         */
+        public static boolean isSU(String shell) {
+            // Strip parameters
+            int pos = shell.indexOf(' ');
+            if (pos >= 0) {
+                shell = shell.substring(0, pos);
+            }
+
+            // Strip path
+            pos = shell.lastIndexOf('/');
+            if (pos >= 0) {
+                shell = shell.substring(pos + 1);
+            }
+
+            return shell.equals("su");
+        }
+
+        /**
+         * Constructs a shell command to start a su shell using the supplied uid
+         * and SELinux context. This is can be an expensive operation, consider
+         * caching the result.
+         * 
+         * @param uid Uid to use (0 == root)
+         * @param context (SELinux) context name to use or null
+         * @return Shell command
+         */
+        public static String shell(int uid, String context) {
+            // su[ --context <context>][ <uid>]
+            String shell = "su";
+
+            if ((context != null) && isSELinuxEnforcing()) {
+                String display = version(false);
+                String internal = version(true);
+
+                // We only know the format for SuperSU v1.90+ right now
+                if ((display != null) &&
+                        (internal != null) &&
+                        (display.endsWith("SUPERSU")) &&
+                        (Integer.valueOf(internal) >= 190)) {
+                    shell = String.format(Locale.ENGLISH, "%s --context %s", shell, context);
+                }
+            }
+
+            // Most su binaries support the "su <uid>" format, but in case
+            // they don't, lets skip it for the default 0 (root) case
+            if (uid > 0) {
+                shell = String.format(Locale.ENGLISH, "%s %d", shell, uid);
+            }
+
+            return shell;
+        }
+
+        /**
+         * Constructs a shell command to start a su shell connected to mount
+         * master daemon, to perform public mounts on Android 4.3+ (or 4.2+ in
+         * SELinux enforcing mode)
+         * 
+         * @return Shell command
+         */
+        public static String shellMountMaster() {
+            if (android.os.Build.VERSION.SDK_INT >= 17) {
+                return "su --mount-master";
+            }
+            return "su";
+        }
+
+        /**
+         * Detect if SELinux is set to enforcing, caches result
+         * 
+         * @return true if SELinux set to enforcing, or false in the case of
+         *         permissive or not present
+         */
+        public static synchronized boolean isSELinuxEnforcing() {
+            if (isSELinuxEnforcing == null) {
+                Boolean enforcing = null;
+
+                // First known firmware with SELinux built-in was a 4.2 (17)
+                // leak
+                if (android.os.Build.VERSION.SDK_INT >= 17) {
+                    // Detect enforcing through sysfs, not always present
+                    if (enforcing == null) {
+                        File f = new File("/sys/fs/selinux/enforce");
+                        if (f.exists()) {
+                            try {
+                                InputStream is = new FileInputStream("/sys/fs/selinux/enforce");
+                                try {
+                                    enforcing = (is.read() == '1');
+                                } finally {
+                                    is.close();
+                                }
+                            } catch (Exception e) {
+                            }
+                        }
+                    }
+
+                    // 4.4+ builds are enforcing by default, take the gamble
+                    if (enforcing == null) {
+                        enforcing = (android.os.Build.VERSION.SDK_INT >= 19);
+                    }
+                }
+
+                if (enforcing == null) {
+                    enforcing = false;
+                }
+
+                isSELinuxEnforcing = enforcing;
+            }
+            return isSELinuxEnforcing;
+        }
+
+        /**
+         * <p>
+         * Clears results cached by isSELinuxEnforcing() and version(boolean
+         * internal) calls.
+         * </p>
+         * <p>
+         * Most apps should never need to call this, as neither enforcing status
+         * nor su version is likely to change on a running device - though it is
+         * not impossible.
+         * </p>
+         */
+        public static synchronized void clearCachedResults() {
+            isSELinuxEnforcing = null;
+            suVersion[0] = null;
+            suVersion[1] = null;
+        }
+    }
+
+    private interface OnResult {
+        // for any onCommandResult callback
+        public static final int WATCHDOG_EXIT = -1;
+        public static final int SHELL_DIED = -2;
+
+        // for Interactive.open() callbacks only
+        public static final int SHELL_EXEC_FAILED = -3;
+        public static final int SHELL_WRONG_UID = -4;
+        public static final int SHELL_RUNNING = 0;
+    }
+
+    /**
+     * 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 extends OnResult {
+        /**
+         * <p>
+         * Command result callback
+         * </p>
+         * <p>
+         * 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
+         * </p>
+         * <p>
+         * See {@link Shell.Interactive} for threading details
+         * </p>
+         * 
+         * @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<String> output);
+    }
+
+    /**
+     * Command per line callback for parsing the output line by line without
+     * buffering It also notifies the recipient of the completion of a command
+     * block, including the (last) exit code.
+     */
+    public interface OnCommandLineListener extends OnResult, OnLineListener {
+        /**
+         * <p>
+         * Command result callback
+         * </p>
+         * <p>
+         * 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
+         * </p>
+         * <p>
+         * See {@link Shell.Interactive} for threading details
+         * </p>
+         * 
+         * @param commandCode Value previously supplied to addCommand
+         * @param exitCode Exit code of the last command in the block
+         */
+        public void onCommandResult(int commandCode, int exitCode);
+    }
+
+    /**
+     * 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 OnCommandLineListener onCommandLineListener;
+        private final String marker;
+
+        public Command(String[] commands, int code,
+                OnCommandResultListener onCommandResultListener,
+                OnCommandLineListener onCommandLineListener) {
+            this.commands = commands;
+            this.code = code;
+            this.onCommandResultListener = onCommandResultListener;
+            this.onCommandLineListener = onCommandLineListener;
+            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<Command> commands = new LinkedList<Command>();
+        private Map<String, String> environment = new HashMap<String, String>();
+        private OnLineListener onSTDOUTLineListener = null;
+        private OnLineListener onSTDERRLineListener = null;
+        private int watchdogTimeout = 0;
+
+        /**
+         * <p>
+         * Set a custom handler that will be used to post all callbacks to
+         * </p>
+         * <p>
+         * See {@link Shell.Interactive} for further details on threading and
+         * handlers
+         * </p>
+         * 
+         * @param handler Handler to use
+         * @return This Builder object for method chaining
+         */
+        public Builder setHandler(Handler handler) {
+            this.handler = handler;
+            return this;
+        }
+
+        /**
+         * <p>
+         * Automatically create a handler if possible ? Default to true
+         * </p>
+         * <p>
+         * See {@link Shell.Interactive} for further details on threading and
+         * handlers
+         * </p>
+         * 
+         * @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<String, String> 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);
+        }
+
+        /**
+         * <p>
+         * Add a command to execute, with a callback to be called on completion
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @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<String> commands) {
+            return addCommand(commands, 0, null);
+        }
+
+        /**
+         * <p>
+         * Add commands to execute, with a callback to be called on completion
+         * (of all commands)
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @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<String> 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);
+        }
+
+        /**
+         * <p>
+         * Add commands to execute, with a callback to be called on completion
+         * (of all commands)
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @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, null));
+            return this;
+        }
+
+        /**
+         * <p>
+         * Set a callback called for every line output to STDOUT by the shell
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @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;
+        }
+
+        /**
+         * <p>
+         * Set a callback called for every line output to STDERR by the shell
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @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;
+        }
+
+        /**
+         * <p>
+         * Enable command timeout callback
+         * </p>
+         * <p>
+         * This will invoke the onCommandResult() callback with exitCode
+         * WATCHDOG_EXIT if a command takes longer than watchdogTimeout seconds
+         * to complete.
+         * </p>
+         * <p>
+         * If a watchdog timeout occurs, it generally means that the Interactive
+         * session is out of sync with the shell process. The caller should
+         * close the current session and open a new one.
+         * </p>
+         * 
+         * @param watchdogTimeout Timeout, in seconds; 0 to disable
+         * @return This Builder object for method chaining
+         */
+        public Builder setWatchdogTimeout(int watchdogTimeout) {
+            this.watchdogTimeout = watchdogTimeout;
+            return this;
+        }
+
+        /**
+         * <p>
+         * Enable/disable reduced logcat output
+         * </p>
+         * <p>
+         * Note that this is a global setting
+         * </p>
+         * 
+         * @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);
+            return this;
+        }
+
+        /**
+         * Construct a {@link Shell.Interactive} instance, and start the shell
+         */
+        public Interactive open() {
+            return new Interactive(this, null);
+        }
+
+        /**
+         * Construct a {@link Shell.Interactive} instance, try to start the
+         * shell, and call onCommandResultListener to report success or failure
+         * 
+         * @param onCommandResultListener Callback to return shell open status
+         */
+        public Interactive open(OnCommandResultListener onCommandResultListener) {
+            return new Interactive(this, onCommandResultListener);
+        }
+    }
+
+    /**
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * When in debug mode, the code will also excessively log the commands
+     * passed to and the output returned from the shell.
+     * </p>
+     * <p>
+     * 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&lt;String&gt;, and as such doing
+     * something like <em>'ls -lR /'</em> 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.
+     * </p>
+     * <h3>Callbacks, threads and handlers</h3>
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * Background threads usually do <em>not</em> 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.
+     * </p>
+     */
+    public static class Interactive {
+        private final Handler handler;
+        private final boolean autoHandler;
+        private final String shell;
+        private final boolean wantSTDERR;
+        private final List<Command> commands;
+        private final Map<String, String> 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 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 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<String> 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;
+            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 (onCommandResultListener != null) {
+                // Allow up to 60 seconds for SuperSU/Superuser dialog, then enable
+                // the user-specified timeout for all subsequent operations
+                watchdogTimeout = 60;
+                commands.add(0, new Command(Shell.availableTestCommands, 0, new OnCommandResultListener() {
+                    public void onCommandResult(int commandCode, int exitCode, List<String> output) {
+                        if (exitCode == OnCommandResultListener.SHELL_RUNNING &&
+                                Shell.parseAvailableResult(output, Shell.SU.isSU(shell)) != true) {
+                            // shell is up, but it's brain-damaged
+                            exitCode = OnCommandResultListener.SHELL_WRONG_UID;
+                        }
+                        watchdogTimeout = builder.watchdogTimeout;
+                        onCommandResultListener.onCommandResult(0, exitCode, output);
+                    }
+                }, null));
+            }
+
+            if (!open() && (onCommandResultListener != null)) {
+                onCommandResultListener.onCommandResult(0,
+                        OnCommandResultListener.SHELL_EXEC_FAILED, null);
+            }
+        }
+
+        @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, (OnCommandResultListener) null);
+        }
+
+        /**
+         * <p>
+         * Add a command to execute, with a callback to be called on completion
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @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);
+        }
+
+        /**
+         * <p>
+         * Add a command to execute, with a callback. This callback gobbles the
+         * output line by line without buffering it and also returns the result
+         * code on completion.
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @param command Command to execute
+         * @param code User-defined value passed back to the callback
+         * @param onCommandLineListener Callback
+         */
+        public void addCommand(String command, int code, OnCommandLineListener onCommandLineListener) {
+            addCommand(new String[] {
+                    command
+            }, code, onCommandLineListener);
+        }
+
+        /**
+         * Add commands to execute
+         * 
+         * @param commands Commands to execute
+         */
+        public void addCommand(List<String> commands) {
+            addCommand(commands, 0, (OnCommandResultListener) null);
+        }
+
+        /**
+         * <p>
+         * Add commands to execute, with a callback to be called on completion
+         * (of all commands)
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @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<String> commands, int code,
+                OnCommandResultListener onCommandResultListener) {
+            addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener);
+        }
+
+        /**
+         * <p>
+         * Add commands to execute, with a callback. This callback gobbles the
+         * output line by line without buffering it and also returns the result
+         * code on completion.
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @param commands Commands to execute
+         * @param code User-defined value passed back to the callback
+         * @param onCommandLineListener Callback
+         */
+        public void addCommand(List<String> commands, int code,
+                OnCommandLineListener onCommandLineListener) {
+            addCommand(commands.toArray(new String[commands.size()]), code, onCommandLineListener);
+        }
+
+        /**
+         * Add commands to execute
+         * 
+         * @param commands Commands to execute
+         */
+        public void addCommand(String[] commands) {
+            addCommand(commands, 0, (OnCommandResultListener) null);
+        }
+
+        /**
+         * <p>
+         * Add commands to execute, with a callback to be called on completion
+         * (of all commands)
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @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, null));
+            runNextCommand();
+        }
+
+        /**
+         * <p>
+         * Add commands to execute, with a callback. This callback gobbles the
+         * output line by line without buffering it and also returns the result
+         * code on completion.
+         * </p>
+         * <p>
+         * The thread on which the callback executes is dependent on various
+         * factors, see {@link Shell.Interactive} for further details
+         * </p>
+         * 
+         * @param commands Commands to execute
+         * @param code User-defined value passed back to the callback
+         * @param onCommandLineListener Callback
+         */
+        public synchronized void addCommand(String[] commands, int code,
+                OnCommandLineListener onCommandLineListener) {
+            this.commands.add(new Command(commands, code, null, onCommandLineListener));
+            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
+         */
+        private synchronized void handleWatchdog() {
+            final int exitCode;
+
+            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)));
+            } else if (watchdogCount++ < watchdogTimeout) {
+                return;
+            } else {
+                exitCode = OnCommandResultListener.WATCHDOG_EXIT;
+                Debug.log(String.format("[%s%%] WATCHDOG_EXIT", shell.toUpperCase(Locale.ENGLISH)));
+            }
+
+            if (handler != null) {
+                postCallback(command, exitCode, buffer);
+            }
+
+            // prevent multiple callbacks for the same command
+            command = null;
+            buffer = null;
+            idle = true;
+
+            watchdog.shutdown();
+            watchdog = null;
+            kill();
+        }
+
+        /**
+         * Start the periodic timer when a command is submitted
+         */
+        private void startWatchdog() {
+            if (watchdogTimeout == 0) {
+                return;
+            }
+            watchdogCount = 0;
+            watchdog = new ScheduledThreadPoolExecutor(1);
+            watchdog.scheduleAtFixedRate(new Runnable() {
+                @Override
+                public void run() {
+                    handleWatchdog();
+                }
+            }, 1, 1, TimeUnit.SECONDS);
+        }
+
+        /**
+         * Disable the watchdog timer upon command completion
+         */
+        private void stopWatchdog() {
+            if (watchdog != null) {
+                watchdog.shutdownNow();
+                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<String>());
+                        }
+
+                        idle = false;
+                        this.command = command;
+                        startWatchdog();
+                        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))) {
+                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<String> fOutput) {
+            if (fCommand.onCommandResultListener == null && fCommand.onCommandLineListener == null) {
+                return;
+            }
+            if (handler == null) {
+                if ((fCommand.onCommandResultListener != null) && (fOutput != null))
+                    fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode,
+                            fOutput);
+                if (fCommand.onCommandLineListener != null)
+                    fCommand.onCommandLineListener.onCommandResult(fCommand.code, fExitCode);
+                return;
+            }
+            startCallback();
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        if ((fCommand.onCommandResultListener != null) && (fOutput != null))
+                            fCommand.onCommandResultListener.onCommandResult(fCommand.code,
+                                    fExitCode, fOutput);
+                        if (fCommand.onCommandLineListener != null)
+                            fCommand.onCommandLineListener
+                                    .onCommandResult(fCommand.code, fExitCode);
+                    } 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() {
+            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<String, String> newEnvironment = new HashMap<String, String>();
+                    newEnvironment.putAll(System.getenv());
+                    newEnvironment.putAll(environment);
+                    int i = 0;
+                    String[] env = new String[newEnvironment.size()];
+                    for (Map.Entry<String, String> 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) {
+                                synchronized (Interactive.this) {
+                                    if (command == null) {
+                                        return;
+                                    }
+                                    if (line.startsWith(command.marker)) {
+                                        try {
+                                            lastExitCode = Integer.valueOf(
+                                                    line.substring(command.marker.length() + 1), 10);
+                                        } catch (Exception e) {
+                                        }
+                                        lastMarkerSTDOUT = command.marker;
+                                        processMarker();
+                                    } else {
+                                        addBuffer(line);
+                                        processLine(line, onSTDOUTLineListener);
+                                        processLine(line, command.onCommandLineListener);
+                                    }
+                                }
+                            }
+                        });
+                STDERR = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "*",
+                        process.getErrorStream(), new OnLineListener() {
+                            @Override
+                            public void onLine(String line) {
+                                synchronized (Interactive.this) {
+                                    if (command == null) {
+                                        return;
+                                    }
+                                    if (line.startsWith(command.marker)) {
+                                        lastMarkerSTDERR = command.marker;
+                                        processMarker();
+                                    } else {
+                                        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.
+            if (!_idle && Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) {
+                Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE);
+                throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE);
+            }
+
+            if (!_idle)
+                waitForIdle();
+
+            try {
+                try {
+                    STDIN.write(("exit\n").getBytes("UTF-8"));
+                    STDIN.flush();
+                } catch (IOException e) {
+                    if (e.getMessage().contains("EPIPE")) {
+                        // we're not running a shell, the shell closed STDIN,
+                        // the script already contained the exit command, etc.                        
+                    } else {
+                        throw e;
+                    }
+                }
+
+                // 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) {
+                    // STDIN going missing is no reason to abort 
+                }
+                STDOUT.join();
+                STDERR.join();
+                stopWatchdog();
+                process.destroy();
+            } catch (IOException e) {
+                // various unforseen IO errors may still occur
+            } 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. Hopefully the StreamGobblers will croak on their own when the
+         * other side of the pipe is closed.
+         */
+        public synchronized void kill() {
+            running = false;
+            closed = true;
+
+            try {
+                STDIN.close();
+            } catch (IOException e) {
+            }
+            try {
+                process.destroy();
+            } catch (Exception e) {
+            }
+        }
+
+        /**
+         * Is our shell still running ?
+         * 
+         * @return Shell running ?
+         */
+        public boolean isRunning() {
+            if (process == null) {
+                return false;
+            }
+            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;
+        }
+
+        /**
+         * <p>
+         * 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.
+         * </p>
+         * <p>
+         * 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.
+         * </p>
+         * <p>
+         * 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.
+         * </p>
+         * <p>
+         * 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.
+         * </p>
+         * <p>
+         * See {@link Shell.Interactive} for further details on threading and
+         * handlers
+         * </p>
+         * 
+         * @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);
+        }
+    }
+}
diff --git a/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java
new file mode 100644
index 000000000..2a44083ec
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012-2014 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/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java
new file mode 100644
index 000000000..69732e3b3
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012-2014 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/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java
new file mode 100644
index 000000000..730521545
--- /dev/null
+++ b/extern/libsuperuser/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012-2014 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 {		
+        /**
+         * <p>Line callback</p>
+         * 
+         * <p>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</p>
+         * 
+         * @param line String that was gobbled
+         */
+        public void onLine(String line);
+    }
+
+    private String shell = null;
+    private BufferedReader reader = null;
+    private List<String> writer = null;
+    private OnLineListener listener = null;
+
+    /**
+     * <p>StreamGobbler constructor</p>
+     * 
+     * <p>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)</p>
+     * 
+     * @param shell Name of the shell
+     * @param inputStream InputStream to read from
+     * @param outputList List<String> to write to, or null
+     */
+    public StreamGobbler(String shell, InputStream inputStream, List<String> outputList) {
+        this.shell = shell;
+        reader = new BufferedReader(new InputStreamReader(inputStream));
+        writer = outputList;
+    }
+
+    /**
+     * <p>StreamGobbler constructor</p>
+     * 
+     * <p>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)</p>
+     * 
+     * @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) {			
+        }
+    }
+}
diff --git a/extern/libsuperuser/settings.gradle b/extern/libsuperuser/settings.gradle
new file mode 100644
index 000000000..32aef3ac0
--- /dev/null
+++ b/extern/libsuperuser/settings.gradle
@@ -0,0 +1 @@
+include ':libsuperuser', ':libsuperuser_example'