/*
* 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 android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeoutException;
public 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.
*/
// --------------------
// # Public Variables #
// --------------------
public static boolean debugMode = false;
public static List<String> lastFoundBinaryPaths = new ArrayList<String>();
public static int lastExitCode;
public static String utilPath;
/**
* You can use this to force sendshell to use a shell other than the deafult.
*/
public static String customShell = "";
/**
* Change this to a lower/higher setting to speed up/slow down shell commands if things are
* taking too long or you are having constant crashes or timeout exceptions.
*/
public static int shellDelay = 0;
/**
* Many Functions here use root by default, but there may be times that you do not want them to
* use root. This can be useful when running a lot of commands at once. By default, if all of
* these functions are requesting root access then superuser will notify the user everytime that
* root is requested...this can lead to a flood of toast messages from superuser notifying the
* user that root access is being requested.
*
* Setting this to false will cause sendShell to not use root by default. So any commands sent
* to the shell will not have root access unless specifically directed to obtain root access by
* you. Some commands will not work properly without root access, so use this with care.
*/
public static boolean useRoot = true;
// ---------------------------
// # 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 String
* 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 InternalMethods.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 <code>boolean</code> 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 InternalMethods.copyFile(source, destination, remountAsRw, preserveFileAttributes);
}
/**
* 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 InternalMethods.exists(file);
}
/**
* This will try and fix a given binary. (This is for Busybox applets or Toolbox applets) By
* "fix", I mean it will try and symlink the binary from either toolbox or Busybox and fix the
* permissions if the permissions are not correct.
*
* @param String
* Name of the utility to fix.
* @param String
* 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) {
InternalMethods.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 String
* Name of the utility to check.
*
* @throws Exception
* if the operation cannot be completed.
*
* @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.
*/
public static boolean fixUtils(String[] utils) throws Exception {
return InternalMethods.fixUtils(utils);
}
/**
*
* @param binaryName
* String that represent the binary to find.
*
* @return <code>true</code> 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 InternalMethods.findBinary(binaryName);
}
/**
* @param binaryName
* 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 InternalMethods.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.
* <p/>
*
* @return <code>List<String></code> a List of strings representing the applets available from
* Busybox.
*
* @return <code>null</code> If we cannot return the list of applets.
*
*/
public static List<String> getBusyBoxApplets() throws Exception {
return RootTools.getBusyBoxApplets("");
}
/**
* This will return an List of Strings. Each string represents an applet available from BusyBox.
* <p/>
*
* @param path
* Path to the busybox binary that you want the list of applets from.
*
* @return <code>List<String></code> a List of strings representing the applets available from
* Busybox.
*
* @return <code>null</code> If we cannot return the list of applets.
*
*/
public static List<String> getBusyBoxApplets(String path) throws Exception {
return InternalMethods.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.
*
* @param shellPath
* a <code>String</code> to Indicate the path to the shell that you want to open.
*
* @throws IOException
* @throws TimeoutException
*
*/
public static Shell getCustomShell(String shellPath) throws IOException, TimeoutException
{
return Shell.startCustomShell(shellPath);
}
/**
*
* @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 InternalMethods.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 String 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 InternalMethods.getInode(file);
}
/**
* This will return an ArrayList of the class Mount. The class mount contains the following
* property's: device mountPoint type flags
* <p/>
* These will provide you with any information you need to work with the mount points.
*
* @return <code>ArrayList<Mount></code> an ArrayList of the class Mount.
* @throws Exception
* if we cannot return the mount points.
*/
public static ArrayList<Mount> getMounts() throws Exception {
return InternalMethods.getMounts();
}
/**
* This will tell you how the specified mount is mounted. rw, ro, etc...
* <p/>
* @param The mount you want to check
*
* @return <code>String</code> 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 InternalMethods.getMountedAs(path);
}
/**
* This will return the environment variable $PATH
*
* @return <code>Set<String></code> A Set of Strings representing the environment variable $PATH
* @throws Exception
* if we cannot return the $PATH variable
*/
public static Set<String> getPath() throws Exception {
return InternalMethods.getPath();
}
/**
* This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
* and for closing the shell when you are done using it.
*
* @param root
* a <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
*
* @throws IOException
* @throws TimeoutException
*
*/
public static Shell getShell(boolean root) throws IOException, TimeoutException
{
if (root)
return Shell.startRootShell();
else
return Shell.startShell();
}
/**
* 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 InternalMethods.getSpace(path);
}
/**
* This will return a String that represent the symlink for a specified file.
* <p/>
*
* @param The
* path to the file to get the Symlink for. (must have absolute path)
*
* @return <code>String</code> 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 InternalMethods.getSymlink(file);
}
/**
* This will return an ArrayList of the class Symlink. The class Symlink contains the following
* property's: path SymplinkPath
* <p/>
* These will provide you with any Symlinks in the given path.
*
* @param The
* path to search for Symlinks.
*
* @return <code>ArrayList<Symlink></code> an ArrayList of the class Symlink.
* @throws Exception
* if we cannot return the Symlinks.
*/
public static ArrayList<Symlink> getSymlinks(String path) throws Exception {
return InternalMethods.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 InternalMethods.getWorkingToolbox();
}
/**
* Checks if there is enough Space on SDCard
*
* @param updateSize
* size to Check (long)
* @return <code>true</code> if the Update will fit on SDCard, <code>false</code> if not enough
* space on SDCard. Will also return <code>false</code>, if the SDCard is not mounted as
* read/write
*/
public static boolean hasEnoughSpaceOnSdCard(long updateSize) {
return InternalMethods.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 InternalMethods.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 <code>Context</code>
* @param sourceId
* resource id; typically <code>R.raw.id</code>
* @param destName
* destination file name; appended to /data/data/app.package/files/
* @param mode
* chmod value for this file
* @return a <code>boolean</code> 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 InternalMethods.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 <code>Context</code>
* @param sourceId
* resource id; typically <code>R.raw.id</code>
* @param binaryName
* destination file name; appended to /data/data/app.package/files/
* @return a <code>boolean</code> 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 will let you know if an applet is available from BusyBox
* <p/>
*
* @param <code>String</code> The applet to check for.
* @param path
* Path to the busybox binary that you want to check. (do not include binary name)
*
* @return <code>true</code> if applet is available, false otherwise.
*/
public static boolean isAppletAvailable(String Applet, String path) {
return InternalMethods.isAppletAvailable(Applet, path);
}
/**
* This will let you know if an applet is available from BusyBox
* <p/>
*
* @param <code>String</code> The applet to check for.
*
* @return <code>true</code> if applet is available, false otherwise.
*/
public static boolean isAppletAvailable(String Applet) {
return RootTools.isAppletAvailable(Applet, "");
}
/**
* @return <code>true</code> 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 InternalMethods.isAccessGiven();
}
/**
* @return <code>true</code> if BusyBox was found.
*/
public static boolean isBusyboxAvailable() {
return findBinary("busybox");
}
public static boolean isNativeToolsReady(int nativeToolsId, Context context) {
return InternalMethods.isNativeToolsReady(nativeToolsId, context);
}
/**
* This method can be used to to check if a process is running
*
* @param processName
* name of process to check
* @return <code>true</code> 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 InternalMethods.isProcessRunning(processName);
}
/**
* @return <code>true</code> 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 <code>true</code> if process was found and killed successfully
*/
public static boolean killProcess(final String processName) {
//TODO convert to new shell
return InternalMethods.killProcess(processName);
}
/**
* This will launch the Android market looking for BusyBox
*
* @param activity
* pass in your Activity
*/
public static void offerBusyBox(Activity activity) {
InternalMethods.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 InternalMethods.offerBusyBox(activity, requestCode);
}
/**
* This will launch the Android market looking for SuperUser
*
* @param activity
* pass in your Activity
*/
public static void offerSuperUser(Activity activity) {
InternalMethods.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 InternalMethods.offerSuperUser(activity, requestCode);
}
/**
* This will take a path, which can contain the file name as well, and attempt to remount the
* underlying partition.
* <p/>
* 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 <code>boolean</code> 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 <code>Context</code>
* @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);
}
/**
* Sends several shell command as su (attempts to)
*
* @param commands
* array of commands to send to the shell
* @param sleepTime
* time to sleep between each command, delay.
* @param result
* injected result object that implements the Result class
* @param timeout
* How long to wait before throwing TimeoutException, sometimes when running root
* commands on certain devices or roms ANR's may occur because a process never
* returns or readline never returns. This allows you to protect your application
* from throwing an ANR.
*
* if you pass -1, then the default timeout is 5 minutes.
*
* @return a <code>LinkedList</code> containing each line that was returned by the shell after
* executing or while trying to execute the given commands. You must iterate over this
* list, it does not allow random access, so no specifying an index of an item you want,
* not like you're going to know that anyways.
* @throws InterruptedException
* @throws IOException
* @throws TimeoutException
*/
public static List<String> sendShell(String[] commands, int sleepTime, Result result,
int timeout) throws IOException, RootToolsException, TimeoutException {
return sendShell(commands, sleepTime, result, useRoot, timeout);
}
/**
* Sends several shell command as su (attempts to) if useRoot is true; as the current user
* (app_xxx) otherwise.
*
* @param commands
* array of commands to send to the shell
* @param sleepTime
* time to sleep between each command, delay.
* @param result
* injected result object that implements the Result class
* @param useRoot
* whether to use root or not when issuing these commands.
* @param timeout
* How long to wait before throwing TimeoutException, sometimes when running root
* commands on certain devices or roms ANR's may occur because a process never
* returns or readline never returns. This allows you to protect your application
* from throwing an ANR.
*
* if you pass -1, then the default timeout is 5 minutes.
*
* @return a <code>LinkedList</code> containing each line that was returned by the shell after
* executing or while trying to execute the given commands. You must iterate over this
* list, it does not allow random access, so no specifying an index of an item you want,
* not like you're going to know that anyways.
* @throws InterruptedException
* @throws IOException
* @throws TimeoutException
*/
public static List<String> sendShell(String[] commands, int sleepTime, Result result,
boolean useRoot, int timeout) throws IOException, RootToolsException, TimeoutException {
return new Executer().sendShell(commands, sleepTime, result, useRoot, timeout);
}
/**
* Sends several shell command as su, unless useRoot is set to false
*
* @param commands
* array of commands to send to the shell
* @param sleepTime
* time to sleep between each command, delay.
* @param timeout
* How long to wait before throwing TimeoutException, sometimes when running root
* commands on certain devices or roms ANR's may occur because a process never
* returns or readline never returns. This allows you to protect your application
* from throwing an ANR.
*
* if you pass -1, then the default timeout is 5 minutes.
*
* @return a LinkedList containing each line that was returned by the shell after executing or
* while trying to execute the given commands. You must iterate over this list, it does
* not allow random access, so no specifying an index of an item you want, not like
* you're going to know that anyways.
*
* @throws InterruptedException
* @throws IOException
* @throws TimeoutException
*/
public static List<String> sendShell(String[] commands, int sleepTime, int timeout)
throws IOException, RootToolsException, TimeoutException {
return sendShell(commands, sleepTime, null, timeout);
}
/**
* Sends one shell command as su, unless useRoot is set to false
*
* @param command
* command to send to the shell
* @param result
* injected result object that implements the Result class
* @param timeout
* How long to wait before throwing TimeoutException, sometimes when running root
* commands on certain devices or roms ANR's may occur because a process never
* returns or readline never returns. This allows you to protect your application
* from throwing an ANR.
*
* if you pass -1, then the default timeout is 5 minutes.
*
* @return a <code>LinkedList</code> containing each line that was returned by the shell after
* executing or while trying to execute the given commands. You must iterate over this
* list, it does not allow random access, so no specifying an index of an item you want,
* not like you're going to know that anyways.
*
* @throws InterruptedException
* @throws IOException
* @throws RootToolsException
* @throws TimeoutException
*/
public static List<String> sendShell(String command, Result result, int timeout)
throws IOException, RootToolsException, TimeoutException {
return sendShell(new String[] { command }, 0, result, timeout);
}
/**
* Sends one shell command as su, unless useRoot is set to false
*
* @param command
* command to send to the shell
* @param timeout
* How long to wait before throwing TimeoutException, sometimes when running root
* commands on certain devices or roms ANR's may occur because a process never
* returns or readline never returns. This allows you to protect your application
* from throwing an ANR.
*
* if you pass -1, then the default timeout is 5 minutes.
*
* @return a LinkedList containing each line that was returned by the shell after executing or
* while trying to execute the given commands. You must iterate over this list, it does
* not allow random access, so no specifying an index of an item you want, not like
* you're going to know that anyways.
* @throws InterruptedException
* @throws IOException
* @throws TimeoutException
*/
public static List<String> sendShell(String command, int timeout) throws IOException,
RootToolsException, TimeoutException {
return sendShell(command, null, timeout);
}
public static abstract class Result implements IResult {
private Process process = null;
private Serializable data = null;
private int error = 0;
public abstract void process(String line) throws Exception;
public abstract void processError(String line) throws Exception;
public abstract void onFailure(Exception ex);
public abstract void onComplete(int diag);
public Result setProcess(Process process) {
this.process = process;
return this;
}
public Process getProcess() {
return process;
}
public Result setData(Serializable data) {
this.data = data;
return this;
}
public Serializable getData() {
return data;
}
public Result setError(int error) {
this.error = error;
return this;
}
public int getError() {
return error;
}
}
/**
* 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.
* <p/>
* 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 exception
* The exception that was thrown (Needed for errors)
*/
public static void log(String msg) {
log(null, msg, 3, null);
}
public static void log(String TAG, String msg) {
log(TAG, msg, 3, null);
}
public static void log(String msg, int type, Exception e) {
log(null, msg, type, e);
}
public static void log(String TAG, String msg, int type, Exception e) {
if (msg != null && !msg.equals("")) {
if (debugMode) {
if (TAG == null) {
TAG = InternalVariables.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;
}
}
}
}
}