+
+
+
+
+
+
+
+
diff --git a/RootTools/res/drawable-hdpi/ic_launcher.png b/RootTools/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/RootTools/res/drawable-hdpi/ic_launcher.png differ
diff --git a/RootTools/res/drawable-mdpi/ic_launcher.png b/RootTools/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
Binary files /dev/null and b/RootTools/res/drawable-mdpi/ic_launcher.png differ
diff --git a/RootTools/res/drawable-xhdpi/ic_launcher.png b/RootTools/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/RootTools/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/RootTools/res/drawable-xxhdpi/ic_launcher.png b/RootTools/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
Binary files /dev/null and b/RootTools/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/RootTools/res/values/strings.xml b/RootTools/res/values/strings.xml
new file mode 100644
index 0000000..0d1efdb
--- /dev/null
+++ b/RootTools/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ RootTools
+
diff --git a/RootTools/src/com/stericson/RootTools/Constants.java b/RootTools/src/com/stericson/RootTools/Constants.java
new file mode 100644
index 0000000..edf81b7
--- /dev/null
+++ b/RootTools/src/com/stericson/RootTools/Constants.java
@@ -0,0 +1,13 @@
+package com.stericson.RootTools;
+
+public class Constants {
+ public static final String TAG = "RootTools v3.4";
+ 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/com/stericson/RootTools/RootTools.java b/RootTools/src/com/stericson/RootTools/RootTools.java
new file mode 100644
index 0000000..32b81ce
--- /dev/null
+++ b/RootTools/src/com/stericson/RootTools/RootTools.java
@@ -0,0 +1,807 @@
+/*
+ * 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.stericson.RootTools;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.stericson.RootTools.containers.Mount;
+import com.stericson.RootTools.containers.Permissions;
+import com.stericson.RootTools.containers.Symlink;
+import com.stericson.RootTools.exceptions.RootDeniedException;
+import com.stericson.RootTools.execution.Command;
+import com.stericson.RootTools.execution.Shell;
+import com.stericson.RootTools.internal.Remounter;
+import com.stericson.RootTools.internal.RootToolsInternalMethods;
+import com.stericson.RootTools.internal.Runner;
+
+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 exists(file, false);
+ }
+
+ /**
+ * Use this to check whether or not a file OR directory exists on the filesystem.
+ *
+ * @param file String that represent the file OR the directory, including the full path to the
+ * file and its name.
+ *
+ * @param isDir boolean that represent whether or not we are looking for a directory
+ *
+ * @return a boolean that will indicate whether or not the file exists.
+ */
+ public static boolean exists(final String file, boolean isDir) {
+ return getInternals().exists(file, isDir);
+ }
+
+ /**
+ * 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 com.stericson.RootTools.exceptions.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 com.stericson.RootTools.exceptions.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 List A List of Strings representing the environment variable $PATH
+ */
+ public static List getPath() {
+ return Arrays.asList(System.getenv("PATH").split(":"));
+ }
+
+ /**
+ * 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 com.stericson.RootTools.exceptions.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 com.stericson.RootTools.exceptions.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 com.stericson.RootTools.exceptions.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/com/stericson/RootTools/containers/Mount.java b/RootTools/src/com/stericson/RootTools/containers/Mount.java
new file mode 100644
index 0000000..8314e78
--- /dev/null
+++ b/RootTools/src/com/stericson/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.stericson.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/com/stericson/RootTools/containers/Permissions.java b/RootTools/src/com/stericson/RootTools/containers/Permissions.java
new file mode 100644
index 0000000..ed36b80
--- /dev/null
+++ b/RootTools/src/com/stericson/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.stericson.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/com/stericson/RootTools/containers/RootClass.java b/RootTools/src/com/stericson/RootTools/containers/RootClass.java
new file mode 100644
index 0000000..342f4b4
--- /dev/null
+++ b/RootTools/src/com/stericson/RootTools/containers/RootClass.java
@@ -0,0 +1,295 @@
+package com.stericson.RootTools.containers;
+
+import java.io.*;
+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.stericson.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 AnnotationsFinder();
+ }
+ else {
+ new RootClass(args);
+ }
+ } catch (Exception e) {
+ displayError(e);
+ }
+ }
+}
diff --git a/RootTools/src/com/stericson/RootTools/containers/Symlink.java b/RootTools/src/com/stericson/RootTools/containers/Symlink.java
new file mode 100644
index 0000000..2446a48
--- /dev/null
+++ b/RootTools/src/com/stericson/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.stericson.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/com/stericson/RootTools/exceptions/RootDeniedException.java b/RootTools/src/com/stericson/RootTools/exceptions/RootDeniedException.java
new file mode 100644
index 0000000..0331b08
--- /dev/null
+++ b/RootTools/src/com/stericson/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.stericson.RootTools.exceptions;
+
+public class RootDeniedException extends Exception {
+
+ private static final long serialVersionUID = -8713947214162841310L;
+
+ public RootDeniedException(String error) {
+ super(error);
+ }
+}
diff --git a/RootTools/src/com/stericson/RootTools/execution/Command.java b/RootTools/src/com/stericson/RootTools/execution/Command.java
new file mode 100644
index 0000000..a3c0bb4
--- /dev/null
+++ b/RootTools/src/com/stericson/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.stericson.RootTools.execution;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import java.io.IOException;
+
+import com.stericson.RootTools.RootTools;
+
+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.stericson.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/com/stericson/RootTools/execution/CommandCapture.java b/RootTools/src/com/stericson/RootTools/execution/CommandCapture.java
new file mode 100644
index 0000000..03aa922
--- /dev/null
+++ b/RootTools/src/com/stericson/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.stericson.RootTools.execution;
+
+import com.stericson.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/com/stericson/RootTools/execution/JavaCommandCapture.java b/RootTools/src/com/stericson/RootTools/execution/JavaCommandCapture.java
new file mode 100644
index 0000000..8d9208f
--- /dev/null
+++ b/RootTools/src/com/stericson/RootTools/execution/JavaCommandCapture.java
@@ -0,0 +1,42 @@
+package com.stericson.RootTools.execution;
+
+import android.content.Context;
+import com.stericson.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/com/stericson/RootTools/execution/Shell.java b/RootTools/src/com/stericson/RootTools/execution/Shell.java
new file mode 100644
index 0000000..5358237
--- /dev/null
+++ b/RootTools/src/com/stericson/RootTools/execution/Shell.java
@@ -0,0 +1,626 @@
+/*
+ * 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.stericson.RootTools.execution;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+import android.content.Context;
+import com.stericson.RootTools.RootTools;
+import com.stericson.RootTools.exceptions.RootDeniedException;
+
+public class Shell {
+
+ //Statics -- visible to all
+ private static int shellTimeout = 25000;
+ 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 String error = "";
+
+ 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;
+
+ public boolean isExecuting = false;
+ public boolean isReading = false;
+
+ private int maxCommands = 5000;
+ 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);
+
+ this.proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
+ this.in = new BufferedReader(new InputStreamReader(this.proc.getInputStream(), "UTF-8"));
+ this.out = new OutputStreamWriter(this.proc.getOutputStream(), "UTF-8");
+
+ /**
+ * Thread responsible for carrying out the requested operations
+ */
+ Worker worker = new Worker(this);
+ 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(Shell.shellTimeout);
+
+ /**
+ * The operation could not be completed before the timeout occured.
+ */
+ if (worker.exit == -911) {
+
+ try {
+ this.proc.destroy();
+ } catch (Exception e) {}
+
+ closeQuietly(this.in);
+ closeQuietly(this.out);
+
+ throw new TimeoutException(this.error);
+ }
+ /**
+ * Root access denied?
+ */
+ else if (worker.exit == -42) {
+
+ try {
+ this.proc.destroy();
+ } catch (Exception e) {}
+
+ closeQuietly(this.in);
+ closeQuietly(this.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(this.input, "Shell Input");
+ si.setPriority(Thread.NORM_PRIORITY);
+ si.start();
+
+ Thread so = new Thread(this.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 (this.close)
+ throw new IllegalStateException(
+ "Unable to add commands to a closed shell");
+
+ while (this.isCleaning) {
+ //Don't add commands while cleaning
+ ;
+ }
+ this.commands.add(command);
+
+ this.notifyThreads();
+
+ return command;
+ }
+
+ public void useCWD(Context context) throws IOException, TimeoutException, RootDeniedException {
+ add(
+ new CommandCapture(
+ -1,
+ false,
+ "cd " + context.getApplicationInfo().dataDir));
+ }
+
+ private void cleanCommands() {
+ this.isCleaning = true;
+ int toClean = Math.abs(this.maxCommands - (this.maxCommands / 4));
+ RootTools.log("Cleaning up: " + toClean);
+
+ for (int i = 0; i < toClean; i++) {
+ this.commands.remove(0);
+ }
+
+ this.read = this.commands.size() - 1;
+ this.write = this.commands.size() - 1;
+ this.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 == Shell.rootShell)
+ Shell.rootShell = null;
+ else if (this == Shell.shell)
+ Shell.shell = null;
+ else if (this == Shell.customShell)
+ Shell.customShell = null;
+ synchronized (this.commands) {
+ /**
+ * instruct the two threads monitoring input and output
+ * of the shell to close.
+ */
+ this.close = true;
+ this.notifyThreads();
+ }
+ }
+
+ public static void closeCustomShell() throws IOException {
+ if (Shell.customShell == null)
+ return;
+ Shell.customShell.close();
+ }
+
+ public static void closeRootShell() throws IOException {
+ if (Shell.rootShell == null)
+ return;
+ Shell.rootShell.close();
+ }
+
+ public static void closeShell() throws IOException {
+ if (Shell.shell == null)
+ return;
+ Shell.shell.close();
+ }
+
+ public static void closeAll() throws IOException {
+ Shell.closeShell();
+ Shell.closeRootShell();
+ Shell.closeCustomShell();
+ }
+
+ public int getCommandQueuePosition(Command cmd) {
+ return this.commands.indexOf(cmd);
+ }
+
+ public String getCommandQueuePositionString(Command cmd) {
+ return "Command is in position " + getCommandQueuePosition(cmd) + " currently executing command at position " + this.write + " and the number of commands is " + commands.size();
+ }
+
+ public static Shell getOpenShell() {
+ if (Shell.customShell != null)
+ return Shell.customShell;
+ else if (Shell.rootShell != null)
+ return Shell.rootShell;
+ else
+ return Shell.shell;
+ }
+
+ public static boolean isShellOpen() {
+ return Shell.shell == null;
+ }
+
+ public static boolean isCustomShellOpen() {
+ return Shell.customShell == null;
+ }
+
+ public static boolean isRootShellOpen() {
+ return Shell.rootShell == null;
+ }
+
+ public static boolean isAnyShellOpen() {
+ return Shell.shell != null || Shell.rootShell != null || Shell.customShell != null;
+ }
+
+ /**
+ * 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 {
+ Shell.startRootShell().add(command);
+ }
+
+ public static void runCommand(Command command) throws IOException, TimeoutException {
+ Shell.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 (Shell.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 (Shell.rootShell == null) {
+ try {
+ Shell.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 Shell.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 (Shell.customShell == null) {
+ RootTools.log("Starting Custom Shell!");
+ Shell.customShell = new Shell(shellPath);
+ } else
+ RootTools.log("Using Existing Custom Shell!");
+
+ return Shell.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.shell == null) {
+ RootTools.log("Starting Shell!");
+ Shell.shell = new Shell("/system/bin/sh");
+ } else
+ RootTools.log("Using Existing Shell!");
+ return Shell.shell;
+ } catch (RootDeniedException e) {
+ //Root Denied should never be thrown.
+ throw new IOException();
+ }
+ }
+
+ protected static class Worker extends Thread {
+ public int exit = -911;
+
+ public Shell shell;
+
+ private Worker(Shell shell) {
+ this.shell = shell;
+ }
+
+ 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 {
+ shell.out.write("echo Started\n");
+ shell.out.flush();
+
+ while (true) {
+ String line = shell.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 = shell.proc.getClass();
+ Field field;
+ try {
+ field = processClass.getDeclaredField("pid");
+ } catch (NoSuchFieldException e) {
+ field = processClass.getDeclaredField("id");
+ }
+ field.setAccessible(true);
+ int pid = (Integer) field.get(shell.proc);
+ shell.out.write("(echo -17 > /proc/" + pid + "/oom_adj) &> /dev/null\n");
+ shell.out.write("(echo -17 > /proc/$$/oom_adj) &> /dev/null\n");
+ shell.out.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/RootTools/src/com/stericson/RootTools/internal/Installer.java b/RootTools/src/com/stericson/RootTools/internal/Installer.java
new file mode 100644
index 0000000..3cc12e8
--- /dev/null
+++ b/RootTools/src/com/stericson/RootTools/internal/Installer.java
@@ -0,0 +1,228 @@
+/*
+ * 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.stericson.RootTools.internal;
+
+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;
+
+import android.util.Log;
+import com.stericson.RootTools.RootTools;
+import com.stericson.RootTools.execution.Command;
+import com.stericson.RootTools.execution.CommandCapture;
+import com.stericson.RootTools.execution.Shell;
+
+import android.content.Context;
+
+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/com/stericson/RootTools/internal/Remounter.java b/RootTools/src/com/stericson/RootTools/internal/Remounter.java
new file mode 100644
index 0000000..d1c62b1
--- /dev/null
+++ b/RootTools/src/com/stericson/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.stericson.RootTools.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import com.stericson.RootTools.Constants;
+import com.stericson.RootTools.RootTools;
+import com.stericson.RootTools.containers.Mount;
+import com.stericson.RootTools.execution.Command;
+import com.stericson.RootTools.execution.CommandCapture;
+import com.stericson.RootTools.execution.Shell;
+
+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/com/stericson/RootTools/internal/RootToolsInternalMethods.java b/RootTools/src/com/stericson/RootTools/internal/RootToolsInternalMethods.java
new file mode 100644
index 0000000..d5f62a2
--- /dev/null
+++ b/RootTools/src/com/stericson/RootTools/internal/RootToolsInternalMethods.java
@@ -0,0 +1,1498 @@
+/*
+ * 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.stericson.RootTools.internal;
+
+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;
+
+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.stericson.RootTools.Constants;
+import com.stericson.RootTools.RootTools;
+import com.stericson.RootTools.containers.Mount;
+import com.stericson.RootTools.containers.Permissions;
+import com.stericson.RootTools.containers.Symlink;
+import com.stericson.RootTools.execution.Command;
+import com.stericson.RootTools.execution.CommandCapture;
+import com.stericson.RootTools.execution.Shell;
+
+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 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(Shell.startRootShell(), 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(Shell.startRootShell(), 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(Shell.startRootShell(), command);
+
+ } else {
+ command = new CommandCapture(0, false, "busybox cp -f " + source + " " + destination);
+ Shell.startRootShell().add(command);
+ commandWait(Shell.startRootShell(), 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(Shell.startRootShell(), command);
+
+ if (preserveFileAttributes) {
+ // set premissions of source to destination
+ command = new CommandCapture(0, false, "chmod " + filePermission + " " + destination);
+ Shell.startRootShell().add(command);
+ commandWait(Shell.startRootShell(), 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(Shell.startRootShell(), 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 rm command is available!");
+
+ CommandCapture command = new CommandCapture(0, false, "busybox rm -rf " + target);
+ Shell.startRootShell().add(command);
+ commandWait(Shell.startRootShell(), 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) {
+ return exists(file, false);
+ }
+
+ /**
+ * Use this to check whether or not a file OR directory exists on the filesystem.
+ *
+ * @param file String that represent the file OR the directory, including the full path to the
+ * file and its name.
+ *
+ * @param isDir boolean that represent whether or not we are looking for a directory
+ *
+ * @return a boolean that will indicate whether or not the file exists.
+ */
+ public boolean exists(final String file, boolean isDir) {
+ final List result = new ArrayList();
+
+ String cmdToExecute = "ls " + (isDir ? "-d " : " ");
+
+ CommandCapture command = new CommandCapture(0, false, cmdToExecute + file) {
+ @Override
+ public void output(int arg0, String arg1) {
+ RootTools.log(arg1);
+ result.add(arg1);
+ }
+ };
+
+ try {
+ //Try without root...
+ Shell.startShell().add(command);
+ commandWait(Shell.startShell(), 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(Shell.startRootShell(), 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(Shell.startRootShell(), 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(Shell.startRootShell(), 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(RootTools.getShell(false), 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 {
+ List 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);
+ }
+ }
+ }
+ };
+
+ //try without root first...
+ Shell.startShell().add(command);
+ commandWait(Shell.startShell(), command);
+
+ if(results.size() <= 0) {
+ //try with root...
+ Shell.startRootShell().add(command);
+ commandWait(Shell.startRootShell(), command);
+ }
+
+ return results;
+ }
+
+ /**
+ * @return BusyBox version if found, "" if not found.
+ */
+ public String getBusyBoxVersion(String path) {
+
+ if (!path.equals("") && !path.endsWith("/")) {
+ path += "/";
+ }
+
+ 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];
+ }
+ }
+ }
+ };
+
+ //try without root first
+ RootTools.log("Getting BusyBox Version without root");
+ Shell.startShell().add(command);
+ commandWait(Shell.startShell(), command);
+
+ if(InternalVariables.busyboxVersion.length() <= 0)
+ {
+ //try without root first
+ RootTools.log("Getting BusyBox Version with root");
+ Shell.startRootShell().add(command);
+ commandWait(Shell.startRootShell(), 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(Shell.startRootShell(), 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(Shell.startRootShell(), command);
+
+ return InternalVariables.accessGiven;
+
+ } 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(Shell.startRootShell(), 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(shell, 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();
+ } catch (Exception e) {}
+
+ try {
+ lnr.close();
+ } 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();
+ }
+ }
+
+ /**
+ * 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(Shell.startRootShell(), 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(Shell.startRootShell(), 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(Shell.startRootShell(), command);
+
+ command = new CommandCapture(0, false, "find " + path + " -type l -exec ls -l {} \\; > /data/local/symlinks.txt");
+ Shell.startRootShell().add(command);
+ commandWait(Shell.startRootShell(), 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(RootTools.getShell(true), 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(RootTools.getShell(true), 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(RootTools.getShell(true), 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(RootTools.getShell(true), 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(Shell shell, Command cmd) throws Exception {
+
+ while (!cmd.isFinished()) {
+
+ RootTools.log(Constants.TAG, shell.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/com/stericson/RootTools/internal/Runner.java b/RootTools/src/com/stericson/RootTools/internal/Runner.java
new file mode 100644
index 0000000..ff12343
--- /dev/null
+++ b/RootTools/src/com/stericson/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.stericson.RootTools.internal;
+
+import java.io.IOException;
+
+import com.stericson.RootTools.RootTools;
+import com.stericson.RootTools.execution.Command;
+import com.stericson.RootTools.execution.CommandCapture;
+import com.stericson.RootTools.execution.Shell;
+
+import android.content.Context;
+import android.util.Log;
+
+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/com/stericson/RootToolsTests/NativeJavaClass.java b/RootTools/src/com/stericson/RootToolsTests/NativeJavaClass.java
new file mode 100644
index 0000000..e09e86f
--- /dev/null
+++ b/RootTools/src/com/stericson/RootToolsTests/NativeJavaClass.java
@@ -0,0 +1,39 @@
+package com.stericson.RootToolsTests;
+
+import com.stericson.RootTools.containers.RootClass;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+@RootClass.Candidate
+public class NativeJavaClass {
+
+ public NativeJavaClass(RootClass.RootArgs args) {
+ System.out.println("NativeJavaClass says: oh hi there.");
+ String p = "/data/data/com.android.browser/cache";
+ File f = new File(p);
+ String[] fl = f.list();
+ if(fl != null) {
+ System.out.println("Look at all the stuff in your browser's cache:");
+ for(String af:fl) {
+ System.out.println("-" + af);
+ }
+ System.out.println("Leaving my mark for posterity...");
+ File f2 = new File(p + "/roottools_was_here");
+ try {
+ FileWriter filewriter = new FileWriter(f2);
+ BufferedWriter out = new BufferedWriter(filewriter);
+ out.write("This is just a file created using RootTool's Sanity check tools..\n");
+ out.close();
+ System.out.println("Done!");
+ } catch (IOException e) {
+ System.out.println("...and I failed miserably.");
+ e.printStackTrace();
+ }
+
+ }
+ }
+
+}
diff --git a/RootTools/src/com/stericson/RootToolsTests/SanityCheckRootTools.java b/RootTools/src/com/stericson/RootToolsTests/SanityCheckRootTools.java
new file mode 100644
index 0000000..d1dcbc4
--- /dev/null
+++ b/RootTools/src/com/stericson/RootToolsTests/SanityCheckRootTools.java
@@ -0,0 +1,393 @@
+/*
+ * 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.stericson.RootToolsTests;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.StrictMode;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.stericson.RootTools.RootTools;
+import com.stericson.RootTools.containers.Permissions;
+import com.stericson.RootTools.exceptions.RootDeniedException;
+import com.stericson.RootTools.execution.CommandCapture;
+import com.stericson.RootTools.execution.JavaCommandCapture;
+import com.stericson.RootTools.execution.Shell;
+
+public class SanityCheckRootTools extends Activity {
+ private ScrollView mScrollView;
+ private TextView mTextView;
+ private ProgressDialog mPDialog;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectDiskReads()
+ .detectDiskWrites()
+ .detectNetwork() // or .detectAll() for all detectable problems
+ .penaltyLog()
+ .build());
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
+ .detectLeakedSqlLiteObjects()
+ .detectLeakedClosableObjects()
+ .penaltyLog()
+ .penaltyDeath()
+ .build());
+
+ RootTools.debugMode = true;
+
+ mTextView = new TextView(this);
+ mTextView.setText("");
+ mScrollView = new ScrollView(this);
+ mScrollView.addView(mTextView);
+ setContentView(mScrollView);
+
+ // Great the user with our version number
+ String version = "?";
+ try {
+ PackageInfo packageInfo =
+ this.getPackageManager().getPackageInfo(this.getPackageName(), 0);
+ version = packageInfo.versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ print("SanityCheckRootTools v " + version + "\n\n");
+
+ if(RootTools.isRootAvailable()) {
+ print("Root found.\n");
+ }
+ else
+ {
+ print("Root not found");
+ }
+
+ try {
+ Shell.startRootShell();
+ } catch (IOException e2) {
+ // TODO Auto-generated catch block
+ e2.printStackTrace();
+ } catch (TimeoutException e) {
+ print("[ TIMEOUT EXCEPTION! ]\n");
+ e.printStackTrace();
+ } catch (RootDeniedException e) {
+ print("[ ROOT DENIED EXCEPTION! ]\n");
+ e.printStackTrace();
+ }
+
+ try {
+ if (!RootTools.isAccessGiven()) {
+ print("ERROR: No root access to this device.\n");
+ return;
+ }
+ } catch (Exception e) {
+ print("ERROR: could not determine root access to this device.\n");
+ return;
+ }
+
+ // Display infinite progress bar
+ mPDialog = new ProgressDialog(this);
+ mPDialog.setCancelable(false);
+ mPDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+
+ new SanityCheckThread(this, new TestHandler()).start();
+ }
+
+ protected void print(CharSequence text) {
+ mTextView.append(text);
+ mScrollView.post(new Runnable() {
+ public void run() {
+ mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
+ }
+ });
+ }
+
+ // Run our long-running tests in their separate thread so as to
+ // not interfere with proper rendering.
+ private class SanityCheckThread extends Thread {
+ private Handler mHandler;
+
+ public SanityCheckThread(Context context, Handler handler) {
+ mHandler = handler;
+ }
+
+ public void run() {
+ visualUpdate(TestHandler.ACTION_SHOW, null);
+
+ // First test: Install a binary file for future use
+ // if it wasn't already installed.
+ /*
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Installing binary if needed");
+ if(false == RootTools.installBinary(mContext, R.raw.nes, "nes_binary")) {
+ visualUpdate(TestHandler.ACTION_HIDE, "ERROR: Failed to install binary. Please see log file.");
+ return;
+ }
+ */
+
+ boolean result;
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getPath");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ getPath ]\n");
+
+ try {
+ List paths = RootTools.getPath();
+
+ for(String path : paths)
+ {
+ visualUpdate(TestHandler.ACTION_DISPLAY, path + " k\n\n");
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing A ton of commands");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Ton of Commands ]\n");
+
+ for (int i = 0; i < 100; i++) {
+ RootTools.exists("/system/xbin/busybox");
+ }
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Find Binary");
+ result = RootTools.isRootAvailable();
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Root ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing file exists");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Exists() ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.exists("/system/sbin/[") + " k\n\n");
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Is Access Given");
+ result = RootTools.isAccessGiven();
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking for Access to Root ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Remount");
+ result = RootTools.remount("/system", "rw");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Remounting System as RW ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing CheckUtil");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox is setup ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.checkUtil("busybox") + " k\n\n");
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getBusyBoxVersion");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox version ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getBusyBoxVersion("/system/bin/") + " k\n\n");
+
+ try {
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing fixUtils");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Utils ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.fixUtils(new String[]{"ls", "rm", "ln", "dd", "chmod", "mount"}) + " k\n\n");
+ } catch (Exception e2) {
+ // TODO Auto-generated catch block
+ e2.printStackTrace();
+ }
+
+ try {
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getSymlink");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking [[ for symlink ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getSymlink("/system/bin/[[") + " k\n\n");
+ } catch (Exception e2) {
+ // TODO Auto-generated catch block
+ e2.printStackTrace();
+ }
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getInode");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Inodes ]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getInode("/system/bin/busybox") + " k\n\n");
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing GetBusyBoxapplets");
+ try {
+
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Getting all available Busybox applets ]\n");
+ for (String applet : RootTools.getBusyBoxApplets("/data/data/stericson.busybox.donate/files/bb")) {
+ visualUpdate(TestHandler.ACTION_DISPLAY, applet + " k\n\n");
+ }
+
+ } catch (Exception e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getFilePermissionsSymlinks");
+ Permissions permissions = RootTools.getFilePermissionsSymlinks("/system/bin/busybox");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox permissions and symlink ]\n");
+
+ if (permissions != null) {
+ visualUpdate(TestHandler.ACTION_DISPLAY, "Symlink: " + permissions.getSymlink() + " k\n\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "Group Permissions: " + permissions.getGroupPermissions() + " k\n\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "Owner Permissions: " + permissions.getOtherPermissions() + " k\n\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "Permissions: " + permissions.getPermissions() + " k\n\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "Type: " + permissions.getType() + " k\n\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "User Permissions: " + permissions.getUserPermissions() + " k\n\n");
+ } else {
+ visualUpdate(TestHandler.ACTION_DISPLAY, "Permissions == null k\n\n");
+ }
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "JAVA");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Running some Java code ]\n");
+
+ Shell shell;
+ try {
+ shell = RootTools.getShell(true);
+ JavaCommandCapture cmd = new JavaCommandCapture(
+ 43,
+ false,
+ SanityCheckRootTools.this,
+ "com.stericson.RootToolsTests.NativeJavaClass") {
+
+ @Override
+ public void commandOutput(int id, String line) {
+ super.commandOutput(id, line);
+ visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
+ }
+ };
+ shell.add(cmd);
+
+ } catch (Exception e) {
+ // Oops. Say, did you run RootClass and move the resulting anbuild.dex " file to res/raw?
+ // If you don't you will not be able to check root mode Java.
+ e.printStackTrace();
+ }
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing df");
+ long spaceValue = RootTools.getSpace("/data");
+ visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking /data partition size]\n");
+ visualUpdate(TestHandler.ACTION_DISPLAY, spaceValue + "k\n\n");
+
+ try {
+ shell = RootTools.getShell(true);
+
+ CommandCapture cmd = new CommandCapture(42, false, "find /") {
+
+ boolean _catch = false;
+
+ @Override
+ public void commandOutput(int id, String line) {
+ super.commandOutput(id, line);
+
+ if (_catch) {
+ RootTools.log("CAUGHT!!!");
+ }
+ }
+
+ @Override
+ public void commandTerminated(int id, String reason) {
+ synchronized (SanityCheckRootTools.this) {
+
+ _catch = true;
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "All tests complete.");
+ visualUpdate(TestHandler.ACTION_HIDE, null);
+
+ try {
+ RootTools.closeAllShells();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ }
+
+ @Override
+ public void commandCompleted(int id, int exitCode) {
+ synchronized (SanityCheckRootTools.this) {
+ _catch = true;
+
+ visualUpdate(TestHandler.ACTION_PDISPLAY, "All tests complete.");
+ visualUpdate(TestHandler.ACTION_HIDE, null);
+
+ try {
+ RootTools.closeAllShells();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ }
+ };
+
+ shell.add(cmd);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ private void visualUpdate(int action, String text) {
+ Message msg = mHandler.obtainMessage();
+ Bundle bundle = new Bundle();
+ bundle.putInt(TestHandler.ACTION, action);
+ bundle.putString(TestHandler.TEXT, text);
+ msg.setData(bundle);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private class TestHandler extends Handler {
+ static final public String ACTION = "action";
+ static final public int ACTION_SHOW = 0x01;
+ static final public int ACTION_HIDE = 0x02;
+ static final public int ACTION_DISPLAY = 0x03;
+ static final public int ACTION_PDISPLAY = 0x04;
+ static final public String TEXT = "text";
+
+ public void handleMessage(Message msg) {
+ int action = msg.getData().getInt(ACTION);
+ String text = msg.getData().getString(TEXT);
+
+ switch (action) {
+ case ACTION_SHOW:
+ mPDialog.show();
+ mPDialog.setMessage("Running Root Library Tests...");
+ break;
+ case ACTION_HIDE:
+ if (null != text)
+ print(text);
+ mPDialog.hide();
+ break;
+ case ACTION_DISPLAY:
+ print(text);
+ break;
+ case ACTION_PDISPLAY:
+ mPDialog.setMessage(text);
+ break;
+ }
+ }
+ }
+}