package com.aero.control.helpers; import android.os.SystemClock; import android.util.Log; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by Alexander Christ on 18.09.13. * Summary of various shell-Methods, for easy method calling */ public final class shellHelper { // Buffer length; private static final int BUFF_LEN = 8192; private static final byte[] buffer = new byte[BUFF_LEN]; private static final String LOG_TAG = shellHelper.class.getName(); private ShellWorkqueue shWork = new ShellWorkqueue(); private static shellHelper mShellHelper; private List<String> mCommands; private Process mProcess = null; private DataOutputStream mShellOutput = null; private BufferedReader mOutput = null; private boolean mShellLoaded = false; private final static String NO_DATA_FOUND = "Unavailable"; private shellHelper() { Runnable run = new Runnable() { @Override public void run() { openShell(); } }; Thread worker = new Thread(run); worker.start(); } /** * Returns a interactive shell. If no shell objects existed previously, * a new one is created. * @return shellHelper */ public static synchronized shellHelper instance() { if (mShellHelper == null) { mShellHelper = new shellHelper(); } return mShellHelper; } /** * Forcefully creates a new shell, this shouldn't be used. * @return shellHelper */ public static synchronized shellHelper forceInstance() { mShellHelper = new shellHelper(); return mShellHelper; } /** * Adds commands to our queue for our shell. * @param commands String[] */ private synchronized void addCommands(String[] commands) { for (String cmd : commands) { if (cmd != null) this.mCommands.add(cmd); } } /** * Adds a single command to our queue for our shell. * @param cmd String */ public synchronized void addCommand(String cmd) { this.mCommands.add(cmd); } /** * If necessary, initiates the relevant parts for the shell to work. * This is only used internally. */ private synchronized void openShell() { if (mCommands == null) { mCommands = new ArrayList<String>(); } try { if (mProcess == null) { mProcess = Runtime.getRuntime().exec("su"); } if (mShellOutput == null) { mShellOutput = new DataOutputStream(mProcess.getOutputStream()); } if (mOutput == null) { mOutput = new BufferedReader(new InputStreamReader(mProcess.getInputStream())); } mShellLoaded = true; } catch (IOException e) { Log.e(LOG_TAG, "We were not able to create a shell!", e); mShellLoaded = false; } } /** * Runs our commands without checking the output inside the shell. * This method is only used internally. */ private synchronized void runCommands() { openShell(); if (mShellLoaded) { List<String> commands = Collections.synchronizedList(mCommands); try { for (String cmd : commands) { mShellOutput.write((cmd + "\n").getBytes("UTF-8")); mShellOutput.flush(); } try { mShellOutput.flush(); } catch (IOException e) { } } catch (IOException e) { Log.e(LOG_TAG, "Something interrupted our operations...", e); } } else { // The shell couldn't be loaded correctly, we are not executing anything and clear our queue } mCommands.clear(); } /** * Returns the output of *all* previously added commands. If one wants * to get the output of the shell, one should call getRootResult() directly * after addCommand() or addCommands(). * @return String */ private String getRootResult() { List<String> commands = Collections.synchronizedList(mCommands); char[] buf = new char[BUFF_LEN]; int read; StringBuilder response = new StringBuilder(); if (mShellLoaded) { try { for (String cmd : commands) { mShellOutput.write((cmd + "\n").getBytes("UTF-8")); while (true) { read = mOutput.read(buf); if (read == -1) { return null; } response.append(buf, 0, read); if (read < BUFF_LEN) { //we have read everything break; } } mShellOutput.flush(); } try { mShellOutput.flush(); } catch (IOException e) { } } catch (IOException e) { Log.e(LOG_TAG, "Something interrupted our operations...", e); return null; } finally { mCommands.clear(); } } return response.toString(); } /* * Allows to simply query up commands to execute by the root process */ private class ShellWorkqueue { private ArrayList<String> mWorkItems; private void addToWork(String work) { if (mWorkItems == null) initWork(); mWorkItems.add(work); } private String[] execWork() { return mWorkItems.toArray(new String[0]); } private void initWork() { mWorkItems = new ArrayList<String>(); } private void flushWork() { // Be super save here and check for null if (mWorkItems != null) { mWorkItems.clear(); mWorkItems = null; } } } /** * Adds a work item to the current workqueue * * @param work => work item (command for shell) * * @return nothing */ public void queueWork(String work) { shWork.addToWork(work); } /** * Executes the current workqueue * * @return nothing */ public void execWork() { shWork.addToWork("echo "); setRootInfo(shWork.execWork()); } /** * Flushes the workqueue with its items * * @return nothing */ public void flushWork() { shWork.flushWork(); } /** * Gets the current Kernel Version + some useful information * * @return String */ public final String getKernel() { // Taken from Androids/CM Gingerbread Branch: String procVersionStr; try { BufferedReader reader = new BufferedReader(new FileReader("/proc/version"), BUFF_LEN); try { procVersionStr = reader.readLine(); } finally { reader.close(); } final String PROC_VERSION_REGEX = "\\w+\\s+" + /* ignore: Linux */ "\\w+\\s+" + /* ignore: version */ "([^\\s]+)\\s+" + /* group 1: 2.6.22-omap1 */ "\\(([^\\s@]+(?:@[^\\s.]+)?)[^)]*\\)\\s+" + /* group 2: (xxxxxx@xxxxx.constant) */ "\\((?:[^(]*\\([^)]*\\))?[^)]*\\)\\s+" + /* ignore: (gcc ..) */ "([^\\s]+)\\s+" + /* group 3: #26 */ "(?:PREEMPT\\s+)?" + /* ignore: PREEMPT (optional) */ "(.+)"; /* group 4: date */ Pattern p = Pattern.compile(PROC_VERSION_REGEX); Matcher m = p.matcher(procVersionStr); if (!m.matches()) { Log.e(LOG_TAG, "Regex did not match on /proc/version: " + procVersionStr); return NO_DATA_FOUND; } else if (m.groupCount() < 4) { Log.e(LOG_TAG, "Regex match on /proc/version only returned " + m.groupCount() + " groups"); return NO_DATA_FOUND; } else { return (new StringBuilder(m.group(1)).append("\n").append( m.group(2)).append(" ").append(m.group(3)).append("\n") .append(m.group(4))).toString(); } } catch (IOException e) { Log.e(LOG_TAG, "IO Exception when getting kernel version for Device Info screen", e); return NO_DATA_FOUND; } } /** * Gets information from the filesystem with a given path * * @param s => path (with filename) * * @return String */ public final String getInfo(String s) { String info = NO_DATA_FOUND; if (s == null || !(new File(s).exists())) return info; try { final BufferedReader reader = new BufferedReader(new FileReader(s), BUFF_LEN); try { info = reader.readLine(); } finally { reader.close(); } if (info == null) info = NO_DATA_FOUND; return info; } catch (IOException e) { String tmp = null; // Make sure that the shell is open; openShell(); addCommand("ls -l " + s); tmp = getRootResult(); // At least try to read it via root, but check for permissions; if (tmp != null && tmp.length() > 10 ) { if (!(tmp.substring(0, 10).equals("--w-------"))) { //info = getLegacyRootInfo("cat", s); addCommand("cat " + s); info = getRootResult(); } } if (info.equals(NO_DATA_FOUND)) Log.e(LOG_TAG, "IO Exception when trying to get information.", e); return info; } } /** * Reads a file from a given path. It has not sanity checks and uses FileInputStream * to read the file. * @param path String, the filepath to read * @return String, the output from the file */ public final String getFastInfo(final String path) { String tmp; try { final FileInputStream fis = new FileInputStream(path); final BufferedReader br = new BufferedReader(new InputStreamReader(fis)); tmp = br.readLine(); } catch (IOException e) { Log.e(LOG_TAG, "IO Exception when trying to get information. Fallback to getInfo()", e); tmp = getInfo(path); } return tmp; } /** * Gets information from the filesystem with a given path * * @param s => path (with filename) * @param deepsleep => Should current deepsleep value be added? * * @return String[] */ public final String[] getInfo(String s, boolean deepsleep) { String info; ArrayList<String> al = new ArrayList<String>(); if (deepsleep) { long sleepTime = (SystemClock.elapsedRealtime() - SystemClock.uptimeMillis()) / 10; al.add(Long.toString(sleepTime)); } try { BufferedReader reader = new BufferedReader(new FileReader(s), BUFF_LEN); try { info = reader.readLine(); while (info != null) { al.add(info); info = reader.readLine(); } } finally { reader.close(); } return al.toArray(new String[0]); } catch (IOException e) { Log.e(LOG_TAG, "IO Exception when trying to get information.", e); return null; } } /** * Gets all files in a given dictionary * * @param s => path to read the files * @param flag => for files or directory * * @return String[] */ public final String[] getDirInfo(String s, boolean flag) { String[] result; // Return null, if the file doesn't exist if (!(new File(s).exists())) return null; // Handle case if files are needed; if (flag) { List<String> results = new ArrayList<String>(); File[] files = new File(s).listFiles(); for (File file : files) { if (file.isFile()) { results.add(file.getName()); } } result = new String[results.size()]; for (int i = 0; i < results.size(); i++) { result[i] = results.get(i); } Arrays.sort(result); return result; } else { // Handle case if directory is needed; File file = new File (s); result = file.list(new FilenameFilter() { @Override public boolean accept(File file, String s) { return new File(file, s).isDirectory(); } }); return result; } } /** * Splits up the information from a string in more usable values * and returns this array. * * @param s => string used (from a given path) * @param flag => set to 1 to convert it with @toMHZ * @param flag_io => set to 1 to read the io_schedulers * @return String[] */ private String[] buildArray(String s, int flag, int flag_io) { String[] completeString = new String[0]; String[] output; // Make sure the last value is not a line feed; if ((int)s.charAt(s.length() - 1) == 10) { s = s.replace(Character.toString((char)10), ""); } if (flag_io == 1) completeString = s.replace("[", "").replace("]", "").split(" "); else if (flag_io == 0) completeString = s.split(" "); output = new String[completeString.length]; output[0] = NO_DATA_FOUND; for (int i = 0; i < output.length; i++) { if (flag == 1) output[i] = toMHz(completeString[i]); else output[i] = completeString[i]; } return output; } /** * This Method returns an Array, useful for the frequency list of the kernel * * @param s => string used (from a given path) * @param flag => set to 1 to convert it with @toMHZ * @param flag_io => set to 1 to read the io_schedulers * * @return String[] */ public final String[] getInfoArray(String s, int flag, int flag_io) { String[] output = new String[] { NO_DATA_FOUND }; String tmp; try { // Try to read the given Path, if not available -> throw exception BufferedReader reader = new BufferedReader(new FileReader(s), BUFF_LEN); try { output = buildArray(reader.readLine(), flag, flag_io); } finally { reader.close(); } return output; } catch (IOException e) { // Make sure that the shell is open; openShell(); String result = getRootInfo("ls -l", s); // At least try to read it via root, but check for permissions; if (result != null && result.length() > 10) { if (!result.substring(0, 10).equals("--w-------")) { tmp = getRootInfo("cat", s); output = buildArray(tmp, flag, flag_io); } } if (output[0].equals(NO_DATA_FOUND)) Log.e(LOG_TAG, "IO Exception when trying to get information.", e); return output; } } /** * Finds a String between two values (for now only used in io_schedulers) * * @param s => string used (from a given path) * * @return String */ public final String getInfoString(String s) { int open, close; String finalString = NO_DATA_FOUND; open = s.indexOf("["); close = s.lastIndexOf("]"); if (open >= 0 && close >= 0) { finalString = s.substring(open + 1, close); return finalString; } else { return finalString; } } /** * Converts raw frequencies to userfriendly values * * @param mhzString => String with frequencies * * @return String */ public final String toMHz(String mhzString) { if (mhzString.equals(NO_DATA_FOUND) || mhzString.equals("Unavaila")) return NO_DATA_FOUND; try { if (mhzString.length() < 8) return new StringBuilder().append(Integer.valueOf(mhzString) / 1000).append(" MHz") .toString(); else return new StringBuilder().append(Integer.valueOf(mhzString) / 1000000).append(" MHz") .toString(); } catch (NumberFormatException e) { Log.e(LOG_TAG, "Tried to add something to a non existing string.", e); return NO_DATA_FOUND; } } /** * Gets the total amount of memory and the total amount * of truly free memory. * * @param s => path to read * * @return String */ public final String getMemory(String s) { String totalMemory; String totalFreeMemory; try { /* /proc/meminfo entries follow this format: * MemTotal: 362096 kB * MemFree: 29144 kB * Buffers: 5236 kB * Cached: 81652 kB */ BufferedReader reader = new BufferedReader(new FileReader(s), BUFF_LEN); totalMemory = reader.readLine(); totalFreeMemory = reader.readLine(); if (totalMemory != null && totalFreeMemory != null) { String parts[] = totalMemory.split("\\s+"); if (parts.length == 3) { totalMemory = Long.parseLong(parts[1]) / 1024 + " MB"; } parts = totalFreeMemory.split("\\s+"); if (parts.length == 3) { totalFreeMemory = Long.parseLong(parts[1]) / 1024 + " MB"; } } } catch (IOException e) { Log.e(LOG_TAG, "Yep, i can't read your memory stats :( .", e); return NO_DATA_FOUND; } return totalFreeMemory + " / " + totalMemory; } /** * Generic Method for setting values in kernel with "echo" command. * This method is just a wrapper for runCommands(). * * @param command => (Object) and the value (echo value >) * @param content => the path to set the value (echo > path) */ public synchronized final void setRootInfo(final String command, final String content) { String tmp; String[] commands; // Check if last char is a whitespace; tmp = command.substring(command.length() - 1); if (tmp.matches("^\\s*$")) { tmp = command.substring(0, command.length() - 1); } else { tmp = command; } commands = new String[]{ "chmod 0666 " + content, "echo \"" + tmp + "\" " + "> " + content }; addCommands(commands); runCommands(); } /** * Generic Method for setting a bunch of commands * Same as setRootInfo but with an array instead of objects. * This method is just a wrapper for runCommands(). * * @param array => Commands to execute in an array */ public final void setRootInfo(final String array[]) { addCommands(array); runCommands(); } /** * Remounts the system (on the Defy) via runCommands(); * TODO: Adjust this for the according device. * */ public final void remountSystem() { addCommand("mount -o remount,rw -t ext3 /dev/block/mmcblk1p21 /system"); runCommands(); } /** * Executes a command in Terminal and returns output. * This method is just a wrapper for getRootResult(). * * @param command => set the command to execute * @param parameter => set a parameter * * @return String */ public final String getRootInfo(String command, String parameter) { String ret = null; addCommand(command + " " + parameter); ret = getRootResult(); if (ret == null) { ret = NO_DATA_FOUND; } return ret; } /** * This method returns an Array, with root rights. * This method is just a wrapper for getRootResult(). * * @param command => the actual command which runs with root-rights * @param split => delimiter, which seperates the strings (mostly \n) * * @return String[] */ public final String[] getRootArray(String command, String split) { ArrayList<String> temp = new ArrayList<String>(); String ret = null; addCommand(command); ret = getRootResult(); for (String a : ret.split(split)) { temp.add(a); } return temp.toArray(new String[0]); } /** * Executes a command in Terminal and returns output. Its only used internally for "legacy" devices/operations. * * @param command => set the command to execute * @param parameter => set a parameter * * @return String */ public String getLegacyRootInfo(String command, String parameter) { Process rooting = null; try { rooting = Runtime.getRuntime().exec("su"); DataOutputStream stdin = new DataOutputStream(rooting.getOutputStream()); stdin.writeBytes(command + " " + parameter + "\n"); InputStream stdout = rooting.getInputStream(); int read; String output = ""; while(true){ read = stdout.read(buffer); if (read == -1) return NO_DATA_FOUND; output += new String(buffer, 0, read); if(read<BUFF_LEN){ //we have read everything break; } } stdin.writeBytes("exit\n"); return output; } catch (IOException e) { Log.e(LOG_TAG, "Do you even root, bro? :/", e); } finally { if (rooting != null) { try { rooting.waitFor(); } catch (InterruptedException e) {} rooting.destroy(); } } return NO_DATA_FOUND; } /** * Sets the proper base addresses for the overclocking module. * * @return boolean, if overclocking was successful */ public final boolean setOverclockAddress() { if (new File("/proc/overclock/omap2_clk_init_cpufreq_table_addr").exists() && new File("/proc/overclock/cpufreq_stats_update_addr").exists()) { // getRootInfo() is much faster for large strings compared to getInfo() final String kallsyms = "/proc/kallsyms"; final String omap_address = getLegacyRootInfo("busybox egrep \"omap2_clk_init_cpufreq_table$\"", kallsyms).substring(0, 8); final String cpufreq_address = getLegacyRootInfo("busybox egrep \"cpufreq_stats_update$\"", kallsyms).substring(0, 8); final String [] commands = new String[] { "echo " + "0x" + omap_address + " > " + "/proc/overclock/omap2_clk_init_cpufreq_table_addr", "echo " + "0x" + cpufreq_address + " > " + "/proc/overclock/cpufreq_stats_update_addr", }; setRootInfo(commands); return true; } else { // Return quickly; return false; } } }