diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5edb4ee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+*.iml
+.gradle
+/local.properties
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/RootTools/.gitignore b/RootTools/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/RootTools/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/RootTools/build.gradle b/RootTools/build.gradle
new file mode 100644
index 0000000..8ac4c71
--- /dev/null
+++ b/RootTools/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+
+
+ defaultConfig {
+ minSdkVersion 7
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
diff --git a/RootTools/proguard-rules.pro b/RootTools/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/RootTools/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/RootTools/src/androidTest/java/com/lucemanb/RootTools/ExampleInstrumentedTest.java b/RootTools/src/androidTest/java/com/lucemanb/RootTools/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..1be151b
--- /dev/null
+++ b/RootTools/src/androidTest/java/com/lucemanb/RootTools/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.lucemanb.RootTools;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.lucemanb.RootTools.test", appContext.getPackageName());
+ }
+}
diff --git a/RootTools/src/main/AndroidManifest.xml b/RootTools/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..04625d2
--- /dev/null
+++ b/RootTools/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/Constants.java b/RootTools/src/main/java/com/lucemanb/RootTools/Constants.java
new file mode 100644
index 0000000..eebcd2d
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/Constants.java
@@ -0,0 +1,13 @@
+package com.lucemanb.RootTools;
+
+public class Constants {
+ public static final String TAG = "RootTools v3.3";
+ public static final int FPS = 1;
+ public static final int IAG = 2;
+ public static final int BBA = 3;
+ public static final int BBV = 4;
+ public static final int GI = 5;
+ public static final int GS = 6;
+ public static final int GSYM = 7;
+
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/RootTools.java b/RootTools/src/main/java/com/lucemanb/RootTools/RootTools.java
new file mode 100644
index 0000000..a0528e2
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/RootTools.java
@@ -0,0 +1,794 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.lucemanb.RootTools.containers.Mount;
+import com.lucemanb.RootTools.containers.Permissions;
+import com.lucemanb.RootTools.containers.Symlink;
+import com.lucemanb.RootTools.exceptions.RootDeniedException;
+import com.lucemanb.RootTools.execution.Command;
+import com.lucemanb.RootTools.execution.Shell;
+import com.lucemanb.RootTools.internal.Remounter;
+import com.lucemanb.RootTools.internal.RootToolsInternalMethods;
+import com.lucemanb.RootTools.internal.Runner;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+
+public final class RootTools {
+
+ /**
+ * This class is the gateway to every functionality within the RootTools library.The developer
+ * should only have access to this class and this class only.This means that this class should
+ * be the only one to be public.The rest of the classes within this library must not have the
+ * public modifier.
+ *
+ * All methods and Variables that the developer may need to have access to should be here.
+ *
+ * If a method, or a specific functionality, requires a fair amount of code, or work to be done,
+ * then that functionality should probably be moved to its own class and the call to it done
+ * here.For examples of this being done, look at the remount functionality.
+ */
+
+ private static RootToolsInternalMethods rim = null;
+
+ public static void setRim(RootToolsInternalMethods rim) {
+ RootTools.rim = rim;
+ }
+
+ private static final RootToolsInternalMethods getInternals() {
+ if (rim == null) {
+ RootToolsInternalMethods.getInstance();
+ return rim;
+ } else {
+ return rim;
+ }
+ }
+
+ // --------------------
+ // # Public Variables #
+ // --------------------
+
+ public static boolean debugMode = false;
+ public static List lastFoundBinaryPaths = new ArrayList();
+ public static String utilPath;
+
+ /**
+ * Setting this to false will disable the handler that is used
+ * by default for the 3 callback methods for Command.
+ *
+ * By disabling this all callbacks will be called from a thread other than
+ * the main UI thread.
+ */
+ public static boolean handlerEnabled = true;
+
+
+ /**
+ * Setting this will change the default command timeout.
+ *
+ * The default is 20000ms
+ */
+ public static int default_Command_Timeout = 20000;
+
+
+ // ---------------------------
+ // # Public Variable Getters #
+ // ---------------------------
+
+ // ------------------
+ // # Public Methods #
+ // ------------------
+
+ /**
+ * This will check a given binary, determine if it exists and determine that it has either the
+ * permissions 755, 775, or 777.
+ *
+ * @param util Name of the utility to check.
+ * @return boolean to indicate whether the binary is installed and has appropriate permissions.
+ */
+ public static boolean checkUtil(String util) {
+
+ return getInternals().checkUtil(util);
+ }
+
+ /**
+ * This will close all open shells.
+ *
+ * @throws IOException
+ */
+ public static void closeAllShells() throws IOException {
+ Shell.closeAll();
+ }
+
+ /**
+ * This will close the custom shell that you opened.
+ *
+ * @throws IOException
+ */
+ public static void closeCustomShell() throws IOException {
+ Shell.closeCustomShell();
+ }
+
+ /**
+ * This will close either the root shell or the standard shell depending on what you specify.
+ *
+ * @param root a boolean
to specify whether to close the root shell or the standard shell.
+ * @throws IOException
+ */
+ public static void closeShell(boolean root) throws IOException {
+ if (root)
+ Shell.closeRootShell();
+ else
+ Shell.closeShell();
+ }
+
+ /**
+ * Copys a file to a destination. Because cp is not available on all android devices, we have a
+ * fallback on the cat command
+ *
+ * @param source example: /data/data/org.adaway/files/hosts
+ * @param destination example: /system/etc/hosts
+ * @param remountAsRw remounts the destination as read/write before writing to it
+ * @param preserveFileAttributes tries to copy file attributes from source to destination, if only cat is available
+ * only permissions are preserved
+ * @return true if it was successfully copied
+ */
+ public static boolean copyFile(String source, String destination, boolean remountAsRw,
+ boolean preserveFileAttributes) {
+ return getInternals().copyFile(source, destination, remountAsRw, preserveFileAttributes);
+ }
+
+ /**
+ * Deletes a file or directory
+ *
+ * @param target example: /data/data/org.adaway/files/hosts
+ * @param remountAsRw remounts the destination as read/write before writing to it
+ * @return true if it was successfully deleted
+ */
+ public static boolean deleteFileOrDirectory(String target, boolean remountAsRw) {
+ return getInternals().deleteFileOrDirectory(target, remountAsRw);
+ }
+
+ /**
+ * Use this to check whether or not a file exists on the filesystem.
+ *
+ * @param file String that represent the file, including the full path to the
+ * file and its name.
+ * @return a boolean that will indicate whether or not the file exists.
+ */
+ public static boolean exists(final String file) {
+ return getInternals().exists(file);
+ }
+
+ /**
+ * This will try and fix a given binary. (This is for Busybox applets or Toolbox applets) By
+ * "fix", I mean it will try and symlink the binary from either toolbox or Busybox and fix the
+ * permissions if the permissions are not correct.
+ *
+ * @param util Name of the utility to fix.
+ * @param utilPath path to the toolbox that provides ln, rm, and chmod. This can be a blank string, a
+ * path to a binary that will provide these, or you can use
+ * RootTools.getWorkingToolbox()
+ */
+ public static void fixUtil(String util, String utilPath) {
+ getInternals().fixUtil(util, utilPath);
+ }
+
+ /**
+ * This will check an array of binaries, determine if they exist and determine that it has
+ * either the permissions 755, 775, or 777. If an applet is not setup correctly it will try and
+ * fix it. (This is for Busybox applets or Toolbox applets)
+ *
+ * @param utils Name of the utility to check.
+ * @return boolean to indicate whether the operation completed. Note that this is not indicative
+ * of whether the problem was fixed, just that the method did not encounter any
+ * exceptions.
+ * @throws Exception if the operation cannot be completed.
+ */
+ public static boolean fixUtils(String[] utils) throws Exception {
+ return getInternals().fixUtils(utils);
+ }
+
+ /**
+ * @param binaryName String that represent the binary to find.
+ * @return true
if the specified binary was found. Also, the path the binary was
+ * found at can be retrieved via the variable lastFoundBinaryPath, if the binary was
+ * found in more than one location this will contain all of these locations.
+ */
+ public static boolean findBinary(String binaryName) {
+ return getInternals().findBinary(binaryName);
+ }
+
+ /**
+ * @param path String that represents the path to the Busybox binary you want to retrieve the version of.
+ * @return BusyBox version is found, "" if not found.
+ */
+ public static String getBusyBoxVersion(String path) {
+ return getInternals().getBusyBoxVersion(path);
+ }
+
+ /**
+ * @return BusyBox version is found, "" if not found.
+ */
+ public static String getBusyBoxVersion() {
+ return RootTools.getBusyBoxVersion("");
+ }
+
+ /**
+ * This will return an List of Strings. Each string represents an applet available from BusyBox.
+ *
+ *
+ * @return null
If we cannot return the list of applets.
+ */
+ public static List getBusyBoxApplets() throws Exception {
+ return RootTools.getBusyBoxApplets("");
+ }
+
+ /**
+ * This will return an List of Strings. Each string represents an applet available from BusyBox.
+ *
+ *
+ * @param path Path to the busybox binary that you want the list of applets from.
+ * @return null
If we cannot return the list of applets.
+ */
+ public static List getBusyBoxApplets(String path) throws Exception {
+ return getInternals().getBusyBoxApplets(path);
+ }
+
+ /**
+ * This will open or return, if one is already open, a custom shell, you are responsible for managing the shell, reading the output
+ * and for closing the shell when you are done using it.
+ *
+ * @throws TimeoutException
+ * @throws RootDeniedException
+ * @param shellPath a String
to Indicate the path to the shell that you want to open.
+ * @param timeout an int
to Indicate the length of time before giving up on opening a shell.
+ * @throws IOException
+ */
+ public static Shell getCustomShell(String shellPath, int timeout) throws IOException, TimeoutException, RootDeniedException {
+ return Shell.startCustomShell(shellPath, timeout);
+ }
+
+ /**
+ * This will open or return, if one is already open, a custom shell, you are responsible for managing the shell, reading the output
+ * and for closing the shell when you are done using it.
+ *
+ * @throws TimeoutException
+ * @throws RootDeniedException
+ * @param shellPath a String
to Indicate the path to the shell that you want to open.
+ * @throws IOException
+ */
+ public static Shell getCustomShell(String shellPath) throws IOException, TimeoutException, RootDeniedException {
+ return RootTools.getCustomShell(shellPath, 10000);
+ }
+
+ /**
+ * @param file String that represent the file, including the full path to the file and its name.
+ * @return An instance of the class permissions from which you can get the permissions of the
+ * file or if the file could not be found or permissions couldn't be determined then
+ * permissions will be null.
+ */
+ public static Permissions getFilePermissionsSymlinks(String file) {
+ return getInternals().getFilePermissionsSymlinks(file);
+ }
+
+ /**
+ * This method will return the inode number of a file. This method is dependent on having a version of
+ * ls that supports the -i parameter.
+ *
+ * @param file path to the file that you wish to return the inode number
+ * @return String The inode number for this file or "" if the inode number could not be found.
+ */
+ public static String getInode(String file) {
+ return getInternals().getInode(file);
+ }
+
+ /**
+ * This will return an ArrayList of the class Mount. The class mount contains the following
+ * property's: device mountPoint type flags
+ *
+ * These will provide you with any information you need to work with the mount points.
+ *
+ * @return ArrayList
an ArrayList of the class Mount.
+ * @throws Exception if we cannot return the mount points.
+ */
+ public static ArrayList getMounts() throws Exception {
+ return getInternals().getMounts();
+ }
+
+ /**
+ * This will tell you how the specified mount is mounted. rw, ro, etc...
+ *
+ *
+ * @param path The mount you want to check
+ * @return String
What the mount is mounted as.
+ * @throws Exception if we cannot determine how the mount is mounted.
+ */
+ public static String getMountedAs(String path) throws Exception {
+ return getInternals().getMountedAs(path);
+ }
+
+ /**
+ * This will return the environment variable $PATH
+ *
+ * @return Set
A Set of Strings representing the environment variable $PATH
+ * @throws Exception if we cannot return the $PATH variable
+ */
+ public static Set getPath() throws Exception {
+ return getInternals().getPath();
+ }
+
+ /**
+ * This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
+ * and for closing the shell when you are done using it.
+ *
+ * @param retry a int
to indicate how many times the ROOT shell should try to open with root priviliges...
+ * @throws TimeoutException
+ * @throws RootDeniedException
+ * @param root a boolean
to Indicate whether or not you want to open a root shell or a standard shell
+ * @param timeout an int
to Indicate the length of time to wait before giving up on opening a shell.
+ * @throws IOException
+ */
+ public static Shell getShell(boolean root, int timeout, int retry) throws IOException, TimeoutException, RootDeniedException {
+ if (root)
+ return Shell.startRootShell(timeout);
+ else
+ return Shell.startShell(timeout);
+ }
+
+ /**
+ * This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
+ * and for closing the shell when you are done using it.
+ *
+ * @throws TimeoutException
+ * @throws RootDeniedException
+ * @param root a boolean
to Indicate whether or not you want to open a root shell or a standard shell
+ * @param timeout an int
to Indicate the length of time to wait before giving up on opening a shell.
+ * @throws IOException
+ */
+ public static Shell getShell(boolean root, int timeout) throws IOException, TimeoutException, RootDeniedException {
+ return getShell(root, timeout, 3);
+ }
+
+ /**
+ * This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
+ * and for closing the shell when you are done using it.
+ *
+ * @throws TimeoutException
+ * @throws RootDeniedException
+ * @param root a boolean
to Indicate whether or not you want to open a root shell or a standard shell
+ * @throws IOException
+ */
+ public static Shell getShell(boolean root) throws IOException, TimeoutException, RootDeniedException {
+ return RootTools.getShell(root, 25000);
+ }
+
+ /**
+ * Get the space for a desired partition.
+ *
+ * @param path The partition to find the space for.
+ * @return the amount if space found within the desired partition. If the space was not found
+ * then the value is -1
+ * @throws TimeoutException
+ */
+ public static long getSpace(String path) {
+ return getInternals().getSpace(path);
+ }
+
+ /**
+ * This will return a String that represent the symlink for a specified file.
+ *
+ *
+ * @param file path to the file to get the Symlink for. (must have absolute path)
+ * @return String
a String that represent the symlink for a specified file or an
+ * empty string if no symlink exists.
+ */
+ public static String getSymlink(String file) {
+ return getInternals().getSymlink(file);
+ }
+
+ /**
+ * This will return an ArrayList of the class Symlink. The class Symlink contains the following
+ * property's: path SymplinkPath
+ *
+ * These will provide you with any Symlinks in the given path.
+ *
+ * @param path path to search for Symlinks.
+ * @return ArrayList
an ArrayList of the class Symlink.
+ * @throws Exception if we cannot return the Symlinks.
+ */
+ public static ArrayList getSymlinks(String path) throws Exception {
+ return getInternals().getSymlinks(path);
+ }
+
+ /**
+ * This will return to you a string to be used in your shell commands which will represent the
+ * valid working toolbox with correct permissions. For instance, if Busybox is available it will
+ * return "busybox", if busybox is not available but toolbox is then it will return "toolbox"
+ *
+ * @return String that indicates the available toolbox to use for accessing applets.
+ */
+ public static String getWorkingToolbox() {
+ return getInternals().getWorkingToolbox();
+ }
+
+ /**
+ * Checks if there is enough Space on SDCard
+ *
+ * @param updateSize size to Check (long)
+ * @return true
if the Update will fit on SDCard, false
if not enough
+ * space on SDCard. Will also return false
, if the SDCard is not mounted as
+ * read/write
+ */
+ public static boolean hasEnoughSpaceOnSdCard(long updateSize) {
+ return getInternals().hasEnoughSpaceOnSdCard(updateSize);
+ }
+
+ /**
+ * Checks whether the toolbox or busybox binary contains a specific util
+ *
+ * @param util
+ * @param box Should contain "toolbox" or "busybox"
+ * @return true if it contains this util
+ */
+ public static boolean hasUtil(final String util, final String box) {
+ //TODO Convert this to use the new shell.
+ return getInternals().hasUtil(util, box);
+ }
+
+ /**
+ * This method can be used to unpack a binary from the raw resources folder and store it in
+ * /data/data/app.package/files/ This is typically useful if you provide your own C- or
+ * C++-based binary. This binary can then be executed using sendShell() and its full path.
+ *
+ * @param context the current activity's Context
+ * @param sourceId resource id; typically R.raw.id
+ * @param destName destination file name; appended to /data/data/app.package/files/
+ * @param mode chmod value for this file
+ * @return a boolean
which indicates whether or not we were able to create the new
+ * file.
+ */
+ public static boolean installBinary(Context context, int sourceId, String destName, String mode) {
+ return getInternals().installBinary(context, sourceId, destName, mode);
+ }
+
+ /**
+ * This method can be used to unpack a binary from the raw resources folder and store it in
+ * /data/data/app.package/files/ This is typically useful if you provide your own C- or
+ * C++-based binary. This binary can then be executed using sendShell() and its full path.
+ *
+ * @param context the current activity's Context
+ * @param sourceId resource id; typically R.raw.id
+ * @param binaryName destination file name; appended to /data/data/app.package/files/
+ * @return a boolean
which indicates whether or not we were able to create the new
+ * file.
+ */
+ public static boolean installBinary(Context context, int sourceId, String binaryName) {
+ return installBinary(context, sourceId, binaryName, "700");
+ }
+
+ /**
+ * This method checks whether a binary is installed.
+ *
+ * @param context the current activity's Context
+ * @param binaryName binary file name; appended to /data/data/app.package/files/
+ * @return a boolean
which indicates whether or not
+ * the binary already exists.
+ */
+ public static boolean hasBinary(Context context, String binaryName) {
+ return getInternals().isBinaryAvailable(context, binaryName);
+ }
+
+ /**
+ * This will let you know if an applet is available from BusyBox
+ *
+ *
+ * @param applet The applet to check for.
+ * @param path Path to the busybox binary that you want to check. (do not include binary name)
+ * @return true
if applet is available, false otherwise.
+ */
+ public static boolean isAppletAvailable(String applet, String path) {
+ return getInternals().isAppletAvailable(applet, path);
+ }
+
+ /**
+ * This will let you know if an applet is available from BusyBox
+ *
+ *
+ * @param applet The applet to check for.
+ * @return true
if applet is available, false otherwise.
+ */
+ public static boolean isAppletAvailable(String applet) {
+ return RootTools.isAppletAvailable(applet, "");
+ }
+
+ /**
+ * @return true
if your app has been given root access.
+ * @throws TimeoutException if this operation times out. (cannot determine if access is given)
+ */
+ public static boolean isAccessGiven() {
+ return getInternals().isAccessGiven();
+ }
+
+ /**
+ * @return true
if BusyBox was found.
+ */
+ public static boolean isBusyboxAvailable() {
+ return findBinary("busybox");
+ }
+
+ public static boolean isNativeToolsReady(int nativeToolsId, Context context) {
+ return getInternals().isNativeToolsReady(nativeToolsId, context);
+ }
+
+ /**
+ * This method can be used to to check if a process is running
+ *
+ * @param processName name of process to check
+ * @return true
if process was found
+ * @throws TimeoutException (Could not determine if the process is running)
+ */
+ public static boolean isProcessRunning(final String processName) {
+ //TODO convert to new shell
+ return getInternals().isProcessRunning(processName);
+ }
+
+ /**
+ * @return true
if su was found.
+ */
+ public static boolean isRootAvailable() {
+ return findBinary("su");
+ }
+
+ /**
+ * This method can be used to kill a running process
+ *
+ * @param processName name of process to kill
+ * @return true
if process was found and killed successfully
+ */
+ public static boolean killProcess(final String processName) {
+ //TODO convert to new shell
+ return getInternals().killProcess(processName);
+ }
+
+ /**
+ * This will launch the Android market looking for BusyBox
+ *
+ * @param activity pass in your Activity
+ */
+ public static void offerBusyBox(Activity activity) {
+ getInternals().offerBusyBox(activity);
+ }
+
+ /**
+ * This will launch the Android market looking for BusyBox, but will return the intent fired and
+ * starts the activity with startActivityForResult
+ *
+ * @param activity pass in your Activity
+ * @param requestCode pass in the request code
+ * @return intent fired
+ */
+ public static Intent offerBusyBox(Activity activity, int requestCode) {
+ return getInternals().offerBusyBox(activity, requestCode);
+ }
+
+ /**
+ * This will launch the Android market looking for SuperUser
+ *
+ * @param activity pass in your Activity
+ */
+ public static void offerSuperUser(Activity activity) {
+ getInternals().offerSuperUser(activity);
+ }
+
+ /**
+ * This will launch the Android market looking for SuperUser, but will return the intent fired
+ * and starts the activity with startActivityForResult
+ *
+ * @param activity pass in your Activity
+ * @param requestCode pass in the request code
+ * @return intent fired
+ */
+ public static Intent offerSuperUser(Activity activity, int requestCode) {
+ return getInternals().offerSuperUser(activity, requestCode);
+ }
+
+ /**
+ * This will take a path, which can contain the file name as well, and attempt to remount the
+ * underlying partition.
+ *
+ * For example, passing in the following string:
+ * "/system/bin/some/directory/that/really/would/never/exist" will result in /system ultimately
+ * being remounted. However, keep in mind that the longer the path you supply, the more work
+ * this has to do, and the slower it will run.
+ *
+ * @param file file path
+ * @param mountType mount type: pass in RO (Read only) or RW (Read Write)
+ * @return a boolean
which indicates whether or not the partition has been
+ * remounted as specified.
+ */
+ public static boolean remount(String file, String mountType) {
+ // Recieved a request, get an instance of Remounter
+ Remounter remounter = new Remounter();
+ // send the request.
+ return (remounter.remount(file, mountType));
+ }
+
+ /**
+ * This restarts only Android OS without rebooting the whole device. This does NOT work on all
+ * devices. This is done by killing the main init process named zygote. Zygote is restarted
+ * automatically by Android after killing it.
+ *
+ * @throws TimeoutException
+ */
+ public static void restartAndroid() {
+ RootTools.log("Restart Android");
+ killProcess("zygote");
+ }
+
+ /**
+ * Executes binary in a separated process. Before using this method, the binary has to be
+ * installed in /data/data/app.package/files/ using the installBinary method.
+ *
+ * @param context the current activity's Context
+ * @param binaryName name of installed binary
+ * @param parameter parameter to append to binary like "-vxf"
+ */
+ public static void runBinary(Context context, String binaryName, String parameter) {
+ Runner runner = new Runner(context, binaryName, parameter);
+ runner.start();
+ }
+
+ /**
+ * Executes a given command with root access or without depending on the value of the boolean passed.
+ * This will also start a root shell or a standard shell without you having to open it specifically.
+ *
+ * You will still need to close the shell after you are done using the shell.
+ *
+ * @param shell The shell to execute the command on, this can be a root shell or a standard shell.
+ * @param command The command to execute in the shell
+ * @throws IOException
+ */
+ public static void runShellCommand(Shell shell, Command command) throws IOException {
+ shell.add(command);
+ }
+
+ /**
+ * This method allows you to output debug messages only when debugging is on. This will allow
+ * you to add a debug option to your app, which by default can be left off for performance.
+ * However, when you need debugging information, a simple switch can enable it and provide you
+ * with detailed logging.
+ *
+ * This method handles whether or not to log the information you pass it depending whether or
+ * not RootTools.debugMode is on. So you can use this and not have to worry about handling it
+ * yourself.
+ *
+ * @param msg The message to output.
+ */
+ public static void log(String msg) {
+ log(null, msg, 3, null);
+ }
+
+ /**
+ * This method allows you to output debug messages only when debugging is on. This will allow
+ * you to add a debug option to your app, which by default can be left off for performance.
+ * However, when you need debugging information, a simple switch can enable it and provide you
+ * with detailed logging.
+ *
+ * This method handles whether or not to log the information you pass it depending whether or
+ * not RootTools.debugMode is on. So you can use this and not have to worry about handling it
+ * yourself.
+ *
+ * @param TAG Optional parameter to define the tag that the Log will use.
+ * @param msg The message to output.
+ */
+ public static void log(String TAG, String msg) {
+ log(TAG, msg, 3, null);
+ }
+
+ /**
+ * This method allows you to output debug messages only when debugging is on. This will allow
+ * you to add a debug option to your app, which by default can be left off for performance.
+ * However, when you need debugging information, a simple switch can enable it and provide you
+ * with detailed logging.
+ *
+ * This method handles whether or not to log the information you pass it depending whether or
+ * not RootTools.debugMode is on. So you can use this and not have to worry about handling it
+ * yourself.
+ *
+ * @param msg The message to output.
+ * @param type The type of log, 1 for verbose, 2 for error, 3 for debug
+ * @param e The exception that was thrown (Needed for errors)
+ */
+ public static void log(String msg, int type, Exception e) {
+ log(null, msg, type, e);
+ }
+
+ /**
+ * This method allows you to check whether logging is enabled.
+ * Yes, it has a goofy name, but that's to keep it as short as possible.
+ * After all writing logging calls should be painless.
+ * This method exists to save Android going through the various Java layers
+ * that are traversed any time a string is created (i.e. what you are logging)
+ *
+ * Example usage:
+ * if(islog) {
+ * StrinbBuilder sb = new StringBuilder();
+ * // ...
+ * // build string
+ * // ...
+ * log(sb.toString());
+ * }
+ *
+ *
+ * @return true if logging is enabled
+ */
+ public static boolean islog() {
+ return debugMode;
+ }
+
+ /**
+ * This method allows you to output debug messages only when debugging is on. This will allow
+ * you to add a debug option to your app, which by default can be left off for performance.
+ * However, when you need debugging information, a simple switch can enable it and provide you
+ * with detailed logging.
+ *
+ * This method handles whether or not to log the information you pass it depending whether or
+ * not RootTools.debugMode is on. So you can use this and not have to worry about handling it
+ * yourself.
+ *
+ * @param TAG Optional parameter to define the tag that the Log will use.
+ * @param msg The message to output.
+ * @param type The type of log, 1 for verbose, 2 for error, 3 for debug
+ * @param e The exception that was thrown (Needed for errors)
+ */
+ public static void log(String TAG, String msg, int type, Exception e) {
+ if (msg != null && !msg.equals("")) {
+ if (debugMode) {
+ if (TAG == null) {
+ TAG = Constants.TAG;
+ }
+
+ switch (type) {
+ case 1:
+ Log.v(TAG, msg);
+ break;
+ case 2:
+ Log.e(TAG, msg, e);
+ break;
+ case 3:
+ Log.d(TAG, msg);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/containers/Mount.java b/RootTools/src/main/java/com/lucemanb/RootTools/containers/Mount.java
new file mode 100644
index 0000000..28aecdc
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/containers/Mount.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.containers;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class Mount {
+ final File mDevice;
+ final File mMountPoint;
+ final String mType;
+ final Set mFlags;
+
+ public Mount(File device, File path, String type, String flagsStr) {
+ mDevice = device;
+ mMountPoint = path;
+ mType = type;
+ mFlags = new LinkedHashSet(Arrays.asList(flagsStr.split(",")));
+ }
+
+ public File getDevice() {
+ return mDevice;
+ }
+
+ public File getMountPoint() {
+ return mMountPoint;
+ }
+
+ public String getType() {
+ return mType;
+ }
+
+ public Set getFlags() {
+ return mFlags;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s on %s type %s %s", mDevice, mMountPoint, mType, mFlags);
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/containers/Permissions.java b/RootTools/src/main/java/com/lucemanb/RootTools/containers/Permissions.java
new file mode 100644
index 0000000..05e7b6e
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/containers/Permissions.java
@@ -0,0 +1,106 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.containers;
+
+public class Permissions {
+ String type;
+ String user;
+ String group;
+ String other;
+ String symlink;
+ int permissions;
+
+ public String getSymlink() {
+ return this.symlink;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public int getPermissions() {
+ return this.permissions;
+ }
+
+ public String getUserPermissions() {
+ return this.user;
+ }
+
+ public String getGroupPermissions() {
+ return this.group;
+ }
+
+ public String getOtherPermissions() {
+ return this.other;
+ }
+
+ public void setSymlink(String symlink) {
+ this.symlink = symlink;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public void setPermissions(int permissions) {
+ this.permissions = permissions;
+ }
+
+ public void setUserPermissions(String user) {
+ this.user = user;
+ }
+
+ public void setGroupPermissions(String group) {
+ this.group = group;
+ }
+
+ public void setOtherPermissions(String other) {
+ this.other = other;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public String getGroup() {
+ return group;
+ }
+
+ public void setGroup(String group) {
+ this.group = group;
+ }
+
+ public String getOther() {
+ return other;
+ }
+
+ public void setOther(String other) {
+ this.other = other;
+ }
+
+
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/containers/RootClass.java b/RootTools/src/main/java/com/lucemanb/RootTools/containers/RootClass.java
new file mode 100644
index 0000000..2b7242b
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/containers/RootClass.java
@@ -0,0 +1,301 @@
+package com.lucemanb.RootTools.containers;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/* #ANNOTATIONS @SupportedAnnotationTypes("com.lucemanb.RootTools.containers.RootClass.Candidate") */
+/* #ANNOTATIONS @SupportedSourceVersion(SourceVersion.RELEASE_6) */
+public class RootClass /* #ANNOTATIONS extends AbstractProcessor */ {
+
+ /* #ANNOTATIONS
+ @Override
+ public boolean process(Set extends TypeElement> typeElements, RoundEnvironment roundEnvironment) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "I was invoked!!!");
+
+ return false;
+ }
+ */
+
+ static String PATH_TO_DX = "/Users/Chris/Projects/android-sdk-macosx/build-tools/18.0.1/dx";
+ enum READ_STATE { STARTING, FOUND_ANNOTATION; };
+
+ public RootClass(String[] args) throws ClassNotFoundException, NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException, InstantiationException {
+
+ // Note: rather than calling System.load("/system/lib/libandroid_runtime.so");
+ // which would leave a bunch of unresolved JNI references,
+ // we are using the 'withFramework' class as a preloader.
+ // So, yeah, russian dolls: withFramework > RootClass > actual method
+
+ String className = args[0];
+ RootArgs actualArgs = new RootArgs();
+ actualArgs.args = new String[args.length - 1];
+ System.arraycopy(args, 1, actualArgs.args, 0, args.length - 1);
+ Class> classHandler = Class.forName(className);
+ Constructor> classConstructor = classHandler.getConstructor(RootArgs.class);
+ classConstructor.newInstance(actualArgs);
+ }
+
+ public @interface Candidate {};
+
+ public class RootArgs {
+ public String args[];
+ }
+
+ static void displayError(Exception e) {
+ // Not using system.err to make it easier to capture from
+ // calling library.
+ System.out.println("##ERR##" + e.getMessage() + "##");
+ e.printStackTrace();
+ }
+
+ // I reckon it would be better to investigate classes using getAttribute()
+ // however this method allows the developer to simply select "Run" on RootClass
+ // and immediately re-generate the necessary jar file.
+ static public class AnnotationsFinder {
+
+ private final String AVOIDDIRPATH = "stericson" + File.separator + "RootTools" + File.separator;
+ private List classFiles;
+
+ public AnnotationsFinder() throws IOException {
+ System.out.println("Discovering root class annotations...");
+ classFiles = new ArrayList();
+ lookup(new File("src"), classFiles);
+ System.out.println("Done discovering annotations. Building jar file.");
+ File builtPath = getBuiltPath();
+ if(null != builtPath) {
+ // Android! Y U no have com.google.common.base.Joiner class?
+ String rc1 = "com" + File.separator
+ + "stericson" + File.separator
+ + "RootTools" + File.separator
+ + "containers" + File.separator
+ + "RootClass.class";
+ String rc2 = "com" + File.separator
+ + "stericson" + File.separator
+ + "RootTools" + File.separator
+ + "containers" + File.separator
+ + "RootClass$RootArgs.class";
+ String rc3 = "com" + File.separator
+ + "stericson" + File.separator
+ + "RootTools" + File.separator
+ + "containers" + File.separator
+ + "RootClass$AnnotationsFinder.class";
+ String rc4 = "com" + File.separator
+ + "stericson" + File.separator
+ + "RootTools" + File.separator
+ + "containers" + File.separator
+ + "RootClass$AnnotationsFinder$1.class";
+ String rc5 = "com" + File.separator
+ + "stericson" + File.separator
+ + "RootTools" + File.separator
+ + "containers" + File.separator
+ + "RootClass$AnnotationsFinder$2.class";
+ String [] cmd;
+ boolean onWindows = (-1 != System.getProperty("os.name").toLowerCase().indexOf("win"));
+ if(onWindows) {
+ StringBuilder sb = new StringBuilder(
+ " " + rc1 + " " + rc2 + " " + rc3 + " " + rc4 + " " + rc5
+ );
+ for(File file:classFiles) {
+ sb.append(" " + file.getPath());
+ }
+ cmd = new String[] {
+ "cmd", "/C",
+ "jar cvf" +
+ " anbuild.jar" +
+ sb.toString()
+ };
+ }
+ else {
+ ArrayList al = new ArrayList();
+ al.add("jar");
+ al.add("cf");
+ al.add("anbuild.jar");
+ al.add(rc1);
+ al.add(rc2);
+ al.add(rc3);
+ al.add(rc4);
+ al.add(rc5);
+ for(File file:classFiles) {
+ al.add(file.getPath());
+ }
+ cmd = al.toArray(new String[al.size()]);
+ }
+ ProcessBuilder jarBuilder = new ProcessBuilder(cmd);
+ jarBuilder.directory(builtPath);
+ try {
+ jarBuilder.start().waitFor();
+ } catch (IOException e) {} catch (InterruptedException e) {}
+
+ System.out.println("Done building jar file. Creating dex file.");
+ if(onWindows) {
+ cmd = new String[] {
+ "cmd", "/C",
+ "dx --dex --output=res/raw/anbuild.dex "
+ + builtPath + File.separator + "anbuild.jar"
+ };
+ }
+ else {
+ cmd = new String[] {
+ getPathToDx(),
+ "--dex",
+ "--output=res/raw/anbuild.dex",
+ builtPath + File.separator + "anbuild.jar"
+ };
+ }
+ ProcessBuilder dexBuilder = new ProcessBuilder(cmd);
+ try {
+ dexBuilder.start().waitFor();
+ } catch (IOException e) {} catch (InterruptedException e) {}
+ }
+ System.out.println("All done. ::: anbuild.dex should now be in your project's res/raw/ folder :::");
+ }
+
+ protected void lookup(File path, List fileList) {
+ String desourcedPath = path.toString().replace("src/", "");
+ File[] files = path.listFiles();
+ for(File file:files) {
+ if(file.isDirectory()) {
+ if(-1 == file.getAbsolutePath().indexOf(AVOIDDIRPATH)) {
+ lookup(file, fileList);
+ }
+ }
+ else {
+ if(file.getName().endsWith(".java")) {
+ if(hasClassAnnotation(file)) {
+ final String fileNamePrefix = file.getName().replace(".java", "");
+ final File compiledPath = new File(getBuiltPath().toString() + File.separator + desourcedPath);
+ File[] classAndInnerClassFiles = compiledPath.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String filename) {
+ return filename.startsWith(fileNamePrefix);
+ }
+ });
+ for(final File matchingFile:classAndInnerClassFiles) {
+ fileList.add(new File(desourcedPath + File.separator + matchingFile.getName()));
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ protected boolean hasClassAnnotation(File file) {
+ READ_STATE readState = READ_STATE.STARTING;
+ Pattern p = Pattern.compile(" class ([A-Za-z0-9_]+)");
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+ String line;
+ while(null != (line = reader.readLine())) {
+ switch(readState) {
+ case STARTING:
+ if(-1 < line.indexOf("@RootClass.Candidate"))
+ readState = READ_STATE.FOUND_ANNOTATION;
+ break;
+ case FOUND_ANNOTATION:
+ Matcher m = p.matcher(line);
+ if(m.find()) {
+ System.out.println(" Found annotated class: " + m.group(0));
+ return true;
+ }
+ else {
+ System.err.println("Error: unmatched annotation in " +
+ file.getAbsolutePath());
+ readState = READ_STATE.STARTING;
+ }
+ break;
+ }
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ protected String getPathToDx() throws IOException {
+ String androidHome = System.getenv("ANDROID_HOME");
+ if(null == androidHome) {
+ throw new IOException("Error: you need to set $ANDROID_HOME globally");
+ }
+ String dxPath = null;
+ File[] files = new File(androidHome + File.separator + "build-tools").listFiles();
+ int recentSdkVersion = 0;
+ for(File file:files) {
+ int sdkVersion;
+ String[] sdkVersionBits = file.getName().split("[.]");
+ sdkVersion = Integer.parseInt(sdkVersionBits[0]) * 10000;
+ if(sdkVersionBits.length > 1) {
+ sdkVersion += Integer.parseInt(sdkVersionBits[1]) * 100;
+ if(sdkVersionBits.length > 2) {
+ sdkVersion += Integer.parseInt(sdkVersionBits[2]);
+ }
+ }
+ if(sdkVersion > recentSdkVersion) {
+ String tentativePath = file.getAbsolutePath() + File.separator + "dx";
+ if(new File(tentativePath).exists()) {
+ recentSdkVersion = sdkVersion;
+ dxPath = tentativePath;
+ }
+ }
+ }
+ if(dxPath == null) {
+ throw new IOException("Error: unable to find dx binary in $ANDROID_HOME");
+ }
+ return dxPath;
+ }
+
+ protected File getBuiltPath() {
+ File foundPath = null;
+
+ File ideaPath = new File("out" + File.separator + "production"); // IntelliJ
+ if(ideaPath.isDirectory()) {
+ File[] children = ideaPath.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.isDirectory();
+ }
+ });
+ if(children.length > 0) {
+ foundPath = new File(ideaPath.getAbsolutePath() + File.separator + children[0].getName());
+ }
+ }
+ if(null == foundPath) {
+ File eclipsePath = new File("bin" + File.separator + "classes"); // Eclipse IDE
+ if(eclipsePath.isDirectory()) {
+ foundPath = eclipsePath;
+ }
+ }
+
+ return foundPath;
+ }
+
+
+ };
+
+ public static void main (String [] args) {
+ try {
+ if(args.length == 0) {
+ new RootClass.AnnotationsFinder();
+ }
+ else {
+ new RootClass(args);
+ }
+ } catch (Exception e) {
+ displayError(e);
+ }
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/containers/Symlink.java b/RootTools/src/main/java/com/lucemanb/RootTools/containers/Symlink.java
new file mode 100644
index 0000000..7853568
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/containers/Symlink.java
@@ -0,0 +1,43 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.containers;
+
+import java.io.File;
+
+public class Symlink {
+ protected final File file;
+ protected final File symlinkPath;
+
+ public Symlink(File file, File path) {
+ this.file = file;
+ symlinkPath = path;
+ }
+
+ public File getFile() {
+ return this.file;
+ }
+
+ public File getSymlinkPath() {
+ return symlinkPath;
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/exceptions/RootDeniedException.java b/RootTools/src/main/java/com/lucemanb/RootTools/exceptions/RootDeniedException.java
new file mode 100644
index 0000000..415c877
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/exceptions/RootDeniedException.java
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.exceptions;
+
+public class RootDeniedException extends Exception {
+
+ private static final long serialVersionUID = -8713947214162841310L;
+
+ public RootDeniedException(String error) {
+ super(error);
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/execution/Command.java b/RootTools/src/main/java/com/lucemanb/RootTools/execution/Command.java
new file mode 100644
index 0000000..f783869
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/execution/Command.java
@@ -0,0 +1,312 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.execution;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.lucemanb.RootTools.RootTools;
+
+import java.io.IOException;
+
+public abstract class Command {
+
+ ExecutionMonitor executionMonitor = null;
+ Handler mHandler = null;
+ boolean executing = false;
+
+ String[] command = {};
+ boolean javaCommand = false;
+ Context context = null;
+ boolean finished = false;
+ boolean terminated = false;
+ boolean handlerEnabled = true;
+ int exitCode = -1;
+ int id = 0;
+ int timeout = RootTools.default_Command_Timeout;
+
+ public abstract void commandOutput(int id, String line);
+ public abstract void commandTerminated(int id, String reason);
+ public abstract void commandCompleted(int id, int exitCode);
+
+ /**
+ * Constructor for executing a normal shell command
+ * @param id the id of the command being executed
+ * @param command the command, or commands, to be executed.
+ */
+ public Command(int id, String... command) {
+ this.command = command;
+ this.id = id;
+
+ createHandler(RootTools.handlerEnabled);
+ }
+
+ /**
+ * Constructor for executing a normal shell command
+ * @param id the id of the command being executed
+ * @param handlerEnabled when true the handler will be used to call the
+ * callback methods if possible.
+ * @param command the command, or commands, to be executed.
+ */
+ public Command(int id, boolean handlerEnabled, String... command) {
+ this.command = command;
+ this.id = id;
+
+ createHandler(handlerEnabled);
+ }
+
+ /**
+ * Constructor for executing a normal shell command
+ * @param id the id of the command being executed
+ * @param timeout the time allowed before the shell will give up executing the command
+ * and throw a TimeoutException.
+ * @param command the command, or commands, to be executed.
+ */
+ public Command(int id, int timeout, String... command) {
+ this.command = command;
+ this.id = id;
+ this.timeout = timeout;
+
+ createHandler(RootTools.handlerEnabled);
+ }
+
+ /**
+ * Constructor for executing Java commands rather than binaries
+ * @param javaCommand when True, it is a java command.
+ * @param context needed to execute java command.
+ */
+ public Command(int id, boolean javaCommand, Context context, String... command) {
+ this(id, command);
+ this.javaCommand = javaCommand;
+ this.context = context;
+ }
+
+ /**
+ * Constructor for executing Java commands rather than binaries
+ * @param javaCommand when True, it is a java command.
+ * @param context needed to execute java command.
+ */
+ public Command(int id, boolean handlerEnabled, boolean javaCommand, Context context, String... command) {
+ this(id, handlerEnabled, command);
+ this.javaCommand = javaCommand;
+ this.context = context;
+ }
+
+ /**
+ * Constructor for executing Java commands rather than binaries
+ * @param javaCommand when True, it is a java command.
+ * @param context needed to execute java command.
+ */
+ public Command(int id, int timeout, boolean javaCommand, Context context, String... command) {
+ this(id, timeout, command);
+ this.javaCommand = javaCommand;
+ this.context = context;
+ }
+
+ protected void finishCommand() {
+ executing = false;
+ finished = true;
+ this.notifyAll();
+ }
+
+ protected void commandFinished() {
+ if (!terminated) {
+ synchronized (this) {
+ if (mHandler != null && handlerEnabled) {
+ Message msg = mHandler.obtainMessage();
+ Bundle bundle = new Bundle();
+ bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_COMPLETED);
+ msg.setData(bundle);
+ mHandler.sendMessage(msg);
+ }
+ else {
+ commandCompleted(id, exitCode);
+ }
+
+ RootTools.log("Command " + id + " finished.");
+ finishCommand();
+ }
+ }
+ }
+
+ private void createHandler(boolean handlerEnabled) {
+
+ this.handlerEnabled = handlerEnabled;
+
+ if (Looper.myLooper() != null && handlerEnabled) {
+ RootTools.log("CommandHandler created");
+ mHandler = new CommandHandler();
+ }
+ else {
+ RootTools.log("CommandHandler not created");
+ }
+ }
+
+ public String getCommand() {
+ StringBuilder sb = new StringBuilder();
+
+ if(javaCommand) {
+ String filePath = context.getFilesDir().getPath();
+ for (int i = 0; i < command.length; i++) {
+ /*
+ * TODO Make withFramework optional for applications
+ * that do not require access to the fw. -CFR
+ */
+ sb.append(
+ "dalvikvm -cp " + filePath + "/anbuild.dex"
+ + " com.android.internal.util.WithFramework"
+ + " com.lucemanb.RootTools.containers.RootClass "
+ + command[i]);
+ sb.append('\n');
+ }
+ }
+ else {
+ for (int i = 0; i < command.length; i++) {
+ sb.append(command[i]);
+ sb.append('\n');
+ }
+ }
+ return sb.toString();
+ }
+
+ public boolean isExecuting() {
+ return executing;
+ }
+
+ public boolean isHandlerEnabled() {
+ return handlerEnabled;
+ }
+
+ public boolean isFinished() {
+ return finished;
+ }
+
+ public int getExitCode() {
+ return this.exitCode;
+ }
+
+ protected void setExitCode(int code) {
+ synchronized (this) {
+ exitCode = code;
+ }
+ }
+
+ protected void startExecution() {
+ executionMonitor = new ExecutionMonitor();
+ executionMonitor.setPriority(Thread.MIN_PRIORITY);
+ executionMonitor.start();
+ executing = true;
+ }
+
+ public void terminate(String reason) {
+ try {
+ Shell.closeAll();
+ RootTools.log("Terminating all shells.");
+ terminated(reason);
+ } catch (IOException e) {}
+ }
+
+ protected void terminated(String reason) {
+ synchronized (Command.this) {
+
+
+ if (mHandler != null && handlerEnabled) {
+ Message msg = mHandler.obtainMessage();
+ Bundle bundle = new Bundle();
+ bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_TERMINATED);
+ bundle.putString(CommandHandler.TEXT, reason);
+ msg.setData(bundle);
+ mHandler.sendMessage(msg);
+ }
+ else {
+ commandTerminated(id, reason);
+ }
+
+ RootTools.log("Command " + id + " did not finish because it was terminated. Termination reason: " + reason);
+ setExitCode(-1);
+ terminated = true;
+ finishCommand();
+ }
+ }
+
+ protected void output(int id, String line) {
+ if (mHandler != null && handlerEnabled) {
+ Message msg = mHandler.obtainMessage();
+ Bundle bundle = new Bundle();
+ bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_OUTPUT);
+ bundle.putString(CommandHandler.TEXT, line);
+ msg.setData(bundle);
+ mHandler.sendMessage(msg);
+ }
+ else {
+ commandOutput(id, line);
+ }
+ }
+
+ private class ExecutionMonitor extends Thread {
+ public void run() {
+ while (!finished) {
+
+ synchronized (Command.this) {
+ try {
+ Command.this.wait(timeout);
+ } catch (InterruptedException e) {}
+ }
+
+ if (!finished) {
+ RootTools.log("Timeout Exception has occurred.");
+ terminate("Timeout Exception");
+ }
+ }
+ }
+ }
+
+ private class CommandHandler extends Handler {
+ static final public String ACTION = "action";
+ static final public String TEXT = "text";
+
+ static final public int COMMAND_OUTPUT = 0x01;
+ static final public int COMMAND_COMPLETED = 0x02;
+ static final public int COMMAND_TERMINATED = 0x03;
+
+ public void handleMessage(Message msg) {
+ int action = msg.getData().getInt(ACTION);
+ String text = msg.getData().getString(TEXT);
+
+ switch (action) {
+ case COMMAND_OUTPUT:
+ commandOutput(id, text);
+ break;
+ case COMMAND_COMPLETED:
+ commandCompleted(id, exitCode);
+ break;
+ case COMMAND_TERMINATED:
+ commandTerminated(id, text);
+ break;
+ }
+ }
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/execution/CommandCapture.java b/RootTools/src/main/java/com/lucemanb/RootTools/execution/CommandCapture.java
new file mode 100644
index 0000000..49cbf5d
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/execution/CommandCapture.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.execution;
+
+import com.lucemanb.RootTools.RootTools;
+
+public class CommandCapture extends Command {
+ private StringBuilder sb = new StringBuilder();
+
+ public CommandCapture(int id, String... command) {
+ super(id, command);
+ }
+
+ public CommandCapture(int id, boolean handlerEnabled, String... command) {
+ super(id, handlerEnabled, command);
+ }
+
+ public CommandCapture(int id, int timeout, String... command) {
+ super(id, timeout, command);
+ }
+
+
+ @Override
+ public void commandOutput(int id, String line) {
+ sb.append(line).append('\n');
+ RootTools.log("Command", "ID: " + id + ", " + line);
+ }
+
+ @Override
+ public void commandTerminated(int id, String reason) {
+ //pass
+ }
+
+ @Override
+ public void commandCompleted(int id, int exitcode) {
+ //pass
+ }
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/execution/JavaCommandCapture.java b/RootTools/src/main/java/com/lucemanb/RootTools/execution/JavaCommandCapture.java
new file mode 100644
index 0000000..cf484f1
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/execution/JavaCommandCapture.java
@@ -0,0 +1,43 @@
+package com.lucemanb.RootTools.execution;
+
+import android.content.Context;
+
+import com.lucemanb.RootTools.RootTools;
+
+public class JavaCommandCapture extends Command {
+ private StringBuilder sb = new StringBuilder();
+
+ public JavaCommandCapture(int id, Context context, String... command) {
+ super(id, true, context, command);
+ }
+
+ public JavaCommandCapture(int id, boolean handlerEnabled, Context context, String... command) {
+ super(id, handlerEnabled, true, context, command);
+ }
+
+ public JavaCommandCapture(int id, int timeout, Context context, String... command) {
+ super(id, timeout, true, context, command);
+ }
+
+ @Override
+ public void commandOutput(int id, String line) {
+ sb.append(line).append('\n');
+ RootTools.log("Command", "ID: " + id + ", " + line);
+ }
+
+ @Override
+ public void commandTerminated(int id, String reason) {
+ // pass
+ }
+
+ @Override
+ public void commandCompleted(int id, int exitCode) {
+ // pass
+ }
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/execution/Shell.java b/RootTools/src/main/java/com/lucemanb/RootTools/execution/Shell.java
new file mode 100644
index 0000000..4dda3e9
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/execution/Shell.java
@@ -0,0 +1,648 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+package com.lucemanb.RootTools.execution;
+
+import android.content.Context;
+
+import com.lucemanb.RootTools.RootTools;
+import com.lucemanb.RootTools.exceptions.RootDeniedException;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+public class Shell {
+
+ private final Process proc;
+ private final BufferedReader in;
+ private final OutputStreamWriter out;
+ private final List commands = new ArrayList();
+
+ //indicates whether or not to close the shell
+ private boolean close = false;
+
+ private static String error = "";
+ private static final String token = "F*D^W@#FGF";
+ private static Shell rootShell = null;
+ private static Shell shell = null;
+ private static Shell customShell = null;
+
+ private static int shellTimeout = 25000;
+ public static boolean isExecuting = false;
+ public static boolean isReading = false;
+
+ private int maxCommands = 1000;
+ private int read = 0;
+ private int write = 0;
+ private int totalExecuted = 0;
+ private int totalRead = 0;
+ private boolean isCleaning = false;
+
+ //private constructor responsible for opening/constructing the shell
+ private Shell(String cmd) throws IOException, TimeoutException, RootDeniedException {
+
+ RootTools.log("Starting shell: " + cmd);
+
+ proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
+ in = new BufferedReader(new InputStreamReader(proc.getInputStream(), "UTF-8"));
+ out = new OutputStreamWriter(proc.getOutputStream(), "UTF-8");
+
+ /**
+ * Thread responsible for carrying out the requested operations
+ */
+ Worker worker = new Worker(proc, in, out);
+ worker.start();
+
+ try {
+ /**
+ * The flow of execution will wait for the thread to die or wait until the
+ * given timeout has expired.
+ *
+ * The result of the worker, which is determined by the exit code of the worker,
+ * will tell us if the operation was completed successfully or it the operation
+ * failed.
+ */
+ worker.join(shellTimeout);
+
+ /**
+ * The operation could not be completed before the timeout occured.
+ */
+ if (worker.exit == -911) {
+
+ try {
+ proc.destroy();
+ } catch (Exception e) {}
+
+ closeQuietly(in);
+ closeQuietly(out);
+
+ throw new TimeoutException(error);
+ }
+ /**
+ * Root access denied?
+ */
+ else if (worker.exit == -42) {
+
+ try {
+ proc.destroy();
+ } catch (Exception e) {}
+
+ closeQuietly(in);
+ closeQuietly(out);
+
+ throw new RootDeniedException("Root Access Denied");
+ }
+ /**
+ * Normal exit
+ */
+ else {
+ /**
+ * The shell is open.
+ *
+ * Start two threads, one to handle the input and one to handle the output.
+ *
+ * input, and output are runnables that the threads execute.
+ */
+ Thread si = new Thread(input, "Shell Input");
+ si.setPriority(Thread.NORM_PRIORITY);
+ si.start();
+
+ Thread so = new Thread(output, "Shell Output");
+ so.setPriority(Thread.NORM_PRIORITY);
+ so.start();
+ }
+ } catch (InterruptedException ex) {
+ worker.interrupt();
+ Thread.currentThread().interrupt();
+ throw new TimeoutException();
+ }
+ }
+
+
+ public Command add(Command command) throws IOException {
+ if (close)
+ throw new IllegalStateException(
+ "Unable to add commands to a closed shell");
+
+ while (isCleaning) {
+ //Don't add commands while cleaning
+ ;
+ }
+ commands.add(command);
+
+ notifyThreads();
+
+ return command;
+ }
+
+ public void useCWD(Context context) throws IOException, TimeoutException, RootDeniedException {
+ add(
+ new CommandCapture(
+ -1,
+ false,
+ "cd " + context.getApplicationInfo().dataDir));
+ }
+
+ private void cleanCommands() {
+ isCleaning = true;
+ int toClean = Math.abs(maxCommands - (maxCommands / 4));
+ RootTools.log("Cleaning up: " + toClean);
+ for (int i = 0; i < toClean; i++) {
+ commands.remove(0);
+ }
+
+ read = commands.size() - 1;
+ write = commands.size() - 1;
+ isCleaning = false;
+ }
+
+ private void closeQuietly(final Reader input) {
+ try {
+ if (input != null) {
+ input.close();
+ }
+ } catch (Exception ignore) {}
+ }
+
+ private void closeQuietly(final Writer output) {
+ try {
+ if (output != null) {
+ output.close();
+ }
+ } catch (Exception ignore) {}
+ }
+
+ public void close() throws IOException {
+ if (this == rootShell)
+ rootShell = null;
+ else if (this == shell)
+ shell = null;
+ else if (this == customShell)
+ customShell = null;
+ synchronized (commands) {
+ /**
+ * instruct the two threads monitoring input and output
+ * of the shell to close.
+ */
+ this.close = true;
+ notifyThreads();
+ }
+ }
+
+ public static void closeCustomShell() throws IOException {
+ if (customShell == null)
+ return;
+ customShell.close();
+ }
+
+ public static void closeRootShell() throws IOException {
+ if (rootShell == null)
+ return;
+ rootShell.close();
+ }
+
+ public static void closeShell() throws IOException {
+ if (shell == null)
+ return;
+ shell.close();
+ }
+
+ public static void closeAll() throws IOException {
+ closeShell();
+ closeRootShell();
+ closeCustomShell();
+ }
+
+ public int getCommandQueuePosition(Command cmd) {
+ return commands.indexOf(cmd);
+ }
+
+ public String getCommandQueuePositionString(Command cmd) {
+ return "Command is in position " + getCommandQueuePosition(cmd) + " currently executing command at position " + write;
+ }
+
+ public static Shell getOpenShell() {
+ if (customShell != null)
+ return customShell;
+ else if (rootShell != null)
+ return rootShell;
+ else
+ return shell;
+ }
+
+ public static boolean isShellOpen() {
+ if (shell == null)
+ return false;
+ else
+ return true;
+ }
+
+ public static boolean isCustomShellOpen() {
+ if (customShell == null)
+ return false;
+ else
+ return true;
+ }
+
+ public static boolean isRootShellOpen() {
+ if (rootShell == null)
+ return false;
+ else
+ return true;
+ }
+
+ public static boolean isAnyShellOpen() {
+ if (shell != null)
+ return true;
+ else if (rootShell != null)
+ return true;
+ else if (customShell != null)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Runnable to write commands to the open shell.
+ *
+ * When writing commands we stay in a loop and wait for new
+ * commands to added to "commands"
+ *
+ * The notification of a new command is handled by the method add in this class
+ */
+ private Runnable input = new Runnable() {
+ public void run() {
+ try {
+ while (true) {
+
+ synchronized (commands) {
+ /**
+ * While loop is used in the case that notifyAll is called
+ * and there are still no commands to be written, a rare
+ * case but one that could happen.
+ */
+ while (!close && write >= commands.size()) {
+ isExecuting = false;
+ commands.wait();
+ }
+ }
+
+ if (write >= maxCommands) {
+
+ /**
+ * wait for the read to catch up.
+ */
+ while (read != write)
+ {
+ RootTools.log("Waiting for read and write to catch up before cleanup.");
+ }
+ /**
+ * Clean up the commands, stay neat.
+ */
+ cleanCommands();
+ }
+
+ /**
+ * Write the new command
+ *
+ * We write the command followed by the token to indicate
+ * the end of the command execution
+ */
+ if (write < commands.size()) {
+ isExecuting = true;
+ Command cmd = commands.get(write);
+ cmd.startExecution();
+ RootTools.log("Executing: " + cmd.getCommand());
+
+ out.write(cmd.getCommand());
+ String line = "\necho " + token + " " + totalExecuted + " $?\n";
+ out.write(line);
+ out.flush();
+ write++;
+ totalExecuted++;
+ } else if (close) {
+ /**
+ * close the thread, the shell is closing.
+ */
+ isExecuting = false;
+ out.write("\nexit 0\n");
+ out.flush();
+ RootTools.log("Closing shell");
+ return;
+ }
+ }
+ } catch (IOException e) {
+ RootTools.log(e.getMessage(), 2, e);
+ } catch (InterruptedException e) {
+ RootTools.log(e.getMessage(), 2, e);
+ } finally {
+ write = 0;
+ closeQuietly(out);
+ }
+ }
+ };
+
+ protected void notifyThreads() {
+ Thread t = new Thread() {
+ public void run() {
+ synchronized (commands) {
+ commands.notifyAll();
+ }
+ }
+ };
+
+ t.start();
+ }
+
+ /**
+ * Runnable to monitor the responses from the open shell.
+ */
+ private Runnable output = new Runnable() {
+ public void run() {
+ try {
+ Command command = null;
+
+ while (!close) {
+ isReading = false;
+ String line = in.readLine();
+ isReading = true;
+
+ /**
+ * If we recieve EOF then the shell closed
+ */
+ if (line == null)
+ break;
+
+ if (command == null) {
+ if (read >= commands.size()) {
+ if (close)
+ break;
+
+ continue;
+ }
+ command = commands.get(read);
+ }
+
+ /**
+ * trying to determine if all commands have been completed.
+ *
+ * if the token is present then the command has finished execution.
+ */
+ int pos = line.indexOf(token);
+
+
+ if (pos == -1) {
+ /**
+ * send the output for the implementer to process
+ */
+ command.output(command.id, line);
+ }
+ if (pos > 0) {
+ /**
+ * token is suffix of output, send output part to implementer
+ */
+ command.output(command.id, line.substring(0, pos));
+ }
+ if (pos >= 0) {
+ line = line.substring(pos);
+ String fields[] = line.split(" ");
+
+ if (fields.length >= 2 && fields[1] != null) {
+ int id = 0;
+
+ try {
+ id = Integer.parseInt(fields[1]);
+ } catch (NumberFormatException e) {
+ }
+
+ int exitCode = -1;
+
+ try {
+ exitCode = Integer.parseInt(fields[2]);
+ } catch (NumberFormatException e) {
+ }
+
+ if (id == totalRead) {
+ command.setExitCode(exitCode);
+ command.commandFinished();
+ command = null;
+
+ read++;
+ totalRead++;
+ continue;
+ }
+ }
+ }
+ }
+
+ RootTools.log("Read all output");
+ try {
+ proc.waitFor();
+ proc.destroy();
+ } catch (Exception e) {}
+
+ closeQuietly(out);
+ closeQuietly(in);
+
+ RootTools.log("Shell destroyed");
+
+ while (read < commands.size()) {
+ if (command == null)
+ command = commands.get(read);
+
+ command.terminated("Unexpected Termination.");
+ command = null;
+ read++;
+ }
+
+ read = 0;
+
+ } catch (IOException e) {
+ RootTools.log(e.getMessage(), 2, e);
+ }
+ }
+ };
+
+ public static void runRootCommand(Command command) throws IOException, TimeoutException, RootDeniedException {
+ startRootShell().add(command);
+ }
+
+ public static void runCommand(Command command) throws IOException, TimeoutException {
+ startShell().add(command);
+ }
+
+ public static Shell startRootShell() throws IOException, TimeoutException, RootDeniedException {
+ return Shell.startRootShell(20000, 3);
+ }
+
+ public static Shell startRootShell(int timeout) throws IOException, TimeoutException, RootDeniedException {
+ return Shell.startRootShell(timeout, 3);
+ }
+
+ public static Shell startRootShell(int timeout, int retry) throws IOException, TimeoutException, RootDeniedException {
+
+ Shell.shellTimeout = timeout;
+
+ if (rootShell == null) {
+ RootTools.log("Starting Root Shell!");
+ String cmd = "su";
+ // keep prompting the user until they accept for x amount of times...
+ int retries = 0;
+ while (rootShell == null) {
+ try {
+ rootShell = new Shell(cmd);
+ } catch (IOException e) {
+ if (retries++ >= retry) {
+ RootTools.log("IOException, could not start shell");
+ throw e;
+ }
+ }
+ }
+ } else {
+ RootTools.log("Using Existing Root Shell!");
+ }
+
+ return rootShell;
+ }
+
+ public static Shell startCustomShell(String shellPath) throws IOException, TimeoutException, RootDeniedException {
+ return Shell.startCustomShell(shellPath, 20000);
+ }
+
+ public static Shell startCustomShell(String shellPath, int timeout) throws IOException, TimeoutException, RootDeniedException {
+ Shell.shellTimeout = timeout;
+
+ if (customShell == null) {
+ RootTools.log("Starting Custom Shell!");
+ customShell = new Shell(shellPath);
+ } else
+ RootTools.log("Using Existing Custom Shell!");
+
+ return customShell;
+ }
+
+ public static Shell startShell() throws IOException, TimeoutException {
+ return Shell.startShell(20000);
+ }
+
+ public static Shell startShell(int timeout) throws IOException, TimeoutException {
+ Shell.shellTimeout = timeout;
+
+ try {
+ if (shell == null) {
+ RootTools.log("Starting Shell!");
+ shell = new Shell("/system/bin/sh");
+ } else
+ RootTools.log("Using Existing Shell!");
+ return shell;
+ } catch (RootDeniedException e) {
+ //Root Denied should never be thrown.
+ throw new IOException();
+ }
+ }
+
+ protected static class Worker extends Thread {
+ public int exit = -911;
+
+ public Process proc;
+ public BufferedReader in;
+ public OutputStreamWriter out;
+
+ private Worker(Process proc, BufferedReader in, OutputStreamWriter out) {
+ this.proc = proc;
+ this.in = in;
+ this.out = out;
+ }
+
+ public void run() {
+
+ /**
+ * Trying to open the shell.
+ *
+ * We echo "Started" and we look for it in the output.
+ *
+ * If we find the output then the shell is open and we return.
+ *
+ * If we do not find it then we determine the error and report
+ * it by setting the value of the variable exit
+ */
+ try {
+ out.write("echo Started\n");
+ out.flush();
+
+ while (true) {
+ String line = in.readLine();
+ if (line == null) {
+ throw new EOFException();
+ }
+ if ("".equals(line))
+ continue;
+ if ("Started".equals(line)) {
+ this.exit = 1;
+ setShellOom();
+ break;
+ }
+
+ Shell.error = "unkown error occured.";
+ }
+ } catch (IOException e) {
+ exit = -42;
+ if (e.getMessage() != null)
+ Shell.error = e.getMessage();
+ else
+ Shell.error = "RootAccess denied?.";
+ }
+
+ }
+
+ /*
+ * setOom for shell processes (sh and su if root shell)
+ * and discard outputs
+ *
+ */
+ private void setShellOom() {
+ try {
+ Class> processClass = proc.getClass();
+ Field field = null;
+ try {
+ field = processClass.getDeclaredField("pid");
+ } catch (NoSuchFieldException e) {
+ field = processClass.getDeclaredField("id");
+ }
+ field.setAccessible(true);
+ int pid = (Integer) field.get(proc);
+ out.write("(echo -17 > /proc/" + pid + "/oom_adj) &> /dev/null\n");
+ out.write("(echo -17 > /proc/$$/oom_adj) &> /dev/null\n");
+ out.flush();
+ } catch (Exception e) {
+ }
+ }
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/internal/Installer.java b/RootTools/src/main/java/com/lucemanb/RootTools/internal/Installer.java
new file mode 100644
index 0000000..de383f6
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/internal/Installer.java
@@ -0,0 +1,229 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.internal;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.lucemanb.RootTools.RootTools;
+import com.lucemanb.RootTools.execution.Command;
+import com.lucemanb.RootTools.execution.CommandCapture;
+import com.lucemanb.RootTools.execution.Shell;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+class Installer {
+
+ //-------------
+ //# Installer #
+ //-------------
+
+ static final String LOG_TAG = "RootTools::Installer";
+
+ static final String BOGUS_FILE_NAME = "bogus";
+
+ Context context;
+ String filesPath;
+
+ public Installer(Context context)
+ throws IOException {
+
+ this.context = context;
+ this.filesPath = context.getFilesDir().getCanonicalPath();
+ }
+
+ /**
+ * This method can be used to unpack a binary from the raw resources folder and store it in
+ * /data/data/app.package/files/
+ * This is typically useful if you provide your own C- or C++-based binary.
+ * This binary can then be executed using sendShell() and its full path.
+ *
+ * @param sourceId resource id; typically R.raw.id
+ * @param destName destination file name; appended to /data/data/app.package/files/
+ * @param mode chmod value for this file
+ * @return a boolean
which indicates whether or not we were
+ * able to create the new file.
+ */
+ protected boolean installBinary(int sourceId, String destName, String mode) {
+ File mf = new File(filesPath + File.separator + destName);
+ if (!mf.exists() ||
+ !getFileSignature(mf).equals(
+ getStreamSignature(
+ context.getResources().openRawResource(sourceId))
+ )) {
+ Log.e(LOG_TAG, "Installing a new version of binary: " + destName);
+ // First, does our files/ directory even exist?
+ // We cannot wait for android to lazily create it as we will soon
+ // need it.
+ try {
+ FileInputStream fis = context.openFileInput(BOGUS_FILE_NAME);
+ fis.close();
+ } catch (FileNotFoundException e) {
+ FileOutputStream fos = null;
+ try {
+ fos = context.openFileOutput("bogus", Context.MODE_PRIVATE);
+ fos.write("justcreatedfilesdirectory".getBytes());
+ } catch (Exception ex) {
+ if (RootTools.debugMode) {
+ Log.e(LOG_TAG, ex.toString());
+ }
+ return false;
+ } finally {
+ if (null != fos) {
+ try {
+ fos.close();
+ context.deleteFile(BOGUS_FILE_NAME);
+ } catch (IOException e1) {}
+ }
+ }
+ } catch (IOException ex) {
+ if (RootTools.debugMode) {
+ Log.e(LOG_TAG, ex.toString());
+ }
+ return false;
+ }
+
+ // Only now can we start creating our actual file
+ InputStream iss = context.getResources().openRawResource(sourceId);
+ ReadableByteChannel rfc = Channels.newChannel(iss);
+ FileOutputStream oss = null;
+ try {
+ oss = new FileOutputStream(mf);
+ FileChannel ofc = oss.getChannel();
+ long pos = 0;
+ try {
+ long size = iss.available();
+ while ((pos += ofc.transferFrom(rfc, pos, size
+ - pos)) < size)
+ ;
+ } catch (IOException ex) {
+ if (RootTools.debugMode) {
+ Log.e(LOG_TAG, ex.toString());
+ }
+ return false;
+ }
+ } catch (FileNotFoundException ex) {
+ if (RootTools.debugMode) {
+ Log.e(LOG_TAG, ex.toString());
+ }
+ return false;
+ } finally {
+ if (oss != null) {
+ try {
+ oss.flush();
+ oss.getFD().sync();
+ oss.close();
+ } catch (Exception e) {
+ }
+ }
+ }
+ try {
+ iss.close();
+ } catch (IOException ex) {
+ if (RootTools.debugMode) {
+ Log.e(LOG_TAG, ex.toString());
+ }
+ return false;
+ }
+
+ try {
+ CommandCapture command = new CommandCapture(0, false, "chmod " + mode + " " + filesPath + File.separator + destName);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ } catch (Exception e) {}
+ }
+ return true;
+ }
+
+ protected boolean isBinaryInstalled(String destName) {
+ boolean installed = false;
+ File mf = new File(filesPath + File.separator + destName);
+ if (mf.exists()) {
+ installed = true;
+ // TODO: pass mode as argument and check it matches
+ }
+ return installed;
+ }
+
+ protected String getFileSignature(File f) {
+ String signature = "";
+ try {
+ signature = getStreamSignature(new FileInputStream(f));
+ } catch (FileNotFoundException ex) {
+ Log.e(LOG_TAG, ex.toString());
+ }
+ return signature;
+ }
+
+ /*
+ * Note: this method will close any string passed to it
+ */
+ protected String getStreamSignature(InputStream is) {
+ String signature = "";
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ DigestInputStream dis = new DigestInputStream(is, md);
+ byte [] buffer = new byte[4096];
+ while(-1 != dis.read(buffer));
+ byte[] digest = md.digest();
+ StringBuffer sb = new StringBuffer();
+
+ for(int i=0; i path;
+ protected static ArrayList mounts;
+ protected static ArrayList symlinks;
+ protected static List results;
+ protected static String inode = "";
+ protected static Permissions permissions;
+
+ // regex to get pid out of ps line, example:
+ // root 2611 0.0 0.0 19408 2104 pts/2 S 13:41 0:00 bash
+ protected static final String PS_REGEX = "^\\S+\\s+([0-9]+).*$";
+ protected static Pattern psPattern;
+
+ static {
+ psPattern = Pattern.compile(PS_REGEX);
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/internal/Remounter.java b/RootTools/src/main/java/com/lucemanb/RootTools/internal/Remounter.java
new file mode 100644
index 0000000..487df68
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/internal/Remounter.java
@@ -0,0 +1,177 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.internal;
+
+import com.lucemanb.RootTools.Constants;
+import com.lucemanb.RootTools.RootTools;
+import com.lucemanb.RootTools.containers.Mount;
+import com.lucemanb.RootTools.execution.Command;
+import com.lucemanb.RootTools.execution.CommandCapture;
+import com.lucemanb.RootTools.execution.Shell;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class Remounter {
+
+ //-------------
+ //# Remounter #
+ //-------------
+
+ /**
+ * This will take a path, which can contain the file name as well,
+ * and attempt to remount the underlying partition.
+ *
+ * For example, passing in the following string:
+ * "/system/bin/some/directory/that/really/would/never/exist"
+ * will result in /system ultimately being remounted.
+ * However, keep in mind that the longer the path you supply, the more work this has to do,
+ * and the slower it will run.
+ *
+ * @param file file path
+ * @param mountType mount type: pass in RO (Read only) or RW (Read Write)
+ * @return a boolean
which indicates whether or not the partition
+ * has been remounted as specified.
+ */
+
+ public boolean remount(String file, String mountType) {
+
+ //if the path has a trailing slash get rid of it.
+ if (file.endsWith("/") && !file.equals("/")) {
+ file = file.substring(0, file.lastIndexOf("/"));
+ }
+ //Make sure that what we are trying to remount is in the mount list.
+ boolean foundMount = false;
+
+ while (!foundMount) {
+ try {
+ for (Mount mount : RootTools.getMounts()) {
+ RootTools.log(mount.getMountPoint().toString());
+
+ if (file.equals(mount.getMountPoint().toString())) {
+ foundMount = true;
+ break;
+ }
+ }
+ } catch (Exception e) {
+ if (RootTools.debugMode) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+ if (!foundMount) {
+ try {
+ file = (new File(file).getParent());
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ }
+
+ Mount mountPoint = findMountPointRecursive(file);
+
+ if (mountPoint != null) {
+
+ RootTools.log(Constants.TAG, "Remounting " + mountPoint.getMountPoint().getAbsolutePath() + " as " + mountType.toLowerCase());
+ final boolean isMountMode = mountPoint.getFlags().contains(mountType.toLowerCase());
+
+ if (!isMountMode) {
+ //grab an instance of the internal class
+ try {
+ CommandCapture command = new CommandCapture(0,
+ true,
+ "busybox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(),
+ "toolbox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(),
+ "mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(),
+ "/system/bin/toolbox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath()
+ );
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ } catch (Exception e) {}
+
+ mountPoint = findMountPointRecursive(file);
+ }
+
+ if (mountPoint != null) {
+ RootTools.log(Constants.TAG, mountPoint.getFlags() + " AND " + mountType.toLowerCase());
+ if (mountPoint.getFlags().contains(mountType.toLowerCase())) {
+ RootTools.log(mountPoint.getFlags().toString());
+ return true;
+ } else {
+ RootTools.log(mountPoint.getFlags().toString());
+ return false;
+ }
+ }
+ else {
+ RootTools.log("mount is null, file was: " + file + " mountType was: " + mountType);
+ }
+ }
+ else {
+ RootTools.log("mount is null, file was: " + file + " mountType was: " + mountType);
+ }
+
+ return false;
+ }
+
+ private Mount findMountPointRecursive(String file) {
+ try {
+ ArrayList mounts = RootTools.getMounts();
+
+ for (File path = new File(file); path != null; ) {
+ for (Mount mount : mounts) {
+ if (mount.getMountPoint().equals(path)) {
+ return mount;
+ }
+ }
+ }
+
+ return null;
+
+ } catch (IOException e) {
+ if (RootTools.debugMode) {
+ e.printStackTrace();
+ }
+ } catch (Exception e) {
+ if (RootTools.debugMode) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ private void commandWait(Command cmd) {
+ synchronized (cmd) {
+ try {
+ if (!cmd.isFinished()) {
+ cmd.wait(2000);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/internal/RootToolsInternalMethods.java b/RootTools/src/main/java/com/lucemanb/RootTools/internal/RootToolsInternalMethods.java
new file mode 100644
index 0000000..78c0ff9
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/internal/RootToolsInternalMethods.java
@@ -0,0 +1,1557 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.internal;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.StatFs;
+import android.util.Log;
+
+import com.lucemanb.RootTools.Constants;
+import com.lucemanb.RootTools.RootTools;
+import com.lucemanb.RootTools.containers.Mount;
+import com.lucemanb.RootTools.containers.Permissions;
+import com.lucemanb.RootTools.containers.Symlink;
+import com.lucemanb.RootTools.execution.Command;
+import com.lucemanb.RootTools.execution.CommandCapture;
+import com.lucemanb.RootTools.execution.Shell;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+
+public final class RootToolsInternalMethods {
+
+ // --------------------
+ // # Internal methods #
+ // --------------------
+
+ protected RootToolsInternalMethods() {}
+
+ public static void getInstance() {
+ //this will allow RootTools to be the only one to get an instance of this class.
+ RootTools.setRim(new RootToolsInternalMethods());
+ }
+
+ public boolean returnPath() throws TimeoutException {
+
+ CommandCapture command = null;
+ LineNumberReader lnr = null;
+ FileReader fr = null;
+
+ try {
+ if (!RootTools.exists("/data/local/tmp")) {
+
+ command = new CommandCapture(0, false, "mkdir /data/local/tmp");
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ }
+
+ InternalVariables.path = new HashSet();
+
+ String mountedas = RootTools.getMountedAs("/");
+ RootTools.remount("/", "rw");
+
+ command = new CommandCapture(0, false, "chmod 0777 /init.rc");
+ Shell.startRootShell().add(command);
+
+ command = new CommandCapture(0, false,
+ "dd if=/init.rc of=/data/local/tmp/init.rc");
+ Shell.startRootShell().add(command);
+
+ command = new CommandCapture(0, false,
+ "chmod 0777 /data/local/tmp/init.rc");
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ RootTools.remount("/", mountedas);
+
+ fr = new FileReader("/data/local/tmp/init.rc");
+ lnr = new LineNumberReader(fr);
+
+ String line;
+ while ((line = lnr.readLine()) != null) {
+ RootTools.log(line);
+ if (line.contains("export PATH")) {
+ int tmp = line.indexOf("/");
+ InternalVariables.path = new HashSet(
+ Arrays.asList(line.substring(tmp).split(":")));
+ return true;
+ }
+ }
+ return false;
+ } catch (Exception e) {
+ if (RootTools.debugMode) {
+ RootTools.log("Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return false;
+ } finally {
+ try {
+ fr.close();
+ } catch (Exception e) {}
+
+ try {
+ lnr.close();
+ } catch (Exception e) {}
+ }
+ }
+
+ public ArrayList getSymLinks() throws IOException {
+
+ LineNumberReader lnr = null;
+ FileReader fr = null;
+
+ try {
+
+ fr = new FileReader("/data/local/symlinks.txt");
+ lnr = new LineNumberReader(fr);
+
+ String line;
+ ArrayList symlink = new ArrayList();
+
+ while ((line = lnr.readLine()) != null) {
+
+ RootTools.log(line);
+
+ String[] fields = line.split(" ");
+ symlink.add(new Symlink(new File(fields[fields.length - 3]), // file
+ new File(fields[fields.length - 1]) // SymlinkPath
+ ));
+ }
+ return symlink;
+ } finally {
+ try {
+ fr.close();
+ } catch (Exception e) {}
+
+ try {
+ lnr.close();
+ } catch (Exception e) {}
+ }
+ }
+
+ public Permissions getPermissions(String line) {
+
+ String[] lineArray = line.split(" ");
+ String rawPermissions = lineArray[0];
+
+ if (rawPermissions.length() == 10
+ && (rawPermissions.charAt(0) == '-'
+ || rawPermissions.charAt(0) == 'd' || rawPermissions
+ .charAt(0) == 'l')
+ && (rawPermissions.charAt(1) == '-' || rawPermissions.charAt(1) == 'r')
+ && (rawPermissions.charAt(2) == '-' || rawPermissions.charAt(2) == 'w')) {
+ RootTools.log(rawPermissions);
+
+ Permissions permissions = new Permissions();
+
+ permissions.setType(rawPermissions.substring(0, 1));
+
+ RootTools.log(permissions.getType());
+
+ permissions.setUserPermissions(rawPermissions.substring(1, 4));
+
+ RootTools.log(permissions.getUserPermissions());
+
+ permissions.setGroupPermissions(rawPermissions.substring(4, 7));
+
+ RootTools.log(permissions.getGroupPermissions());
+
+ permissions.setOtherPermissions(rawPermissions.substring(7, 10));
+
+ RootTools.log(permissions.getOtherPermissions());
+
+ StringBuilder finalPermissions = new StringBuilder();
+ finalPermissions.append(parseSpecialPermissions(rawPermissions));
+ finalPermissions.append(parsePermissions(permissions.getUserPermissions()));
+ finalPermissions.append(parsePermissions(permissions.getGroupPermissions()));
+ finalPermissions.append(parsePermissions(permissions.getOtherPermissions()));
+
+ permissions.setPermissions(Integer.parseInt(finalPermissions.toString()));
+
+ return permissions;
+ }
+
+ return null;
+ }
+
+ public int parsePermissions(String permission) {
+ int tmp;
+ if (permission.charAt(0) == 'r')
+ tmp = 4;
+ else
+ tmp = 0;
+
+ RootTools.log("permission " + tmp);
+ RootTools.log("character " + permission.charAt(0));
+
+ if (permission.charAt(1) == 'w')
+ tmp += 2;
+ else
+ tmp += 0;
+
+ RootTools.log("permission " + tmp);
+ RootTools.log("character " + permission.charAt(1));
+
+ if (permission.charAt(2) == 'x')
+ tmp += 1;
+ else
+ tmp += 0;
+
+ RootTools.log("permission " + tmp);
+ RootTools.log("character " + permission.charAt(2));
+
+ return tmp;
+ }
+
+ public int parseSpecialPermissions(String permission) {
+ int tmp = 0;
+ if (permission.charAt(2) == 's')
+ tmp += 4;
+
+ if (permission.charAt(5) == 's')
+ tmp += 2;
+
+ if (permission.charAt(8) == 't')
+ tmp += 1;
+
+ RootTools.log("special permissions " + tmp);
+
+ return tmp;
+ }
+
+ /**
+ * Copys a file to a destination. Because cp is not available on all android devices, we have a
+ * fallback on the cat command
+ *
+ * @param source example: /data/data/org.adaway/files/hosts
+ * @param destination example: /system/etc/hosts
+ * @param remountAsRw remounts the destination as read/write before writing to it
+ * @param preserveFileAttributes tries to copy file attributes from source to destination, if only cat is available
+ * only permissions are preserved
+ * @return true if it was successfully copied
+ */
+ public boolean copyFile(String source, String destination, boolean remountAsRw,
+ boolean preserveFileAttributes) {
+
+ CommandCapture command = null;
+ boolean result = true;
+
+ try {
+ // mount destination as rw before writing to it
+ if (remountAsRw) {
+ RootTools.remount(destination, "RW");
+ }
+
+ // if cp is available and has appropriate permissions
+ if (checkUtil("cp")) {
+ RootTools.log("cp command is available!");
+
+ if (preserveFileAttributes) {
+ command = new CommandCapture(0, false, "cp -fp " + source + " " + destination);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ //ensure that the file was copied, an exitcode of zero means success
+ result = command.getExitCode() == 0;
+
+ } else {
+ command = new CommandCapture(0, false, "cp -f " + source + " " + destination);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ //ensure that the file was copied, an exitcode of zero means success
+ result = command.getExitCode() == 0;
+
+ }
+ } else {
+ if (checkUtil("busybox") && hasUtil("cp", "busybox")) {
+ RootTools.log("busybox cp command is available!");
+
+ if (preserveFileAttributes) {
+ command = new CommandCapture(0, false, "busybox cp -fp " + source + " " + destination);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ } else {
+ command = new CommandCapture(0, false, "busybox cp -f " + source + " " + destination);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ }
+ } else { // if cp is not available use cat
+ // if cat is available and has appropriate permissions
+ if (checkUtil("cat")) {
+ RootTools.log("cp is not available, use cat!");
+
+ int filePermission = -1;
+ if (preserveFileAttributes) {
+ // get permissions of source before overwriting
+ Permissions permissions = getFilePermissionsSymlinks(source);
+ filePermission = permissions.getPermissions();
+ }
+
+ // copy with cat
+ command = new CommandCapture(0, false, "cat " + source + " > " + destination);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ if (preserveFileAttributes) {
+ // set premissions of source to destination
+ command = new CommandCapture(0, false, "chmod " + filePermission + " " + destination);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+ }
+ } else {
+ result = false;
+ }
+ }
+ }
+
+ // mount destination back to ro
+ if (remountAsRw) {
+ RootTools.remount(destination, "RO");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ result = false;
+ }
+
+ if (command != null) {
+ //ensure that the file was copied, an exitcode of zero means success
+ result = command.getExitCode() == 0;
+ }
+
+ return result;
+ }
+
+ /**
+ * This will check a given binary, determine if it exists and determine that
+ * it has either the permissions 755, 775, or 777.
+ *
+ * @param util Name of the utility to check.
+ * @return boolean to indicate whether the binary is installed and has
+ * appropriate permissions.
+ */
+ public boolean checkUtil(String util) {
+ if (RootTools.findBinary(util)) {
+
+ List binaryPaths = new ArrayList();
+ binaryPaths.addAll(RootTools.lastFoundBinaryPaths);
+
+ for (String path : binaryPaths) {
+ Permissions permissions = RootTools
+ .getFilePermissionsSymlinks(path + "/" + util);
+
+ if (permissions != null) {
+ String permission;
+
+ if (Integer.toString(permissions.getPermissions()).length() > 3)
+ permission = Integer.toString(permissions.getPermissions()).substring(1);
+ else
+ permission = Integer.toString(permissions.getPermissions());
+
+ if (permission.equals("755") || permission.equals("777")
+ || permission.equals("775")) {
+ RootTools.utilPath = path + "/" + util;
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Deletes a file or directory
+ *
+ * @param target example: /data/data/org.adaway/files/hosts
+ * @param remountAsRw remounts the destination as read/write before writing to it
+ * @return true if it was successfully deleted
+ */
+ public boolean deleteFileOrDirectory(String target, boolean remountAsRw) {
+ boolean result = true;
+
+ try {
+ // mount destination as rw before writing to it
+ if (remountAsRw) {
+ RootTools.remount(target, "RW");
+ }
+
+ if (hasUtil("rm", "toolbox")) {
+ RootTools.log("rm command is available!");
+
+ CommandCapture command = new CommandCapture(0, false, "rm -r " + target);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ if (command.getExitCode() != 0) {
+ RootTools.log("target not exist or unable to delete file");
+ result = false;
+ }
+ } else {
+ if (checkUtil("busybox") && hasUtil("rm", "busybox")) {
+ RootTools.log("busybox cp command is available!");
+
+ CommandCapture command = new CommandCapture(0, false, "busybox rm -rf " + target);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ if (command.getExitCode() != 0) {
+ RootTools.log("target not exist or unable to delete file");
+ result = false;
+ }
+ }
+ }
+
+ // mount destination back to ro
+ if (remountAsRw) {
+ RootTools.remount(target, "RO");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ result = false;
+ }
+
+ return result;
+ }
+
+ /**
+ * Use this to check whether or not a file exists on the filesystem.
+ *
+ * @param file String that represent the file, including the full path to the
+ * file and its name.
+ * @return a boolean that will indicate whether or not the file exists.
+ */
+ public boolean exists(final String file) {
+ final List result = new ArrayList();
+
+ CommandCapture command = new CommandCapture(0, false, "ls " + file) {
+ @Override
+ public void output(int arg0, String arg1) {
+ RootTools.log(arg1);
+ result.add(arg1);
+ }
+ };
+
+ try {
+ //Try not to open a new shell if one is open.
+ if (!Shell.isAnyShellOpen()) {
+ Shell.startShell().add(command);
+ commandWait(command);
+
+ }
+ else {
+ Shell.getOpenShell().add(command);
+ commandWait(command);
+ }
+ } catch (Exception e) {
+ return false;
+ }
+
+ for (String line : result) {
+ if (line.trim().equals(file)) {
+ return true;
+ }
+ }
+
+ try {
+ RootTools.closeShell(false);
+ } catch (Exception e) {
+ }
+
+ result.clear();
+ try {
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ } catch (Exception e) {
+ return false;
+ }
+
+ //Avoid concurrent modification...
+ List final_result = new ArrayList();
+ final_result.addAll(result);
+
+ for (String line : final_result) {
+ if (line.trim().equals(file)) {
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * This will try and fix a given binary. (This is for Busybox applets or Toolbox applets) By
+ * "fix", I mean it will try and symlink the binary from either toolbox or Busybox and fix the
+ * permissions if the permissions are not correct.
+ *
+ * @param util Name of the utility to fix.
+ * @param utilPath path to the toolbox that provides ln, rm, and chmod. This can be a blank string, a
+ * path to a binary that will provide these, or you can use
+ * RootTools.getWorkingToolbox()
+ */
+ public void fixUtil(String util, String utilPath) {
+ try {
+ RootTools.remount("/system", "rw");
+
+ if (RootTools.findBinary(util)) {
+ List paths = new ArrayList();
+ paths.addAll(RootTools.lastFoundBinaryPaths);
+ for (String path : paths) {
+ CommandCapture command = new CommandCapture(0, false, utilPath + " rm " + path + "/" + util);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ }
+
+ CommandCapture command = new CommandCapture(0, false, utilPath + " ln -s " + utilPath + " /system/bin/" + util, utilPath + " chmod 0755 /system/bin/" + util);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ }
+
+ RootTools.remount("/system", "ro");
+ } catch (Exception e) {
+ }
+ }
+
+ /**
+ * This will check an array of binaries, determine if they exist and determine that it has
+ * either the permissions 755, 775, or 777. If an applet is not setup correctly it will try and
+ * fix it. (This is for Busybox applets or Toolbox applets)
+ *
+ * @param utils Name of the utility to check.
+ * @return boolean to indicate whether the operation completed. Note that this is not indicative
+ * of whether the problem was fixed, just that the method did not encounter any
+ * exceptions.
+ * @throws Exception if the operation cannot be completed.
+ */
+ public boolean fixUtils(String[] utils) throws Exception {
+
+ for (String util : utils) {
+ if (!checkUtil(util)) {
+ if (checkUtil("busybox")) {
+ if (hasUtil(util, "busybox")) {
+ fixUtil(util, RootTools.utilPath);
+ }
+ } else {
+ if (checkUtil("toolbox")) {
+ if (hasUtil(util, "toolbox")) {
+ fixUtil(util, RootTools.utilPath);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param binaryName String that represent the binary to find.
+ * @return true
if the specified binary was found. Also, the path the binary was
+ * found at can be retrieved via the variable lastFoundBinaryPath, if the binary was
+ * found in more than one location this will contain all of these locations.
+ */
+ public boolean findBinary(final String binaryName) {
+ boolean found = false;
+ RootTools.lastFoundBinaryPaths.clear();
+
+ final List list = new ArrayList();
+ String[] places = {"/sbin/", "/system/bin/", "/system/xbin/", "/data/local/xbin/",
+ "/data/local/bin/", "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/"};
+
+ RootTools.log("Checking for " + binaryName);
+
+ //Try to use stat first
+ try {
+ for(final String path : places) {
+ CommandCapture cc = new CommandCapture(0, false, "stat " + path + binaryName) {
+ @Override
+ public void commandOutput(int id, String line) {
+ if(line.contains("File: ") && line.contains(binaryName)) {
+ list.add(path);
+
+ RootTools.log(binaryName + " was found here: " + path);
+ }
+
+ RootTools.log(line);
+ }
+ };
+
+ RootTools.getShell(false).add(cc);
+ commandWait(cc);
+
+ }
+
+ found = !list.isEmpty();
+ } catch (Exception e) {
+ RootTools.log(binaryName + " was not found, more information MAY be available with Debugging on.");
+ }
+
+ if (!found) {
+ RootTools.log("Trying second method");
+
+ for (String where : places) {
+ if (RootTools.exists(where + binaryName)) {
+ RootTools.log(binaryName + " was found here: " + where);
+ list.add(where);
+ found = true;
+ } else {
+ RootTools.log(binaryName + " was NOT found here: " + where);
+ }
+ }
+ }
+
+ if(!found) {
+ RootTools.log("Trying third method");
+
+ try {
+ Set paths = RootTools.getPath();
+
+ if (paths != null) {
+ for (String path : paths) {
+ if (RootTools.exists(path + "/" + binaryName)) {
+ RootTools.log(binaryName + " was found here: " + path);
+ list.add(path);
+ found = true;
+ } else {
+ RootTools.log(binaryName + " was NOT found here: " + path);
+ }
+ }
+ }
+ } catch (Exception e) {
+ RootTools.log(binaryName + " was not found, more information MAY be available with Debugging on.");
+ }
+ }
+
+ Collections.reverse(list);
+
+ RootTools.lastFoundBinaryPaths.addAll(list);
+
+ return found;
+ }
+
+ /**
+ * This will return an List of Strings. Each string represents an applet available from BusyBox.
+ *
+ *
+ * @param path Path to the busybox binary that you want the list of applets from.
+ * @return null
If we cannot return the list of applets.
+ */
+ public List getBusyBoxApplets(String path) throws Exception {
+
+ if (path != null && !path.endsWith("/") && !path.equals("")) {
+ path += "/";
+ } else if (path == null) {
+ //Don't know what the user wants to do...what am I pshycic?
+ throw new Exception("Path is null, please specifiy a path");
+ }
+
+ final List results = new ArrayList();
+
+ CommandCapture command = new CommandCapture(Constants.BBA, false, path + "busybox --list") {
+
+ @Override
+ public void output(int id, String line) {
+ if (id == Constants.BBA) {
+ if (!line.trim().equals("") && !line.trim().contains("not found")) {
+ results.add(line);
+ }
+ }
+ }
+ };
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ return results;
+ }
+
+ /**
+ * @return BusyBox version is found, "" if not found.
+ */
+ public String getBusyBoxVersion(String path) {
+
+ if (!path.equals("") && !path.endsWith("/")) {
+ path += "/";
+ }
+
+ RootTools.log("Getting BusyBox Version");
+ InternalVariables.busyboxVersion = "";
+ try {
+ CommandCapture command = new CommandCapture(Constants.BBV, false, path + "busybox") {
+ @Override
+ public void output(int id, String line) {
+ if (id == Constants.BBV) {
+ if (line.startsWith("BusyBox") && InternalVariables.busyboxVersion.equals("")) {
+ String[] temp = line.split(" ");
+ InternalVariables.busyboxVersion = temp[1];
+ }
+ }
+ }
+ };
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ } catch (Exception e) {
+ RootTools.log("BusyBox was not found, more information MAY be available with Debugging on.");
+ return "";
+ }
+
+ return InternalVariables.busyboxVersion;
+ }
+
+ /**
+ * @return long Size, converted to kilobytes (from xxx or xxxm or xxxk etc.)
+ */
+ public long getConvertedSpace(String spaceStr) {
+ try {
+ double multiplier = 1.0;
+ char c;
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < spaceStr.length(); i++) {
+ c = spaceStr.charAt(i);
+ if (!Character.isDigit(c) && c != '.') {
+ if (c == 'm' || c == 'M') {
+ multiplier = 1024.0;
+ } else if (c == 'g' || c == 'G') {
+ multiplier = 1024.0 * 1024.0;
+ }
+ break;
+ }
+ sb.append(spaceStr.charAt(i));
+ }
+ return (long) Math.ceil(Double.valueOf(sb.toString()) * multiplier);
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ /**
+ * This method will return the inode number of a file. This method is dependent on having a version of
+ * ls that supports the -i parameter.
+ *
+ * @param file path to the file that you wish to return the inode number
+ * @return String The inode number for this file or "" if the inode number could not be found.
+ */
+ public String getInode(String file) {
+ try {
+ CommandCapture command = new CommandCapture(Constants.GI, false, "/data/local/ls -i " + file) {
+
+ @Override
+ public void output(int id, String line) {
+ if (id == Constants.GI) {
+ if (!line.trim().equals("") && Character.isDigit((char) line.trim().substring(0, 1).toCharArray()[0])) {
+ InternalVariables.inode = line.trim().split(" ")[0];
+ }
+ }
+ }
+ };
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ return InternalVariables.inode;
+ } catch (Exception ignore) {
+ return "";
+ }
+ }
+
+ /**
+ * @return true
if your app has been given root access.
+ * @throws TimeoutException if this operation times out. (cannot determine if access is given)
+ */
+ public boolean isAccessGiven() {
+ try {
+ RootTools.log("Checking for Root access");
+ InternalVariables.accessGiven = false;
+
+ CommandCapture command = new CommandCapture(Constants.IAG, false, "id") {
+ @Override
+ public void output(int id, String line) {
+ if (id == Constants.IAG) {
+ Set ID = new HashSet(Arrays.asList(line.split(" ")));
+ for (String userid : ID) {
+ RootTools.log(userid);
+
+ if (userid.toLowerCase().contains("uid=0")) {
+ InternalVariables.accessGiven = true;
+ RootTools.log("Access Given");
+ break;
+ }
+ }
+ if (!InternalVariables.accessGiven) {
+ RootTools.log("Access Denied?");
+ }
+ }
+ }
+ };
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ if (InternalVariables.accessGiven) {
+ return true;
+ } else {
+ return false;
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public boolean isNativeToolsReady(int nativeToolsId, Context context) {
+ RootTools.log("Preparing Native Tools");
+ InternalVariables.nativeToolsReady = false;
+
+ Installer installer;
+ try {
+ installer = new Installer(context);
+ } catch (IOException ex) {
+ if (RootTools.debugMode) {
+ ex.printStackTrace();
+ }
+ return false;
+ }
+
+ if (installer.isBinaryInstalled("nativetools")) {
+ InternalVariables.nativeToolsReady = true;
+ } else {
+ InternalVariables.nativeToolsReady = installer.installBinary(nativeToolsId,
+ "nativetools", "700");
+ }
+ return InternalVariables.nativeToolsReady;
+ }
+
+ /**
+ * @param file String that represent the file, including the full path to the
+ * file and its name.
+ * @return An instance of the class permissions from which you can get the
+ * permissions of the file or if the file could not be found or
+ * permissions couldn't be determined then permissions will be null.
+ */
+ public Permissions getFilePermissionsSymlinks(String file) {
+ RootTools.log("Checking permissions for " + file);
+ if (RootTools.exists(file)) {
+ RootTools.log(file + " was found.");
+ try {
+
+ CommandCapture command = new CommandCapture(
+ Constants.FPS, false, "ls -l " + file,
+ "busybox ls -l " + file,
+ "/system/bin/failsafe/toolbox ls -l " + file,
+ "toolbox ls -l " + file) {
+ @Override
+ public void output(int id, String line) {
+ if (id == Constants.FPS) {
+ String symlink_final = "";
+
+ String[] lineArray = line.split(" ");
+ if (lineArray[0].length() != 10) {
+ return;
+ }
+
+ RootTools.log("Line " + line);
+
+ try {
+ String[] symlink = line.split(" ");
+ if (symlink[symlink.length - 2].equals("->")) {
+ RootTools.log("Symlink found.");
+ symlink_final = symlink[symlink.length - 1];
+ }
+ } catch (Exception e) {}
+
+ try {
+ InternalVariables.permissions = getPermissions(line);
+ if (InternalVariables.permissions != null) {
+ InternalVariables.permissions.setSymlink(symlink_final);
+ }
+ } catch (Exception e) {
+ RootTools.log(e.getMessage());
+ }
+ }
+ }
+ };
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ return InternalVariables.permissions;
+
+ } catch (Exception e) {
+ RootTools.log(e.getMessage());
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * This will return an ArrayList of the class Mount. The class mount contains the following
+ * property's: device mountPoint type flags
+ *
+ * These will provide you with any information you need to work with the mount points.
+ *
+ * @return ArrayList
an ArrayList of the class Mount.
+ * @throws Exception if we cannot return the mount points.
+ */
+ public ArrayList getMounts() throws Exception {
+
+ Shell shell = RootTools.getShell(true);
+
+ CommandCapture cmd = new CommandCapture(0,
+ false,
+ "cat /proc/mounts > /data/local/RootToolsMounts",
+ "chmod 0777 /data/local/RootToolsMounts");
+ shell.add(cmd);
+ this.commandWait(cmd);
+
+ LineNumberReader lnr = null;
+ FileReader fr = null;
+
+ try {
+ fr = new FileReader("/data/local/RootToolsMounts");
+ lnr = new LineNumberReader(fr);
+ String line;
+ ArrayList mounts = new ArrayList();
+ while ((line = lnr.readLine()) != null) {
+
+ RootTools.log(line);
+
+ String[] fields = line.split(" ");
+ mounts.add(new Mount(new File(fields[0]), // device
+ new File(fields[1]), // mountPoint
+ fields[2], // fstype
+ fields[3] // flags
+ ));
+ }
+ InternalVariables.mounts = mounts;
+
+ if (InternalVariables.mounts != null) {
+ return InternalVariables.mounts;
+ } else {
+ throw new Exception();
+ }
+ } finally {
+ try {
+ fr.close();
+ fr = null;
+ } catch (Exception e) {}
+
+ try {
+ lnr.close();
+ lnr = null;
+ } catch (Exception e) {}
+ }
+ }
+
+ /**
+ * This will tell you how the specified mount is mounted. rw, ro, etc...
+ *
+ *
+ * @param path mount you want to check
+ * @return String
What the mount is mounted as.
+ * @throws Exception if we cannot determine how the mount is mounted.
+ */
+ public String getMountedAs(String path) throws Exception {
+ InternalVariables.mounts = getMounts();
+ String mp;
+ if (InternalVariables.mounts != null) {
+ for (Mount mount : InternalVariables.mounts) {
+
+ mp = mount.getMountPoint().getAbsolutePath();
+
+ if (mp.equals("/")) {
+ if (path.equals("/")) {
+ return (String) mount.getFlags().toArray()[0];
+ }
+ else {
+ continue;
+ }
+ }
+
+ if (path.equals(mp) || path.startsWith(mp + "/")) {
+ RootTools.log((String) mount.getFlags().toArray()[0]);
+ return (String) mount.getFlags().toArray()[0];
+ }
+ }
+
+ throw new Exception();
+ } else {
+ throw new Exception();
+ }
+ }
+
+ /**
+ * This will return the environment variable $PATH
+ *
+ * @return Set
A Set of Strings representing the environment variable $PATH
+ * @throws Exception if we cannot return the $PATH variable
+ */
+ public Set getPath() throws Exception {
+ if (InternalVariables.path != null) {
+ return InternalVariables.path;
+ } else {
+ if (returnPath()) {
+ return InternalVariables.path;
+ } else {
+ throw new Exception();
+ }
+ }
+ }
+
+ /**
+ * Get the space for a desired partition.
+ *
+ * @param path The partition to find the space for.
+ * @return the amount if space found within the desired partition. If the space was not found
+ * then the value is -1
+ * @throws TimeoutException
+ */
+ public long getSpace(String path) {
+ InternalVariables.getSpaceFor = path;
+ boolean found = false;
+ RootTools.log("Looking for Space");
+ try {
+ final CommandCapture command = new CommandCapture(Constants.GS, false, "df " + path) {
+
+ @Override
+ public void output(int id, String line) {
+ if (id == Constants.GS) {
+ if (line.contains(InternalVariables.getSpaceFor.trim())) {
+ InternalVariables.space = line.split(" ");
+ }
+ }
+ }
+ };
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ } catch (Exception e) {}
+
+ if (InternalVariables.space != null) {
+ RootTools.log("First Method");
+
+ for (String spaceSearch : InternalVariables.space) {
+
+ RootTools.log(spaceSearch);
+
+ if (found) {
+ return getConvertedSpace(spaceSearch);
+ } else if (spaceSearch.equals("used,")) {
+ found = true;
+ }
+ }
+
+ // Try this way
+ int count = 0, targetCount = 3;
+
+ RootTools.log("Second Method");
+
+ if (InternalVariables.space[0].length() <= 5) {
+ targetCount = 2;
+ }
+
+ for (String spaceSearch : InternalVariables.space) {
+
+ RootTools.log(spaceSearch);
+ if (spaceSearch.length() > 0) {
+ RootTools.log(spaceSearch + ("Valid"));
+ if (count == targetCount) {
+ return getConvertedSpace(spaceSearch);
+ }
+ count++;
+ }
+ }
+ }
+ RootTools.log("Returning -1, space could not be determined.");
+ return -1;
+ }
+
+ /**
+ * This will return a String that represent the symlink for a specified file.
+ *
+ *
+ * @param file file to get the Symlink for. (must have absolute path)
+ * @return String
a String that represent the symlink for a specified file or an
+ * empty string if no symlink exists.
+ */
+ public String getSymlink(String file) {
+ RootTools.log("Looking for Symlink for " + file);
+
+ try {
+ final List results = new ArrayList();
+
+ CommandCapture command = new CommandCapture(Constants.GSYM, false, "ls -l " + file) {
+
+ @Override
+ public void output(int id, String line) {
+ if (id == Constants.GSYM) {
+ if (!line.trim().equals("")) {
+ results.add(line);
+ }
+ }
+ }
+ };
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ String[] symlink = results.get(0).split(" ");
+ if (symlink.length > 2 && symlink[symlink.length - 2].equals("->")) {
+ RootTools.log("Symlink found.");
+
+ String final_symlink = "";
+ if (!symlink[symlink.length - 1].equals("") && !symlink[symlink.length - 1].contains("/")) {
+ //We assume that we need to get the path for this symlink as it is probably not absolute.
+ findBinary(symlink[symlink.length - 1]);
+ if (RootTools.lastFoundBinaryPaths.size() > 0) {
+ //We return the first found location.
+ final_symlink = RootTools.lastFoundBinaryPaths.get(0) + "/" + symlink[symlink.length - 1];
+ } else {
+ //we couldnt find a path, return the symlink by itself.
+ final_symlink = symlink[symlink.length - 1];
+ }
+ } else {
+ final_symlink = symlink[symlink.length - 1];
+ }
+
+ return final_symlink;
+ }
+ } catch (Exception e) {
+ if (RootTools.debugMode)
+ e.printStackTrace();
+ }
+
+ RootTools.log("Symlink not found");
+ return "";
+ }
+
+ /**
+ * This will return an ArrayList of the class Symlink. The class Symlink contains the following
+ * property's: path SymplinkPath
+ *
+ * These will provide you with any Symlinks in the given path.
+ *
+ * @param path path to search for Symlinks.
+ * @return ArrayList
an ArrayList of the class Symlink.
+ * @throws Exception if we cannot return the Symlinks.
+ */
+ public ArrayList getSymlinks(String path) throws Exception {
+
+ // this command needs find
+ if (!checkUtil("find")) {
+ throw new Exception();
+ }
+
+ CommandCapture command = new CommandCapture(0, false, "dd if=/dev/zero of=/data/local/symlinks.txt bs=1024 count=1", "chmod 0777 /data/local/symlinks.txt");
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ command = new CommandCapture(0, false, "find " + path + " -type l -exec ls -l {} \\; > /data/local/symlinks.txt");
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ InternalVariables.symlinks = getSymLinks();
+ if (InternalVariables.symlinks != null) {
+ return InternalVariables.symlinks;
+ } else {
+ throw new Exception();
+ }
+ }
+
+ /**
+ * This will return to you a string to be used in your shell commands which will represent the
+ * valid working toolbox with correct permissions. For instance, if Busybox is available it will
+ * return "busybox", if busybox is not available but toolbox is then it will return "toolbox"
+ *
+ * @return String that indicates the available toolbox to use for accessing applets.
+ */
+ public String getWorkingToolbox() {
+ if (RootTools.checkUtil("busybox")) {
+ return "busybox";
+ } else if (RootTools.checkUtil("toolbox")) {
+ return "toolbox";
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Checks if there is enough Space on SDCard
+ *
+ * @param updateSize size to Check (long)
+ * @return true
if the Update will fit on SDCard, false
if not enough
+ * space on SDCard. Will also return false
, if the SDCard is not mounted as
+ * read/write
+ */
+ public boolean hasEnoughSpaceOnSdCard(long updateSize) {
+ RootTools.log("Checking SDcard size and that it is mounted as RW");
+ String status = Environment.getExternalStorageState();
+ if (!status.equals(Environment.MEDIA_MOUNTED)) {
+ return false;
+ }
+ File path = Environment.getExternalStorageDirectory();
+ StatFs stat = new StatFs(path.getPath());
+ long blockSize = stat.getBlockSize();
+ long availableBlocks = stat.getAvailableBlocks();
+ return (updateSize < availableBlocks * blockSize);
+ }
+
+ /**
+ * Checks whether the toolbox or busybox binary contains a specific util
+ *
+ * @param util
+ * @param box Should contain "toolbox" or "busybox"
+ * @return true if it contains this util
+ */
+ public boolean hasUtil(final String util, final String box) {
+
+ InternalVariables.found = false;
+
+ // only for busybox and toolbox
+ if (!(box.endsWith("toolbox") || box.endsWith("busybox"))) {
+ return false;
+ }
+
+ try {
+
+ CommandCapture command = new CommandCapture(0, false, box.endsWith("toolbox") ? box + " " + util : box + " --list") {
+
+ @Override
+ public void output(int id, String line) {
+ if (box.endsWith("toolbox")) {
+ if (!line.contains("no such tool")) {
+ InternalVariables.found = true;
+ }
+ } else if (box.endsWith("busybox")) {
+ // go through all lines of busybox --list
+ if (line.contains(util)) {
+ RootTools.log("Found util!");
+ InternalVariables.found = true;
+ }
+ }
+ }
+ };
+ RootTools.getShell(true).add(command);
+ commandWait(command);
+
+ if (InternalVariables.found) {
+ RootTools.log("Box contains " + util + " util!");
+ return true;
+ } else {
+ RootTools.log("Box does not contain " + util + " util!");
+ return false;
+ }
+ } catch (Exception e) {
+ RootTools.log(e.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * This method can be used to unpack a binary from the raw resources folder and store it in
+ * /data/data/app.package/files/ This is typically useful if you provide your own C- or
+ * C++-based binary. This binary can then be executed using sendShell() and its full path.
+ *
+ * @param context the current activity's Context
+ * @param sourceId resource id; typically R.raw.id
+ * @param destName destination file name; appended to /data/data/app.package/files/
+ * @param mode chmod value for this file
+ * @return a boolean
which indicates whether or not we were able to create the new
+ * file.
+ */
+ public boolean installBinary(Context context, int sourceId, String destName, String mode) {
+ Installer installer;
+
+ try {
+ installer = new Installer(context);
+ } catch (IOException ex) {
+ if (RootTools.debugMode) {
+ ex.printStackTrace();
+ }
+ return false;
+ }
+
+ return (installer.installBinary(sourceId, destName, mode));
+ }
+
+ /**
+ * This method checks whether a binary is installed.
+ *
+ * @param context the current activity's Context
+ * @param binaryName binary file name; appended to /data/data/app.package/files/
+ * @return a boolean
which indicates whether or not
+ * the binary already exists.
+ */
+ public boolean isBinaryAvailable(Context context, String binaryName) {
+ Installer installer;
+
+ try {
+ installer = new Installer(context);
+ } catch (IOException ex) {
+ if (RootTools.debugMode) {
+ ex.printStackTrace();
+ }
+ return false;
+ }
+
+ return (installer.isBinaryInstalled(binaryName));
+ }
+
+ /**
+ * This will let you know if an applet is available from BusyBox
+ *
+ *
+ * @param applet The applet to check for.
+ * @return true
if applet is available, false otherwise.
+ */
+ public boolean isAppletAvailable(String applet, String binaryPath) {
+ try {
+ for (String aplet : getBusyBoxApplets(binaryPath)) {
+ if (aplet.equals(applet)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (Exception e) {
+ RootTools.log(e.toString());
+ return false;
+ }
+ }
+
+ /**
+ * This method can be used to to check if a process is running
+ *
+ * @param processName name of process to check
+ * @return true
if process was found
+ * @throws TimeoutException (Could not determine if the process is running)
+ */
+ public boolean isProcessRunning(final String processName) {
+
+ RootTools.log("Checks if process is running: " + processName);
+
+ InternalVariables.processRunning = false;
+
+ try {
+ CommandCapture command = new CommandCapture(0, false, "ps") {
+ @Override
+ public void output(int id, String line) {
+ if (line.contains(processName)) {
+ InternalVariables.processRunning = true;
+ }
+ }
+ };
+ RootTools.getShell(true).add(command);
+ commandWait(command);
+
+ } catch (Exception e) {
+ RootTools.log(e.getMessage());
+ }
+
+ return InternalVariables.processRunning;
+ }
+
+ /**
+ * This method can be used to kill a running process
+ *
+ * @param processName name of process to kill
+ * @return true
if process was found and killed successfully
+ */
+ public boolean killProcess(final String processName) {
+ RootTools.log("Killing process " + processName);
+
+ InternalVariables.pid_list = "";
+
+ //Assume that the process is running
+ InternalVariables.processRunning = true;
+
+ try {
+
+ CommandCapture command = new CommandCapture(0, false, "ps") {
+ @Override
+ public void output(int id, String line) {
+ if (line.contains(processName)) {
+ Matcher psMatcher = InternalVariables.psPattern.matcher(line);
+
+ try {
+ if (psMatcher.find()) {
+ String pid = psMatcher.group(1);
+
+ InternalVariables.pid_list += " " + pid;
+ InternalVariables.pid_list = InternalVariables.pid_list.trim();
+
+ RootTools.log("Found pid: " + pid);
+ } else {
+ RootTools.log("Matching in ps command failed!");
+ }
+ } catch (Exception e) {
+ RootTools.log("Error with regex!");
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+ RootTools.getShell(true).add(command);
+ commandWait(command);
+
+ // get all pids in one string, created in process method
+ String pids = InternalVariables.pid_list;
+
+ // kill processes
+ if (!pids.equals("")) {
+ try {
+ // example: kill -9 1234 1222 5343
+ command = new CommandCapture(0, false, "kill -9 " + pids);
+ RootTools.getShell(true).add(command);
+ commandWait(command);
+
+ return true;
+ } catch (Exception e) {
+ RootTools.log(e.getMessage());
+ }
+ } else {
+ //no pids match, must be dead
+ return true;
+ }
+ } catch (Exception e) {
+ RootTools.log(e.getMessage());
+ }
+
+ return false;
+ }
+
+ /**
+ * This will launch the Android market looking for BusyBox
+ *
+ * @param activity pass in your Activity
+ */
+ public void offerBusyBox(Activity activity) {
+ RootTools.log("Launching Market for BusyBox");
+ Intent i = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=stericson.busybox"));
+ activity.startActivity(i);
+ }
+
+ /**
+ * This will launch the Android market looking for BusyBox, but will return the intent fired and
+ * starts the activity with startActivityForResult
+ *
+ * @param activity pass in your Activity
+ * @param requestCode pass in the request code
+ * @return intent fired
+ */
+ public Intent offerBusyBox(Activity activity, int requestCode) {
+ RootTools.log("Launching Market for BusyBox");
+ Intent i = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=stericson.busybox"));
+ activity.startActivityForResult(i, requestCode);
+ return i;
+ }
+
+ /**
+ * This will launch the Android market looking for SuperUser
+ *
+ * @param activity pass in your Activity
+ */
+ public void offerSuperUser(Activity activity) {
+ RootTools.log("Launching Market for SuperUser");
+ Intent i = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=com.noshufou.android.su"));
+ activity.startActivity(i);
+ }
+
+ /**
+ * This will launch the Android market looking for SuperUser, but will return the intent fired
+ * and starts the activity with startActivityForResult
+ *
+ * @param activity pass in your Activity
+ * @param requestCode pass in the request code
+ * @return intent fired
+ */
+ public Intent offerSuperUser(Activity activity, int requestCode) {
+ RootTools.log("Launching Market for SuperUser");
+ Intent i = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=com.noshufou.android.su"));
+ activity.startActivityForResult(i, requestCode);
+ return i;
+ }
+
+ private void commandWait(Command cmd) throws Exception {
+
+ while (!cmd.isFinished()) {
+
+ RootTools.log(Constants.TAG, Shell.getOpenShell().getCommandQueuePositionString(cmd));
+
+ synchronized (cmd) {
+ try {
+ if (!cmd.isFinished()) {
+ cmd.wait(2000);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (!cmd.isExecuting() && !cmd.isFinished()) {
+ if (!Shell.isExecuting && !Shell.isReading) {
+ Log.e(Constants.TAG, "Waiting for a command to be executed in a shell that is not executing and not reading! \n\n Command: " + cmd.getCommand());
+ Exception e = new Exception();
+ e.setStackTrace(Thread.currentThread().getStackTrace());
+ e.printStackTrace();
+ } else if (Shell.isExecuting && !Shell.isReading) {
+ Log.e(Constants.TAG, "Waiting for a command to be executed in a shell that is executing but not reading! \n\n Command: " + cmd.getCommand());
+ Exception e = new Exception();
+ e.setStackTrace(Thread.currentThread().getStackTrace());
+ e.printStackTrace();
+ } else {
+ Log.e(Constants.TAG, "Waiting for a command to be executed in a shell that is not reading! \n\n Command: " + cmd.getCommand());
+ Exception e = new Exception();
+ e.setStackTrace(Thread.currentThread().getStackTrace());
+ e.printStackTrace();
+ }
+ }
+
+ }
+ }
+}
diff --git a/RootTools/src/main/java/com/lucemanb/RootTools/internal/Runner.java b/RootTools/src/main/java/com/lucemanb/RootTools/internal/Runner.java
new file mode 100644
index 0000000..27c0fcb
--- /dev/null
+++ b/RootTools/src/main/java/com/lucemanb/RootTools/internal/Runner.java
@@ -0,0 +1,81 @@
+/*
+ * This file is part of the RootTools Project: http://code.google.com/p/roottools/
+ *
+ * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
+ *
+ * This code is dual-licensed under the terms of the Apache License Version 2.0 and
+ * the terms of the General Public License (GPL) Version 2.
+ * You may use this code according to either of these licenses as is most appropriate
+ * for your project on a case-by-case basis.
+ *
+ * The terms of each license can be found in the root directory of this project's repository as well as at:
+ *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under these Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See each License for the specific language governing permissions and
+ * limitations under that License.
+ */
+
+package com.lucemanb.RootTools.internal;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.lucemanb.RootTools.RootTools;
+import com.lucemanb.RootTools.execution.Command;
+import com.lucemanb.RootTools.execution.CommandCapture;
+import com.lucemanb.RootTools.execution.Shell;
+
+import java.io.IOException;
+
+public class Runner extends Thread {
+
+ private static final String LOG_TAG = "RootTools::Runner";
+
+ Context context;
+ String binaryName;
+ String parameter;
+
+ public Runner(Context context, String binaryName, String parameter) {
+ this.context = context;
+ this.binaryName = binaryName;
+ this.parameter = parameter;
+ }
+
+ public void run() {
+ String privateFilesPath = null;
+ try {
+ privateFilesPath = context.getFilesDir().getCanonicalPath();
+ } catch (IOException e) {
+ if (RootTools.debugMode) {
+ Log.e(LOG_TAG, "Problem occured while trying to locate private files directory!");
+ }
+ e.printStackTrace();
+ }
+ if (privateFilesPath != null) {
+ try {
+ CommandCapture command = new CommandCapture(0, false, privateFilesPath + "/" + binaryName + " " + parameter);
+ Shell.startRootShell().add(command);
+ commandWait(command);
+
+ } catch (Exception e) {}
+ }
+ }
+
+ private void commandWait(Command cmd) {
+ synchronized (cmd) {
+ try {
+ if (!cmd.isFinished()) {
+ cmd.wait(2000);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
diff --git a/RootTools/src/main/res/values/strings.xml b/RootTools/src/main/res/values/strings.xml
new file mode 100644
index 0000000..3d89dd1
--- /dev/null
+++ b/RootTools/src/main/res/values/strings.xml
@@ -0,0 +1,31 @@
+
+ RootTools
+
+ Torch Companion
+ Fixed Torch
+ Settings
+ Set fixed torch widget to current brightness
+ Error!
+ OK
+ Cancel
+ Done
+ Turn on torch first!
+ Quit and uninstall
+ The sysfs file this app interfaces with does not exist. This app will never work on this phone.
+ The device is not rooted, please root your device and restart the application. Do not ask about instructions on how to do this, use a search engine to find out.
+ You seem to have denied root access to this app, go to your Superuser application and allow access, then restart this application.
+ Quit
+ WARNING: Using the flash at maximum brightness may cause damage to the flash. The developer of this application is not responsible for any damage caused to your device due to prolonged usage of the flash at a high brightness.
+ Click here to turn off torch
+ Torch turned on
+ About
+ Enable ads
+ Invert values
+ Set maximum value
+ About
+ Adjustable torch application allows you to use the torch light at different brightness levels.\n\n© 2013/14\nMohammad Abu-Garbeyyeh\nand\nC.C.P. Cre@ions
+ Donate
+ ResultActivity
+ Using the slider below, find the highest value that still makes sense and select OK to set the limit.\nCurrent value: %s
+
+
diff --git a/RootTools/src/test/java/com/lucemanb/RootTools/ExampleUnitTest.java b/RootTools/src/test/java/com/lucemanb/RootTools/ExampleUnitTest.java
new file mode 100644
index 0000000..49bfdcd
--- /dev/null
+++ b/RootTools/src/test/java/com/lucemanb/RootTools/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.lucemanb.RootTools;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..43c0708
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.1.3'
+
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..743d692
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,13 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7a3265e
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1e1d686
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Oct 27 11:51:57 EAT 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..bc960e1
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':RootTools'