/*
* 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.
* <p/>
* All methods and Variables that the developer may need to have access to should be here.
* <p/>
* 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<String> lastFoundBinaryPaths = new ArrayList<String>();
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 <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 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 <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 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.
* <p/>
*
* @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>null</code> If we cannot return the list of applets.
*/
public static List<String> 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 <code>String</code> to Indicate the path to the shell that you want to open.
* @param timeout an <code>int</code> 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 <code>String</code> 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
* <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 getInternals().getMounts();
}
/**
* This will tell you how the specified mount is mounted. rw, ro, etc...
* <p/>
*
* @param path 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 getInternals().getMountedAs(path);
}
/**
* This will return the environment variable PATH
*
* @return <code>List<String></code> A List of Strings representing the environment variable $PATH
*/
public static List<String> 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.
*
* @throws TimeoutException
* @throws com.stericson.RootTools.exceptions.RootDeniedException
* @param root a <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
* @param timeout an <code>int</code> to Indicate the length of time to wait before giving up on opening a shell.
* @param shellContext the context to execute the shell with
* @param retry a <code>int</code> to indicate how many times the ROOT shell should try to open with root priviliges...
* @throws IOException
*/
public static Shell getShell(boolean root, int timeout, Shell.ShellContext shellContext, int retry) throws IOException, TimeoutException, RootDeniedException {
if (root)
return Shell.startRootShell(timeout, shellContext, retry);
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 <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
* @param timeout an <code>int</code> to Indicate the length of time to wait before giving up on opening a shell.
* @param shellContext the context to execute the shell with
* @throws IOException
*/
public static Shell getShell(boolean root, int timeout, Shell.ShellContext shellContext) throws IOException, TimeoutException, RootDeniedException {
return getShell(root, timeout, shellContext, 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 <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
* @param shellContext the context to execute the shell with
* @throws IOException
*/
public static Shell getShell(boolean root, Shell.ShellContext shellContext) throws IOException, TimeoutException, RootDeniedException {
return getShell(root, 0, Shell.defaultContext, 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 <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
* @param timeout an <code>int</code> 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, Shell.defaultContext, 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 <code>boolean</code> 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, 0);
}
/**
* 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.
* <p/>
*
* @param file 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 getInternals().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 path 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 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 <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 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 <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 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 <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 method checks whether a binary is installed.
*
* @param context the current activity's <code>Context</code>
* @param binaryName binary file name; appended to /data/data/app.package/files/
* @return a <code>boolean</code> 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
* <p/>
*
* @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 <code>true</code> 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
* <p/>
*
* @param applet 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 getInternals().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 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 <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 getInternals().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 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.
* <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.
* <p/>
* 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.
* <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 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.
* <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.
*/
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.
* <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 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.
* <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 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;
}
}
}
}
}