diff --git a/app/build.gradle b/app/build.gradle index 3c3f43a1553b3f33979f663a23c044d5b199ab16..26ba779558d721bef24342454e4e4f8bebaee11f 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { minSdkVersion 8 targetSdkVersion 23 - versionCode 108 - versionName "6.0d" + versionCode 111 + versionName "6.0e" } buildTypes { @@ -35,12 +35,11 @@ android { dependencies { compile 'com.android.support:appcompat-v7:23.4.0' - compile 'com.github.Andre1299:CompareString:1.4.2' } applicationVariants.all { variant -> def vn = variant.versionName - def x = vn.substring(0,vn.length() - 1) + def x = vn.substring(0, vn.length() - 1) variant.outputs.each { output -> output.outputFile = new File( @@ -48,4 +47,6 @@ android { output.outputFile.name.replace("app-release.apk", "${x}/${variant.applicationId}_${variant.versionName}_${new Date().format("dd-MM_hh.mm.ss")}.apk")) } } +} +dependencies { } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index caefc4f77430f1e641ae43c47a422ba334241191..83368d5ae174b2a12e4baa5bc528766b348531af 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,15 +29,6 @@ <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> - <uses-permission android:name="android.permission.READ_PHONE_STATE" /> - - <permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> - <permission - android:name="android.permission.FLASHLIGHT" - android:description="@string/help_flash" - android:label="@string/help_flash" - android:permissionGroup="android.permission-group.HARDWARE_CONTROLS" - android:protectionLevel="normal" /> <!-- features --> <uses-feature android:name="android.hardware.camera" diff --git a/app/src/main/java/ohi/andre/consolelauncher/LauncherActivity.java b/app/src/main/java/ohi/andre/consolelauncher/LauncherActivity.java index 83208301da35d7af1b2603e707cfdf970b908b10..816f3c09dcf08274fabd7e631f8af5a251b2959e 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/LauncherActivity.java +++ b/app/src/main/java/ohi/andre/consolelauncher/LauncherActivity.java @@ -160,11 +160,7 @@ public class LauncherActivity extends AppCompatActivity implements Reloadable { TimeManager.create(); } catch (Exception e) { Tuils.log(Tuils.getStackTrace(e)); - - try { - e.printStackTrace(new PrintStream(new FileOutputStream(new File(Tuils.getFolder(), "crash.txt"), true))); - } catch (FileNotFoundException e1) {} - + Tuils.toFile(e); return; } @@ -213,6 +209,8 @@ public class LauncherActivity extends AppCompatActivity implements Reloadable { main = new MainManager(this, in, out, sugg); ui = new UIManager(main.getMainPack(), this, mainView, ex, main.getMainPack()); main.setRedirectionListener(ui.buildRedirectionListener()); + main.setHintable(ui.getHintable()); + main.setRooter(ui.getRooter()); in.in(Tuils.EMPTYSTRING); ui.focusTerminal(); diff --git a/app/src/main/java/ohi/andre/consolelauncher/MainManager.java b/app/src/main/java/ohi/andre/consolelauncher/MainManager.java index 1a69a4d007e759eb6c486f83bf2d5d919de5efa0..309afe16c4b407c1fc073330adfa680582163ca5 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/MainManager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/MainManager.java @@ -2,11 +2,14 @@ package ohi.andre.consolelauncher; import android.content.Context; import android.content.Intent; +import android.os.Environment; import android.text.SpannableString; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.util.Log; +import java.io.File; +import java.util.List; import java.util.regex.Pattern; import ohi.andre.consolelauncher.commands.Command; @@ -18,7 +21,6 @@ import ohi.andre.consolelauncher.commands.specific.RedirectCommand; import ohi.andre.consolelauncher.managers.AliasManager; import ohi.andre.consolelauncher.managers.AppsManager; import ohi.andre.consolelauncher.managers.ContactManager; -import ohi.andre.consolelauncher.managers.ShellManager; import ohi.andre.consolelauncher.managers.TerminalManager; import ohi.andre.consolelauncher.managers.XMLPrefsManager; import ohi.andre.consolelauncher.managers.music.MusicManager; @@ -26,11 +28,15 @@ import ohi.andre.consolelauncher.tuils.StoppableThread; import ohi.andre.consolelauncher.tuils.TimeManager; import ohi.andre.consolelauncher.tuils.Tuils; import ohi.andre.consolelauncher.tuils.interfaces.CommandExecuter; +import ohi.andre.consolelauncher.tuils.interfaces.Hintable; import ohi.andre.consolelauncher.tuils.interfaces.Inputable; import ohi.andre.consolelauncher.tuils.interfaces.OnRedirectionListener; import ohi.andre.consolelauncher.tuils.interfaces.Outputable; import ohi.andre.consolelauncher.tuils.interfaces.Redirectator; +import ohi.andre.consolelauncher.tuils.interfaces.Rooter; import ohi.andre.consolelauncher.tuils.interfaces.Suggester; +import ohi.andre.consolelauncher.tuils.libsuperuser.Shell; +import ohi.andre.consolelauncher.tuils.libsuperuser.StreamGobbler; /*Copyright Francesco Andreuzzi @@ -88,7 +94,6 @@ public class MainManager { new SystemCommandTrigger() }; private MainPack mainPack; - private ShellManager shell; private Context mContext; @@ -98,6 +103,10 @@ public class MainManager { private boolean showAliasValue; private boolean showAppHistory; + public static Shell.Interactive interactive; + + private Hintable hintable; + protected MainManager(LauncherActivity c, Inputable i, Outputable o, Suggester sugg) { mContext = c; @@ -127,9 +136,24 @@ public class MainManager { AppsManager appsMgr = new AppsManager(c, out, sugg); AliasManager aliasManager = new AliasManager(); - shell = new ShellManager(out); + interactive = new Shell.Builder() + .setOnSTDOUTLineListener(new StreamGobbler.OnLineListener() { + @Override + public void onLine(String line) { + out.onOutput(line); + } + }) + .setOnSTDERRLineListener(new StreamGobbler.OnLineListener() { + @Override + public void onLine(String line) { + out.onOutput(line); + } + }) + .open(); - mainPack = new MainPack(mContext, group, aliasManager, appsMgr, music, cont, c, executer, out, redirectator, shell); + interactive.addCommand("cd " + Environment.getExternalStorageDirectory().getAbsolutePath()); + + mainPack = new MainPack(mContext, group, aliasManager, appsMgr, music, cont, c, executer, out, redirectator); } // command manager @@ -179,12 +203,21 @@ public class MainManager { public void destroy() { mainPack.destroy(); + interactive.close(); } public MainPack getMainPack() { return mainPack; } + public void setHintable(Hintable hintable) { + this.hintable = hintable; + } + + public void setRooter(Rooter rooter) { + this.mainPack.rooter = rooter; + } + interface CmdTrigger { boolean trigger(ExecutePack info, String input) throws Exception; } @@ -205,14 +238,48 @@ public class MainManager { private class SystemCommandTrigger implements CmdTrigger { + final int CD_CODE = 10; + final int PWD_CODE = 11; + + final Shell.OnCommandResultListener pwdResult = new Shell.OnCommandResultListener() { + @Override + public void onCommandResult(int commandCode, int exitCode, List<String> output) { + if(commandCode == PWD_CODE && output.size() == 1) { + File f = new File(output.get(0)); + if(f.exists()) { + mainPack.currentDirectory = f; + if(hintable != null) hintable.updateHint(); + } + } + } + }; + + final Shell.OnCommandResultListener cdResult = new Shell.OnCommandResultListener() { + @Override + public void onCommandResult(int commandCode, int exitCode, List<String> output) { + if(commandCode == CD_CODE) { + interactive.addCommand("pwd", PWD_CODE, pwdResult); + } + } + }; + @Override public boolean trigger(final ExecutePack info, final String input) throws Exception { + new Thread() { @Override public void run() { - shell.cmd(input, true); - ((MainPack) info).currentDirectory = shell.currentDir(); - }; + if(input.trim().equalsIgnoreCase("su")) { + if(Shell.SU.available() && mainPack.rooter != null) mainPack.rooter.onRoot(); + interactive.addCommand("su"); + + } else if(input.contains("cd ")) { + interactive.addCommand(input, CD_CODE, cdResult); + } else { + interactive.addCommand(input); + } + + } }.start(); return true; diff --git a/app/src/main/java/ohi/andre/consolelauncher/UIManager.java b/app/src/main/java/ohi/andre/consolelauncher/UIManager.java index e231bdda47ae84a9ab7841601dbaa8d46c87ec8d..40f77b61733047be5a27a2a2bfe6f39adbb935f3 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/UIManager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/UIManager.java @@ -44,7 +44,9 @@ import ohi.andre.consolelauncher.tuils.StoppableThread; import ohi.andre.consolelauncher.tuils.TimeManager; import ohi.andre.consolelauncher.tuils.Tuils; import ohi.andre.consolelauncher.tuils.interfaces.CommandExecuter; +import ohi.andre.consolelauncher.tuils.interfaces.Hintable; import ohi.andre.consolelauncher.tuils.interfaces.OnRedirectionListener; +import ohi.andre.consolelauncher.tuils.interfaces.Rooter; import ohi.andre.consolelauncher.tuils.interfaces.SuggestionViewDecorer; import ohi.andre.consolelauncher.tuils.stuff.PolicyReceiver; import ohi.andre.consolelauncher.tuils.stuff.TrashInterfaces; @@ -740,6 +742,19 @@ public class UIManager implements OnTouchListener { mTerminalAdapter.scrollToEnd(); } + public Hintable getHintable() { + return new Hintable() { + @Override + public void updateHint() { + mTerminalAdapter.setDefaultHint(); + } + }; + } + + public Rooter getRooter() { + return mTerminalAdapter.getRooter(); + } + // init detector for double tap private void initDetector() { det = new GestureDetector(mContext, TrashInterfaces.trashGestureListener); diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/CommandAbstraction.java b/app/src/main/java/ohi/andre/consolelauncher/commands/CommandAbstraction.java index 69dfc94edd0539251f0e988deabcff97e212b5a1..1b2f4c177e4b175a7a6ec619914d07b08ee08b4a 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/CommandAbstraction.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/CommandAbstraction.java @@ -22,6 +22,7 @@ public interface CommandAbstraction { int CONFIG_ENTRY = 23; int INT = 24; int DEFAULT_APP = 25; + int ALL_PACKAGES = 26; String exec(ExecutePack pack) throws Exception; diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/CommandTuils.java b/app/src/main/java/ohi/andre/consolelauncher/commands/CommandTuils.java index afabfdb364ce5864809c78948c46fbc17bc054b9..d1714868d00af2d0bc5959c349b4c596e72e2049 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/CommandTuils.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/CommandTuils.java @@ -24,9 +24,6 @@ import ohi.andre.consolelauncher.tuils.Tuils; @SuppressLint("DefaultLocale") public class CommandTuils { - private static final int MIN_CONTACT_RATE = 4; - private static final int MIN_SONG_RATE = 5; - private static FileManager.SpecificExtensionFileFilter extensionFileFilter = new FileManager.SpecificExtensionFileFilter(); private static FileManager.SpecificNameFileFilter nameFileFilter = new FileManager.SpecificNameFileFilter(); @@ -37,12 +34,6 @@ public class CommandTuils { public static Command parse(String input, ExecutePack info, boolean suggestion) throws Exception { Command command = new Command(); - boolean pendingSuVerification = false; - if (!suggestion && isSuCommand(input)) { - input = input.substring(3); - pendingSuVerification = true; - } - String name = CommandTuils.findName(input); if (!Tuils.isAlpha(name)) return null; @@ -53,10 +44,6 @@ public class CommandTuils { } command.cmd = cmd; - if (pendingSuVerification && info instanceof MainPack) { - ((MainPack) info).setSu(Tuils.verifyRoot()); - } - input = input.substring(name.length()); input = input.trim(); @@ -169,6 +156,8 @@ public class CommandTuils { return integer(input); } else if(type == CommandAbstraction.DEFAULT_APP) { return defaultApp(input, ((MainPack) info).appsManager); + } else if(type == CommandAbstraction.ALL_PACKAGES) { + return allPackages(input, ((MainPack) info).appsManager); } return null; @@ -354,6 +343,15 @@ public class CommandTuils { return new ArgInfo(info, null, info != null, info != null ? 1 : 0); } + private static ArgInfo allPackages(String input, AppsManager apps) { + AppsManager.LaunchInfo info = apps.findLaunchInfoWithLabel(input, AppsManager.SHOWN_APPS); + if(info == null) { + info = apps.findLaunchInfoWithLabel(input, AppsManager.HIDDEN_APPS); + } + + return new ArgInfo(info, null, info != null, info != null ? 1 : 0); + } + private static ArgInfo defaultApp(String input, AppsManager apps) { AppsManager.LaunchInfo info = apps.findLaunchInfoWithLabel(input, AppsManager.SHOWN_APPS); if(info == null) { @@ -369,14 +367,13 @@ public class CommandTuils { if (Tuils.isNumber(input)) number = input; else - number = contacts.findNumber(input, MIN_CONTACT_RATE); + number = contacts.findNumber(input); return new ArgInfo(number, null, number != null, 1); } private static ArgInfo song(String input, MusicManager music) { - String name = music.getSong(input, MIN_SONG_RATE); - return new ArgInfo(name, null, name != null, 1); + return new ArgInfo(input, null, true, 1); } private static ArgInfo configEntry(String input) { diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/MainPack.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/MainPack.java index f89f0aac194f39c4a5f9bbc4eaf1c2808b0b7c45..d6bfb0701510fcd5276b85b14eb7e95953363cbb 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/MainPack.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/MainPack.java @@ -8,6 +8,7 @@ import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Build; +import android.os.Environment; import java.io.File; import java.lang.reflect.Method; @@ -20,14 +21,13 @@ import ohi.andre.consolelauncher.commands.main.raw.flash; import ohi.andre.consolelauncher.managers.AliasManager; import ohi.andre.consolelauncher.managers.AppsManager; import ohi.andre.consolelauncher.managers.ContactManager; -import ohi.andre.consolelauncher.managers.ShellManager; import ohi.andre.consolelauncher.managers.SkinManager; -import ohi.andre.consolelauncher.managers.XMLPrefsManager; import ohi.andre.consolelauncher.managers.music.MusicManager; import ohi.andre.consolelauncher.tuils.interfaces.CommandExecuter; import ohi.andre.consolelauncher.tuils.interfaces.Outputable; import ohi.andre.consolelauncher.tuils.interfaces.Redirectator; import ohi.andre.consolelauncher.tuils.interfaces.Reloadable; +import ohi.andre.consolelauncher.tuils.interfaces.Rooter; /** * Created by francescoandreuzzi on 24/01/2017. @@ -40,8 +40,6 @@ public class MainPack extends ExecutePack { // current directory public File currentDirectory; - public ShellManager shellManager; - public SkinManager skinManager; // resources references @@ -55,9 +53,6 @@ public class MainPack extends ExecutePack { // internet public WifiManager wifi; - // prefs - public XMLPrefsManager preferencesManager; - // 3g/data public Method setMobileDataEnabledMethod; public ConnectivityManager connectivityMgr; @@ -76,12 +71,12 @@ public class MainPack extends ExecutePack { // reload field public Reloadable reloadable; + public Rooter rooter; + public CommandsPreferences cmdPrefs; // execute a command public CommandExecuter executer; - // uses su - private boolean canUseSu = false; public LocationManager locationManager; @@ -90,11 +85,10 @@ public class MainPack extends ExecutePack { public Redirectator redirectator; public MainPack(Context context, CommandGroup commandGroup, AliasManager alMgr, AppsManager appmgr, MusicManager p, - ContactManager c, Reloadable r, CommandExecuter executeCommand, Outputable outputable, Redirectator redirectator, ShellManager shellManager) { + ContactManager c, Reloadable r, CommandExecuter executeCommand, Outputable outputable, Redirectator redirectator) { super(commandGroup); - this.shellManager = shellManager; - this.currentDirectory = shellManager.currentDir(); + this.currentDirectory = Environment.getExternalStorageDirectory(); this.outputable = outputable; @@ -119,16 +113,6 @@ public class MainPack extends ExecutePack { this.redirectator = redirectator; } - public boolean getSu() { - boolean su = canUseSu; - canUseSu = false; - return su; - } - - public void setSu(boolean su) { - this.canUseSu = su; - } - public void initCamera() { try { this.camera = Camera.open(); @@ -160,13 +144,5 @@ public class MainPack extends ExecutePack { public void destroy() { player.destroy(this.context); appsManager.onDestroy(); - shellManager.destroy(); - } - - @Override - public void clear() { - super.clear(); - - setSu(false); } } diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/apps.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/apps.java index 7eb3ad7cce7d5c59a4d31507c2e1c1f30fa7404f..27d74d7d30e3ae189d784abe73840c8c5855ef9f 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/apps.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/apps.java @@ -132,7 +132,7 @@ public class apps extends ParamCommand { frc { @Override public int[] args() { - return new int[] {CommandAbstraction.VISIBLE_PACKAGE}; + return new int[] {CommandAbstraction.ALL_PACKAGES}; } @Override diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/cp.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/cp.java deleted file mode 100755 index 470df4564af675877df035bbb975de7fa2c641b6..0000000000000000000000000000000000000000 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/cp.java +++ /dev/null @@ -1,77 +0,0 @@ -package ohi.andre.consolelauncher.commands.main.raw; - -import java.io.File; -import java.util.ArrayList; - -import ohi.andre.consolelauncher.R; -import ohi.andre.consolelauncher.commands.CommandAbstraction; -import ohi.andre.consolelauncher.commands.ExecutePack; -import ohi.andre.consolelauncher.commands.main.MainPack; -import ohi.andre.consolelauncher.managers.FileManager; - -/** - * Created by andre on 03/12/15. - */ -public class cp implements CommandAbstraction { - - @Override - public String exec(ExecutePack pack) throws Exception { - MainPack info = (MainPack) pack; - - ArrayList<File> args = info.get(ArrayList.class, 0); - - File where = args.remove(args.size() - 1); - File[] files = new File[args.size()]; - args.toArray(files); - - int result = FileManager.cp(files, where, info.getSu()); - switch (result) { - case FileManager.ISFILE: - return info.res.getString(R.string.output_isfile); - case FileManager.IOERROR: - return info.res.getString(R.string.output_error); - case FileManager.NOT_READABLE: - return info.res.getString(R.string.output_noreadable); - case FileManager.NOT_WRITEABLE: - return info.res.getString(R.string.output_nowriteable); - } - return null; - } - - @Override - public int minArgs() { - return 2; - } - - @Override - public int maxArgs() { - return CommandAbstraction.UNDEFINIED; - } - - @Override - public int[] argType() { - return new int[]{CommandAbstraction.FILE_LIST}; - } - - @Override - public int priority() { - return 4; - } - - @Override - public int helpRes() { - return R.string.help_cp; - } - - @Override - public String onArgNotFound(ExecutePack pack, int index) { - MainPack info = (MainPack) pack; - return info.res.getString(R.string.output_filenotfound); - } - - @Override - public String onNotArgEnough(ExecutePack pack, int nArgs) { - MainPack info = (MainPack) pack; - return info.res.getString(R.string.output_filenotfound); - } -} diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/ctrlc.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/ctrlc.java index f8bd9ac29ce139a457bf0d1c316ba9f643c6a31d..81aa1ed805a975849328fcdd441f50571940f6b2 100644 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/ctrlc.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/ctrlc.java @@ -1,9 +1,14 @@ package ohi.andre.consolelauncher.commands.main.raw; +import android.os.Environment; + +import ohi.andre.consolelauncher.MainManager; import ohi.andre.consolelauncher.R; import ohi.andre.consolelauncher.commands.CommandAbstraction; import ohi.andre.consolelauncher.commands.ExecutePack; import ohi.andre.consolelauncher.commands.main.MainPack; +import ohi.andre.consolelauncher.tuils.libsuperuser.Shell; +import ohi.andre.consolelauncher.tuils.libsuperuser.StreamGobbler; /** * Created by francescoandreuzzi on 26/07/2017. @@ -12,8 +17,37 @@ import ohi.andre.consolelauncher.commands.main.MainPack; public class ctrlc implements CommandAbstraction { @Override - public String exec(ExecutePack pack) throws Exception { - ((MainPack) pack).shellManager.reset(); + public String exec(final ExecutePack pack) throws Exception { + new Thread() { + @Override + public void run() { + super.run(); + + MainManager.interactive.kill(); + MainManager.interactive.close(); + MainManager.interactive = null; + + MainManager.interactive = new Shell.Builder() + .setOnSTDOUTLineListener(new StreamGobbler.OnLineListener() { + @Override + public void onLine(String line) { + ((MainPack) pack).outputable.onOutput(line); + } + }) + .setOnSTDERRLineListener(new StreamGobbler.OnLineListener() { + @Override + public void onLine(String line) { + ((MainPack) pack).outputable.onOutput(line); + } + }) + .open(); + + MainManager.interactive.addCommand("cd " + Environment.getExternalStorageDirectory().getAbsolutePath()); + } + }.start(); + + ((MainPack) pack).rooter.onStandard(); + return null; } diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/data.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/data.java index 313f46b97b61bb37f753c5a3d1d9e6261029d62e..7240a73174f29883f792d1fb21c31b99db81db2c 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/data.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/data.java @@ -13,7 +13,6 @@ import ohi.andre.consolelauncher.R; import ohi.andre.consolelauncher.commands.CommandAbstraction; import ohi.andre.consolelauncher.commands.ExecutePack; import ohi.andre.consolelauncher.commands.main.MainPack; -import ohi.andre.consolelauncher.tuils.ShellUtils; import ohi.andre.consolelauncher.tuils.Tuils; public class data implements CommandAbstraction { @@ -27,8 +26,8 @@ public class data implements CommandAbstraction { active = toggle(info); return info.res.getString(R.string.output_data) + Tuils.SPACE + Boolean.toString(active); } else { - ShellUtils.CommandResult result = ShellUtils.execCommand("svc data " + (active ? "enable" : "disable"), true, null); - return info.res.getString(R.string.output_commandexitvalue) + Tuils.SPACE + result.result; +// ShellUtils.CommandResult result = ShellUtils.execCommand("svc data " + (active ? "enable" : "disable"), true, null); + return pack.context.getString(R.string.output_nofeature); } } diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/help.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/help.java index b0d47f0469e35320834e8351e055ad6117a18f76..465f71f540059f7574d88f7411bda4332f0e15a2 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/help.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/help.java @@ -6,7 +6,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import ohi.andre.comparestring.Compare; import ohi.andre.consolelauncher.R; import ohi.andre.consolelauncher.commands.CommandAbstraction; import ohi.andre.consolelauncher.commands.ExecutePack; @@ -56,7 +55,7 @@ public class help implements CommandAbstraction { Collections.sort(toPrint, new Comparator<String>() { @Override public int compare(String lhs, String rhs) { - return Compare.alphabeticCompare(lhs, rhs); + return Tuils.alphabeticCompare(lhs, rhs); } }); diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/ls.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/ls.java deleted file mode 100755 index c4995c969810d980972d13b1565fd2d1cd3ea8fd..0000000000000000000000000000000000000000 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/ls.java +++ /dev/null @@ -1,85 +0,0 @@ -package ohi.andre.consolelauncher.commands.main.raw; - -/*Copyright Francesco Andreuzzi - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.*/ - -import java.io.File; -import java.util.List; - -import ohi.andre.consolelauncher.R; -import ohi.andre.consolelauncher.commands.CommandAbstraction; -import ohi.andre.consolelauncher.commands.ExecutePack; -import ohi.andre.consolelauncher.commands.main.MainPack; -import ohi.andre.consolelauncher.managers.FileManager; -import ohi.andre.consolelauncher.tuils.ShellUtils; -import ohi.andre.consolelauncher.tuils.Tuils; - -public class ls implements CommandAbstraction { - - @Override - public String exec(ExecutePack pack) throws Exception { - MainPack info = (MainPack) pack; - File file = info.get(File.class, 0); - - if (info.getSu()) { - ShellUtils.CommandResult result = ShellUtils.execCommand("ls " + file.getAbsolutePath(), true, info.currentDirectory.getAbsolutePath()); - return result.toString(); - } else { - List<File> files = FileManager.lsFile(file, true); - return Tuils.filesToPlanString(files, Tuils.NEWLINE); - } - } - - @Override - public int minArgs() { - return 1; - } - - @Override - public int maxArgs() { - return 1; - } - - @Override - public int[] argType() { - return new int[]{CommandAbstraction.FILE}; - } - - @Override - public int priority() { - return 3; - } - - @Override - public int helpRes() { - return R.string.help_ls; - } - - @Override - public String onArgNotFound(ExecutePack pack, int index) { - MainPack info = (MainPack) pack; - return info.res.getString(R.string.output_filenotfound); - } - - @Override - public String onNotArgEnough(ExecutePack pack, int nArgs) { - MainPack info = (MainPack) pack; - info.set(new File[]{info.currentDirectory}); - try { - return exec(info); - } catch (Exception e) { - return e.toString(); - } - } -} diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/mv.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/mv.java deleted file mode 100755 index b7c565ba5e74d40650786a04647b4810e053fc0b..0000000000000000000000000000000000000000 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/mv.java +++ /dev/null @@ -1,77 +0,0 @@ -package ohi.andre.consolelauncher.commands.main.raw; - -import java.io.File; -import java.util.ArrayList; - -import ohi.andre.consolelauncher.R; -import ohi.andre.consolelauncher.commands.CommandAbstraction; -import ohi.andre.consolelauncher.commands.ExecutePack; -import ohi.andre.consolelauncher.commands.main.MainPack; -import ohi.andre.consolelauncher.managers.FileManager; - -/** - * Created by andre on 03/12/15. - */ -public class mv implements CommandAbstraction { - - @Override - public String exec(ExecutePack pack) throws Exception { - MainPack info = (MainPack) pack; - - ArrayList<File> args = info.get(ArrayList.class, 0); - - File where = args.remove(args.size() - 1); - File[] files = new File[args.size()]; - args.toArray(files); - - int result = FileManager.mv(files, where, info.getSu()); - switch (result) { - case FileManager.ISFILE: - return info.res.getString(R.string.output_isfile); - case FileManager.IOERROR: - return info.res.getString(R.string.output_error); - case FileManager.NOT_READABLE: - return info.res.getString(R.string.output_noreadable); - case FileManager.NOT_WRITEABLE: - return info.res.getString(R.string.output_nowriteable); - } - return null; - } - - @Override - public int minArgs() { - return 2; - } - - @Override - public int maxArgs() { - return CommandAbstraction.UNDEFINIED; - } - - @Override - public int[] argType() { - return new int[]{CommandAbstraction.FILE_LIST}; - } - - @Override - public int priority() { - return 4; - } - - @Override - public int helpRes() { - return R.string.help_mv; - } - - @Override - public String onArgNotFound(ExecutePack pack, int index) { - MainPack info = (MainPack) pack; - return info.res.getString(R.string.output_filenotfound); - } - - @Override - public String onNotArgEnough(ExecutePack pack, int nArgs) { - MainPack info = (MainPack) pack; - return info.res.getString(R.string.output_filenotfound); - } -} diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/rm.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/rm.java deleted file mode 100755 index 0dd1827343d96bd226406a69316a6bd70a3aa16d..0000000000000000000000000000000000000000 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/rm.java +++ /dev/null @@ -1,75 +0,0 @@ -package ohi.andre.consolelauncher.commands.main.raw; - -import android.util.Log; - -import java.io.File; -import java.util.ArrayList; - -import ohi.andre.consolelauncher.R; -import ohi.andre.consolelauncher.commands.CommandAbstraction; -import ohi.andre.consolelauncher.commands.ExecutePack; -import ohi.andre.consolelauncher.commands.main.MainPack; -import ohi.andre.consolelauncher.managers.FileManager; - -/** - * Created by andre on 03/12/15. - */ -public class rm implements CommandAbstraction { - - @Override - public String exec(ExecutePack pack) { - MainPack info = (MainPack) pack; - ArrayList<File> args = info.get(ArrayList.class, 0); - - File[] files = new File[args.size()]; - args.toArray(files); - - int result = FileManager.rm(files, info.getSu()); - switch (result) { - case FileManager.ISFILE: - return info.res.getString(R.string.output_isfile); - case FileManager.IOERROR: - return info.res.getString(R.string.output_error); - case FileManager.NOT_WRITEABLE: - return info.res.getString(R.string.output_nowriteable); - } - return null; - } - - @Override - public int minArgs() { - return 1; - } - - @Override - public int maxArgs() { - return CommandAbstraction.UNDEFINIED; - } - - @Override - public int[] argType() { - return new int[]{CommandAbstraction.FILE_LIST}; - } - - @Override - public int priority() { - return 4; - } - - @Override - public int helpRes() { - return R.string.help_rm; - } - - @Override - public String onArgNotFound(ExecutePack pack, int index) { - MainPack info = (MainPack) pack; - return info.res.getString(R.string.output_filenotfound); - } - - @Override - public String onNotArgEnough(ExecutePack pack, int nArgs) { - MainPack info = (MainPack) pack; - return info.res.getString(R.string.output_filenotfound); - } -} diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/search.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/search.java index 556b86344a9515b66d64b39956a54a30c7dd0dce..3d98020be9c6c0805d25318d71ba542ad2f05111 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/search.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/search.java @@ -9,18 +9,14 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -import ohi.andre.comparestring.Compare; import ohi.andre.consolelauncher.R; import ohi.andre.consolelauncher.commands.CommandAbstraction; import ohi.andre.consolelauncher.commands.ExecutePack; import ohi.andre.consolelauncher.commands.main.MainPack; import ohi.andre.consolelauncher.commands.specific.ParamCommand; -import ohi.andre.consolelauncher.managers.FileManager; import ohi.andre.consolelauncher.tuils.Tuils; import ohi.andre.consolelauncher.tuils.interfaces.Outputable; -import static ohi.andre.consolelauncher.managers.FileManager.MIN_FILE_RATE; - public class search extends ParamCommand { private static final String YOUTUBE_PREFIX = "https://www.youtube.com/results?search_query="; @@ -177,7 +173,7 @@ public class search extends ParamCommand { super.run(); String name = Tuils.toPlanString(args); - List<String> paths = rightPaths(cd, name, FileManager.USE_SCROLL_COMPARE); + List<String> paths = rightPaths(cd, name); if(paths.size() == 0) { outputable.onOutput(res.getString(R.string.output_nothing_found)); } else { @@ -189,31 +185,27 @@ public class search extends ParamCommand { return Tuils.EMPTYSTRING; } - private static List<String> rightPaths(File dir, String name, boolean scrollCompare) { + private static List<String> rightPaths(File dir, String name) { File[] files = dir.listFiles(); List<String> rightPaths = new ArrayList<>(files.length); boolean check = false; for (File file : files) { - if (fileMatch(file, name, scrollCompare)) { + if (fileMatch(file, name)) { if (!check) rightPaths.add(dir.getAbsolutePath()); check = true; rightPaths.add(Tuils.NEWLINE + Tuils.DOUBLE_SPACE + file.getAbsolutePath()); } if (file.isDirectory()) - rightPaths.addAll(rightPaths(file, name, scrollCompare)); + rightPaths.addAll(rightPaths(file, name)); } return rightPaths; } - private static boolean fileMatch(File f, String name, boolean scrollCompare) { - if (scrollCompare) { - return Compare.scrollComparison(f.getName(), name) >= MIN_FILE_RATE; - } else { - return Compare.linearComparison(f.getName(), name) >= MIN_FILE_RATE; - } + private static boolean fileMatch(File f, String name) { + return f.getName().equalsIgnoreCase(name); } private static String youTube(List<String> args, Context c) { diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/shellcommands.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/shellcommands.java index 250fb4d7f341a214fd609c1d54eb480a3f7913b0..f9ad8ed30264a5a1c9a58e1428f08b5016f6b5ac 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/shellcommands.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/shellcommands.java @@ -10,7 +10,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import ohi.andre.comparestring.Compare; import ohi.andre.consolelauncher.R; import ohi.andre.consolelauncher.commands.CommandAbstraction; import ohi.andre.consolelauncher.commands.ExecutePack; @@ -29,7 +28,7 @@ public class shellcommands implements CommandAbstraction { Collections.sort(commands, new Comparator<String>() { @Override public int compare(String lhs, String rhs) { - return Compare.alphabeticCompare(lhs, rhs); + return Tuils.alphabeticCompare(lhs, rhs); } }); diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/tui.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/tui.java index e572ae0bc93afb91433ad2436c7018e5383f048a..26b1f8d4396ca01793e039e901ccfbc01519d506 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/tui.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/tui.java @@ -49,7 +49,7 @@ public class tui extends ParamCommand { reset { @Override public String exec(ExecutePack pack) { - FileManager.rm(Tuils.getFolder(), false); + FileManager.rm(Tuils.getFolder()); return null; } }, diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/uninstall.java b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/uninstall.java index 881377f4d6b583dcd3dd9ca745c686c0d4a2f6de..949d893d873c561f0a60354de5965806b20df7fe 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/uninstall.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/main/raw/uninstall.java @@ -8,7 +8,6 @@ import ohi.andre.consolelauncher.commands.CommandAbstraction; import ohi.andre.consolelauncher.commands.ExecutePack; import ohi.andre.consolelauncher.commands.main.MainPack; import ohi.andre.consolelauncher.managers.AppsManager; -import ohi.andre.consolelauncher.tuils.ShellUtils; import ohi.andre.consolelauncher.tuils.Tuils; public class uninstall implements CommandAbstraction { @@ -18,11 +17,11 @@ public class uninstall implements CommandAbstraction { MainPack info = (MainPack) pack; String packageName = info.get(AppsManager.LaunchInfo.class, 0).componentName.getPackageName(); - if (info.getSu()) { - try { - return ShellUtils.execCommand("su pm uninstall " + packageName, true, null).toString(); - } catch (Exception e) {} - } +// if (info.getSu()) { +// try { +// return ShellUtils.execCommand("su pm uninstall " + packageName, true, null).toString(); +// } catch (Exception e) {} +// } Uri packageURI = Uri.parse("package:" + packageName); Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI); diff --git a/app/src/main/java/ohi/andre/consolelauncher/commands/tuixt/raw/help.java b/app/src/main/java/ohi/andre/consolelauncher/commands/tuixt/raw/help.java index fd90d4dfa0fa3ef8ae50cbea0b484dc5ba5e8287..8383a3356d093b7a21ff2c201031b7998fe6f989 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/commands/tuixt/raw/help.java +++ b/app/src/main/java/ohi/andre/consolelauncher/commands/tuixt/raw/help.java @@ -6,7 +6,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import ohi.andre.comparestring.Compare; import ohi.andre.consolelauncher.R; import ohi.andre.consolelauncher.commands.CommandAbstraction; import ohi.andre.consolelauncher.commands.ExecutePack; @@ -66,7 +65,7 @@ public class help implements CommandAbstraction { Collections.sort(toPrint, new Comparator<String>() { @Override public int compare(String lhs, String rhs) { - return Compare.alphabeticCompare(lhs, rhs); + return Tuils.alphabeticCompare(lhs, rhs); } }); diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/AppsManager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/AppsManager.java index 7a8280bdb7ec79c214ff8476e4b87a2dc0662885..31d9e7671ded0b7044980753c08c243e6006ea34 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/managers/AppsManager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/managers/AppsManager.java @@ -35,7 +35,6 @@ import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import ohi.andre.comparestring.Compare; import ohi.andre.consolelauncher.R; import ohi.andre.consolelauncher.tuils.TimeManager; import ohi.andre.consolelauncher.tuils.Tuils; @@ -52,8 +51,6 @@ public class AppsManager implements XMLPrefsManager.XmlPrefsElement { public static final int SHOWN_APPS = 10; public static final int HIDDEN_APPS = 11; - public static final boolean USE_SCROLL_COMPARE = false; - public static final String PATH = "apps.xml"; private final String NAME = "APPS"; @@ -932,7 +929,7 @@ public class AppsManager implements XMLPrefsManager.XmlPrefsElement { Collections.sort(list, new Comparator<String>() { @Override public int compare(String lhs, String rhs) { - return Compare.alphabeticCompare(lhs, rhs); + return Tuils.alphabeticCompare(lhs, rhs); } }); diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/ContactManager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/ContactManager.java index 978deb1e361b2d62e6c1d99602925bd156a06dba..10e8aa736aa554369b8e6c0d9d26cd9adb9d53da 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/managers/ContactManager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/managers/ContactManager.java @@ -15,14 +15,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import ohi.andre.comparestring.Compare; import ohi.andre.consolelauncher.LauncherActivity; import ohi.andre.consolelauncher.tuils.Tuils; public class ContactManager { - public static final boolean USE_SCROLL_COMPARE = false; - private Context context; private List<Contact> contacts; @@ -213,15 +210,12 @@ public class ContactManager { return about; } - public String findNumber(String name, int minRate) { + public String findNumber(String name) { if(contacts == null) refreshContacts(this, context); - String mostSuitable = Compare.similarString(listNames(), name, minRate, USE_SCROLL_COMPARE); - if(mostSuitable == null) return null; - for(int count = 0; count < contacts.size(); count++) { Contact c = contacts.get(count); - if(c.name.equals(mostSuitable)) { + if(c.name.equalsIgnoreCase(name)) { if(c.numbers.size() > 0) return c.numbers.get(0); } } diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/FileManager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/FileManager.java index 8292b0497fa923c29b2287fe7e4fe6eb64f4de51..e81a56e930745513c61bf8b5adc8c98b5bfefcd8 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/managers/FileManager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/managers/FileManager.java @@ -13,8 +13,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; -import ohi.andre.comparestring.Compare; -import ohi.andre.consolelauncher.tuils.ShellUtils; +import ohi.andre.consolelauncher.MainManager; import ohi.andre.consolelauncher.tuils.Tuils; public class FileManager { @@ -62,7 +61,7 @@ public class FileManager { } } - public static int mv(File[] files, File where, boolean su) throws IOException { + public static int mv(File[] files, File where) throws IOException { if (files == null || files.length == 0 || where == null) { return FileManager.FILE_NOTFOUND; } @@ -72,43 +71,35 @@ public class FileManager { } for (File f : files) { - mv(f, where, su); + mv(f, where); } return 0; } - private static int mv(File f, File where, boolean su) throws IOException { - ShellUtils.CommandResult result = ShellUtils.execCommand("mv " + Tuils.SPACE + - f.getAbsolutePath() + Tuils.SPACE + - where.getAbsolutePath(), su, null); - - return result.result; + private static int mv(File f, File where) throws IOException { + MainManager.interactive.addCommand("mv " + Tuils.SPACE + f.getAbsolutePath() + Tuils.SPACE + where.getAbsolutePath()); + return 0; } - public static int rm(File[] files, boolean su) { + public static int rm(File[] files) { if (files == null || files.length == 0) { return FileManager.FILE_NOTFOUND; } for (File f : files) { - rm(f, su); + rm(f); } return 0; } - public static int rm(File f, boolean su) { - ShellUtils.CommandResult result = ShellUtils.execCommand("rm " + - (f.isDirectory() ? "-r" : Tuils.EMPTYSTRING) + - Tuils.SPACE + - f.getAbsolutePath(), su, null); - - if(result == null) return IOERROR; - return result.result; + public static int rm(File f) { + MainManager.interactive.addCommand("rm " + (f.isDirectory() ? "-r" : Tuils.EMPTYSTRING) + Tuils.SPACE + f.getAbsolutePath()); + return 0; } - public static int cp(File[] files, File where, boolean su) throws IOException { + public static int cp(File[] files, File where) throws IOException { if (files == null || files.length == 0 || where == null) { return FileManager.FILE_NOTFOUND; } @@ -118,17 +109,15 @@ public class FileManager { } for (File f : files) { - cp(f, where, su); + cp(f, where); } return 0; } - private static int cp(File f, File where, boolean su) throws IOException { - ShellUtils.CommandResult result = ShellUtils.execCommand("cp " + Tuils.SPACE + - f.getAbsolutePath() + Tuils.SPACE + - where.getAbsolutePath(), su, null); - return result.result; + private static int cp(File f, File where) throws IOException { + MainManager.interactive.addCommand("cp " + Tuils.SPACE + f.getAbsolutePath() + Tuils.SPACE + where.getAbsolutePath()); + return 0; } public static List<File> lsFile(File f, boolean showHidden) { @@ -156,7 +145,7 @@ public class FileManager { if (rhs.isDirectory() && !lhs.isDirectory()) return 1; - return Compare.alphabeticCompare(lhs.getName(), rhs.getName()); + return Tuils.alphabeticCompare(lhs.getName(), rhs.getName()); } }); diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/ShellManager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/ShellManager.java deleted file mode 100644 index 2636bc14ed0ee8894212d5c4511e7ca5dfe16281..0000000000000000000000000000000000000000 --- a/app/src/main/java/ohi/andre/consolelauncher/managers/ShellManager.java +++ /dev/null @@ -1,259 +0,0 @@ -package ohi.andre.consolelauncher.managers; - -import android.os.Environment; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import ohi.andre.consolelauncher.tuils.StoppableThread; -import ohi.andre.consolelauncher.tuils.Tuils; -import ohi.andre.consolelauncher.tuils.interfaces.Outputable; - -/** - * Created by francescoandreuzzi on 25/07/2017. - */ - -public class ShellManager { - - Process p; - Thread t; - - File file; - - InputStream output; - InputStream error; - OutputStream cmdStream; - - Outputable outputable; - - public ShellManager(Outputable outputable) { - this.outputable = outputable; - - init(Environment.getExternalStorageDirectory()); - } - - private void init(File f) { - try { - p = Runtime.getRuntime().exec("/system/bin/sh"); - } catch (IOException e) { - p = null; - return; - } - - this.file = f; - - output = p.getInputStream(); - error = p.getErrorStream(); - cmdStream = p.getOutputStream(); - - new Thread() { - @Override - public void run() { - super.run(); - - cd(file); - } - }.start(); - } - - public String cmd(final String cmd, final boolean write) { - - if(t != null && t.isAlive()) { - try { - synchronized (t) { - t.wait(); - } - } catch (InterruptedException e) {} - } - - try { - cmdStream.write((cmd + Tuils.NEWLINE).getBytes()); - cmdStream.write(("echo EOF" + Tuils.NEWLINE).getBytes()); - } catch (IOException e) { - return null; - } - - final String[] outputMsg = {Tuils.EMPTYSTRING}; - - t = new StoppableThread() { - String line = Tuils.EMPTYSTRING; - String errorMsg = Tuils.EMPTYSTRING; - - @Override - public void run() { - super.run(); - - do { - if(Thread.currentThread().isInterrupted()) return; - - int n = 0; - try { - n = output.read(); - } catch (IOException e) {} - - if(n == -1) continue; - - char c = (char) n; - if(c == '\n') { - if(line.equals("EOF")) break; - else { - if(write) outputable.onOutput(line); - outputMsg[0] = outputMsg[0] + Tuils.EMPTYSTRING + line; - line = Tuils.EMPTYSTRING; - } - } else { - line = line + c; - } - } while (true); - - if(write) { - try { - int available = error.available(); - for (int i = 0; i < available; i++) errorMsg = errorMsg + (char) error.read(); - } catch (IOException e) {} - - if(Thread.currentThread().isInterrupted()) return; - - outputable.onOutput(errorMsg); - } - - - if(cmd.startsWith("cd ")) { - line = Tuils.EMPTYSTRING; - -// update the current dir - try { - cmdStream.write(("pwd" + Tuils.NEWLINE).getBytes()); - } catch (IOException e) {} - - do { - int n = 0; - try { - n = output.read(); - } catch (IOException e) {} - - if(n == -1) continue; - char c = (char) n; - - if(c == '\n') { - file = new File(line); - break; - } else { - line = line + c; - } - } while (true); - } - - synchronized (outputMsg) { - outputMsg.notify(); - } - - synchronized (this) { - this.notify(); - } - } - }; - - t.start(); - - try { - synchronized (outputMsg) { - outputMsg.wait(); - } - } catch (InterruptedException e) {} - - return outputMsg[0]; - } - - public void cd(File file) { - cmd("cd" + Tuils.SPACE + file.getAbsolutePath(), false); - } - - public File currentDir() { - return file == null ? Environment.getExternalStorageDirectory() : file; - } - - public void reset() { - - if(this.p != null) { - p.destroy(); - p = null; - - try { - output.close(); - error.close(); - cmdStream.close(); - } catch (IOException e) {} - - output = null; - error = null; - cmdStream = null; - } - - if(this.t != null) { - t.interrupt(); - t = null; - } - - init(this.file); - } - - public void destroy() { - if(t != null) t.interrupt(); - if(p != null) { - p.destroy(); - - try { - output.close(); - error.close(); - cmdStream.close(); - } catch (IOException e) {} - } - } - -// public boolean sendSigint() { -// -// if(t != null) { -// t.interrupt(); -// -// int pid = getPid(); -// if(pid != 0) { -// -// try { -// p.destroy(); -// Runtime.getRuntime().exec("kill -SIGINT" + Tuils.SPACE + pid); -// } catch (IOException e) {} -// } -// -// try { -// int av = output.available(); -// for(int i = 0; i < av; i++) { -// output.read(); -// } -// } catch (IOException e) {} -// -// try { -// int av = error.available(); -// for(int i = 0; i < av; i++) { -// error.read(); -// } -// } catch (IOException e) {} -// -// return true; -// } -// return false; -// } -// -// public int getPid() { -// String s = p.toString(); -// String sPid = s.replaceAll("Process", Tuils.EMPTYSTRING).replace("[", Tuils.EMPTYSTRING).replace("]", Tuils.EMPTYSTRING).replaceAll("pid=", Tuils.EMPTYSTRING); -// -// try { -// return Integer.parseInt(sPid); -// } catch (Exception e) { -// return 0; -// } -// } -} diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/TerminalMAnager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/TerminalMAnager.java index 80bf0b843dd4ad02dcbdfb361cfe6a39c683f254..457685b3595d78f1c2564cd03971c32d4f855cd6 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/managers/TerminalMAnager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/managers/TerminalMAnager.java @@ -1,5 +1,6 @@ package ohi.andre.consolelauncher.managers; +import android.app.Activity; import android.content.Context; import android.graphics.Typeface; import android.os.IBinder; @@ -27,6 +28,7 @@ import ohi.andre.consolelauncher.commands.main.MainPack; import ohi.andre.consolelauncher.commands.main.raw.clear; import ohi.andre.consolelauncher.tuils.TimeManager; import ohi.andre.consolelauncher.tuils.Tuils; +import ohi.andre.consolelauncher.tuils.interfaces.Rooter; /*Copyright Francesco Andreuzzi @@ -61,6 +63,9 @@ public class TerminalManager { private TextView mTerminalView; private EditText mInputView; + private TextView mPrefix; + private boolean suMode; + private List<String> cmdList = new ArrayList<>(CMD_LIST_SIZE); private int howBack = -1; @@ -98,11 +103,15 @@ public class TerminalManager { private String inputFormat; private String outputFormat; + private Context mContext; + public TerminalManager(final TextView terminalView, EditText inputView, TextView prefixView, ImageButton submitView, final ImageButton backView, ImageButton nextView, ImageButton deleteView, ImageButton pasteView, SkinManager skinManager, final Context context, MainPack mainPack) { if (terminalView == null || inputView == null || prefixView == null || skinManager == null) throw new UnsupportedOperationException(); + this.mContext = context; + final Typeface lucidaConsole = Typeface.createFromAsset(context.getAssets(), "lucida_console.ttf"); this.mSkinManager = skinManager; @@ -122,6 +131,7 @@ public class TerminalManager { prefixView.setTextColor(this.mSkinManager.inputColor); prefixView.setTextSize(this.mSkinManager.getTextSize()); prefixView.setText(prefix.endsWith(Tuils.SPACE) ? prefix : prefix + Tuils.SPACE); + this.mPrefix = prefixView; if (submitView != null) { submitView.setColorFilter(mSkinManager.enter_color); @@ -369,7 +379,7 @@ public class TerminalManager { case CATEGORY_INPUT: t = t.toString().trim(); - boolean su = t.toString().startsWith("su "); + boolean su = t.toString().startsWith("su ") || suMode; SpannableString si = new SpannableString(inputFormat); si.setSpan(new ForegroundColorSpan(mSkinManager.inputColor), 0, inputFormat.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -474,6 +484,32 @@ public class TerminalManager { clearCmdsCount = 0; } + public Rooter getRooter() { + return new Rooter() { + @Override + public void onRoot() { + ((Activity) mContext).runOnUiThread(new Runnable() { + @Override + public void run() { + suMode = true; + mPrefix.setText(suPrefix.endsWith(Tuils.SPACE) ? suPrefix : suPrefix + Tuils.SPACE); + } + }); + } + + @Override + public void onStandard() { + ((Activity) mContext).runOnUiThread(new Runnable() { + @Override + public void run() { + suMode = false; + mPrefix.setText(prefix.endsWith(Tuils.SPACE) ? prefix : prefix + Tuils.SPACE); + } + }); + } + }; + } + public static class Messager { int n; diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/XMLPrefsManager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/XMLPrefsManager.java index ce03da4b451f9cfcf0902908977650ec3e41110f..4e4a19e0ba6434b4687559c6effe6b564ff60479 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/managers/XMLPrefsManager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/managers/XMLPrefsManager.java @@ -433,6 +433,18 @@ public class XMLPrefsManager { public String defaultValue() { return "#03A9F4"; } + }, + suggest_alias_default { + @Override + public String defaultValue() { + return "true"; + } + }, + click_to_launch { + @Override + public String defaultValue() { + return "true"; + } }; @Override @@ -524,12 +536,6 @@ public class XMLPrefsManager { return "true"; } }, - suggest_alias_default { - @Override - public String defaultValue() { - return "true"; - } - }, clear_after_cmds { @Override public String defaultValue() { diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/music/MusicManager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/music/MusicManager.java index 316e0a8f678c85f7e2a906c449d33ebb29e7e94f..1e9a4ac7667f215d949365d9d9b23244d51819c8 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/managers/music/MusicManager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/managers/music/MusicManager.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import ohi.andre.comparestring.Compare; import ohi.andre.consolelauncher.managers.XMLPrefsManager; import ohi.andre.consolelauncher.tuils.Tuils; import ohi.andre.consolelauncher.tuils.broadcast.HeadsetBroadcast; @@ -22,8 +21,6 @@ public class MusicManager implements OnCompletionListener { public static final String[] MUSIC_EXTENSIONS = {".mp3", ".wav", ".ogg", ".flac"}; - public static final boolean USE_SCROLL_COMPARE = true; - private List<File> files; private MediaPlayer mp; @@ -78,9 +75,9 @@ public class MusicManager implements OnCompletionListener { } // return a song by incomplete name - public String getSong(String s, int minRate) { - return Compare.similarString(getNames(), s, minRate, USE_SCROLL_COMPARE); - } +// public String getSong(String s) { +// return s; +// } // return the path by complete name public String getPath(String name) { diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/suggestions/SuggestionsManager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/suggestions/SuggestionsManager.java index 9b740f438a9b0baae14fa495260f89b2f96315e2..7cb42704d0713cb24a0b9f63e7b1ab3be3800803 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/managers/suggestions/SuggestionsManager.java +++ b/app/src/main/java/ohi/andre/consolelauncher/managers/suggestions/SuggestionsManager.java @@ -6,7 +6,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import ohi.andre.comparestring.Compare; import ohi.andre.consolelauncher.commands.Command; import ohi.andre.consolelauncher.commands.CommandAbstraction; import ohi.andre.consolelauncher.commands.CommandTuils; @@ -18,9 +17,10 @@ import ohi.andre.consolelauncher.managers.AliasManager; import ohi.andre.consolelauncher.managers.AppsManager; import ohi.andre.consolelauncher.managers.ContactManager; import ohi.andre.consolelauncher.managers.FileManager; -import ohi.andre.consolelauncher.managers.music.MusicManager; import ohi.andre.consolelauncher.managers.XMLPrefsManager; import ohi.andre.consolelauncher.managers.notifications.NotificationManager; +import ohi.andre.consolelauncher.tuils.Compare; +import ohi.andre.consolelauncher.tuils.SimpleMutableEntry; import ohi.andre.consolelauncher.tuils.Tuils; import static ohi.andre.consolelauncher.commands.CommandTuils.xmlPrefsEntrys; @@ -33,25 +33,26 @@ public class SuggestionsManager { private final int MIN_COMMAND_PRIORITY = 5; - private int min_command_rate = 4; - private int min_apps_rate = 4; - private int min_contacts_rate = 4; - private int min_file_rate = 2; - private int min_songs_rate = 4; +// private int min_command_rate = 4; +// private int min_apps_rate = 2; +// private int min_contacts_rate = 2; +// private int min_file_rate = 2; +// private int min_songs_rate = 2; // use to place something at the top private final int MAX_RATE = 100; private final int NO_RATE = -1; - private final int FIRST_INTERVAL = 7; + private final int FIRST_INTERVAL = 6; - private boolean showAliasDefault, showAliasWasSet = false; + private boolean showAliasDefault, set = false, clickToLaunch; public Suggestion[] getSuggestions(MainPack info, String before, String lastWord) { - if(!showAliasWasSet) { - showAliasDefault = XMLPrefsManager.get(boolean.class, XMLPrefsManager.Behavior.suggest_alias_default); - showAliasWasSet = true; + if(!set) { + showAliasDefault = XMLPrefsManager.get(boolean.class, XMLPrefsManager.Suggestions.suggest_alias_default); + set = true; + clickToLaunch = XMLPrefsManager.get(boolean.class, XMLPrefsManager.Suggestions.click_to_launch); } List<Suggestion> suggestionList = new ArrayList<>(); @@ -62,6 +63,7 @@ public class SuggestionsManager { // lastword = 0 if (lastWord.length() == 0) { // lastword = 0 && before = 0 + if (before.length() == 0) { String[] apps = info.appsManager.getSuggestedApps(); if (apps != null) { @@ -72,7 +74,7 @@ public class SuggestionsManager { float shift = count + 1; float rate = 1f / shift; - suggestionList.add(new Suggestion(before, apps[count], true, (int) Math.ceil(rate), Suggestion.TYPE_APP)); + suggestionList.add(new Suggestion(before, apps[count], clickToLaunch, (int) Math.ceil(rate), Suggestion.TYPE_APP)); } } @@ -80,6 +82,7 @@ public class SuggestionsManager { return suggestionList.toArray(new Suggestion[suggestionList.size()]); } + // lastword == 0 && before > 0 else { // check if this is a command @@ -111,15 +114,31 @@ public class SuggestionsManager { else suggestArgs(info, cmd.nextArg(), suggestionList, before); } else { -// >>word -// not a command -// ==> app - suggestApp(info, suggestionList, before, Tuils.EMPTYSTRING); + + String[] split = before.replaceAll("['\"]", Tuils.EMPTYSTRING).split(Tuils.SPACE); + boolean isShellCmd = false; + for(String s : split) { + if(needsFileSuggestion(s)) { + isShellCmd = true; + break; + } + } + + if(isShellCmd) { + suggestFile(info, suggestionList, Tuils.EMPTYSTRING, before); + } else { +// ==> app + suggestApp(info, suggestionList, before, Tuils.EMPTYSTRING); + } + } } } + + // lastWord > 0 else { + if (before.length() > 0) { // lastword > 0 && before > 0 Command cmd = null; @@ -142,10 +161,23 @@ public class SuggestionsManager { suggestParams(info, suggestionList, (ParamCommand) cmd.cmd, before, lastWord); } else suggestArgs(info, cmd.nextArg(), suggestionList, lastWord, before); } else { -// not a command -// ==> app - suggestApp(info, suggestionList, before + lastWord, Tuils.EMPTYSTRING); + + String[] split = before.replaceAll("['\"]", Tuils.EMPTYSTRING).split(Tuils.SPACE); + boolean isShellCmd = false; + for(String s : split) { + if(needsFileSuggestion(s)) { + isShellCmd = true; + break; + } + } + + if(isShellCmd) { + suggestFile(info, suggestionList, lastWord, before); + } else { + suggestApp(info, suggestionList, before + lastWord, Tuils.EMPTYSTRING); + } } + } else { // lastword > 0 && before = 0 suggestCommand(info, suggestionList, lastWord, before); @@ -159,6 +191,10 @@ public class SuggestionsManager { return suggestionList.toArray(array); } + private boolean needsFileSuggestion(String cmd) { + return cmd.equalsIgnoreCase("ls") || cmd.equalsIgnoreCase("cd") || cmd.equalsIgnoreCase("mv") || cmd.equalsIgnoreCase("cp") || cmd.equalsIgnoreCase("rm"); + } + private void suggestPermanentSuggestions(List<Suggestion> suggestions, PermanentSuggestionCommand cmd) { for(String s : cmd.permanentSuggestions()) { Suggestion sugg = new Suggestion(null, s, false, NO_RATE, Suggestion.TYPE_PERMANENT); @@ -167,8 +203,8 @@ public class SuggestionsManager { } private void suggestAlias(AliasManager aliasManager, List<Suggestion> suggestions, String lastWord) { - if(lastWord.length() == 0) for(String s : aliasManager.getAliases()) suggestions.add(new Suggestion(Tuils.EMPTYSTRING, s, true, NO_RATE, Suggestion.TYPE_ALIAS)); - else for(String s : aliasManager.getAliases()) if(s.startsWith(lastWord)) suggestions.add(new Suggestion(Tuils.EMPTYSTRING, s, true, NO_RATE, Suggestion.TYPE_ALIAS)); + if(lastWord.length() == 0) for(String s : aliasManager.getAliases()) suggestions.add(new Suggestion(Tuils.EMPTYSTRING, s, clickToLaunch, NO_RATE, Suggestion.TYPE_ALIAS)); + else for(String s : aliasManager.getAliases()) if(s.startsWith(lastWord)) suggestions.add(new Suggestion(Tuils.EMPTYSTRING, s, clickToLaunch, NO_RATE, Suggestion.TYPE_ALIAS)); } private void suggestParams(MainPack pack, List<Suggestion> suggestions, ParamCommand cmd, String before, String lastWord) { @@ -182,7 +218,7 @@ public class SuggestionsManager { Param p = cmd.getParam(pack, s).getValue(); if(p == null) continue; - suggestions.add(new Suggestion(before, s, p.args().length == 0, NO_RATE, 0)); + suggestions.add(new Suggestion(before, s, p.args().length == 0 && clickToLaunch, NO_RATE, 0)); } } else { @@ -191,7 +227,7 @@ public class SuggestionsManager { if(p == null) continue; if (s.startsWith(lastWord) || s.replace("-", Tuils.EMPTYSTRING).startsWith(lastWord)) { - suggestions.add(new Suggestion(before, s, p.args().length == 0, NO_RATE, 0)); + suggestions.add(new Suggestion(before, s, p.args().length == 0 && clickToLaunch, NO_RATE, 0)); } } } @@ -232,6 +268,10 @@ public class SuggestionsManager { break; case CommandAbstraction.DEFAULT_APP: suggestDefaultApp(info, suggestions, prev, before); + break; + case CommandAbstraction.ALL_PACKAGES: + suggestAllPackages(info, suggestions, prev, before); + break; } } @@ -240,8 +280,8 @@ public class SuggestionsManager { } private void suggestBoolean(List<Suggestion> suggestions, String before) { - suggestions.add(new Suggestion(before, "true", true, NO_RATE, Suggestion.TYPE_BOOLEAN)); - suggestions.add(new Suggestion(before, "false", true, NO_RATE, Suggestion.TYPE_BOOLEAN)); + suggestions.add(new Suggestion(before, "true", clickToLaunch, NO_RATE, Suggestion.TYPE_BOOLEAN)); + suggestions.add(new Suggestion(before, "false", clickToLaunch, NO_RATE, Suggestion.TYPE_BOOLEAN)); } private void suggestFile(MainPack info, List<Suggestion> suggestions, String prev, String before) { @@ -290,12 +330,9 @@ public class SuggestionsManager { if(files == null) { return; } - Arrays.sort(files); - List<Compare.CompareInfo> infos = Compare.compareInfo(files, prev, min_file_rate, - FileManager.USE_SCROLL_COMPARE); - for(Compare.CompareInfo i : infos) { - suggestions.add(new Suggestion(before, i.s, false, i.rate, Suggestion.TYPE_FILE)); + for(SimpleMutableEntry<String, Integer> s : Compare.matchesWithRate(files, prev, false)) { + suggestions.add(new Suggestion(before, s.getKey(), false, s.getValue(), Suggestion.TYPE_FILE)); } } @@ -322,20 +359,22 @@ public class SuggestionsManager { suggestions.add(new Suggestion(before, contact.name, true, NO_RATE, Suggestion.TYPE_CONTACT, contact)); } - else if(prev.length() <= FIRST_INTERVAL) { - prev = prev.trim().toLowerCase(); - - for (ContactManager.Contact contact : info.contacts.getContacts()) - if(contact.name.toLowerCase().trim().startsWith(prev)) { - suggestions.add(new Suggestion(before, contact.name, true, NO_RATE, Suggestion.TYPE_CONTACT, contact)); - } - } +// else if(prev.length() <= FIRST_INTERVAL) { +// prev = prev.trim().toLowerCase(); +// +// for (ContactManager.Contact contact : info.contacts.getContacts()) +// if(contact.name.toLowerCase().trim().startsWith(prev)) { +// suggestions.add(new Suggestion(before, contact.name, true, NO_RATE, Suggestion.TYPE_CONTACT, contact)); +// } +// } else { for(ContactManager.Contact contact : info.contacts.getContacts()) { - int rate = ContactManager.USE_SCROLL_COMPARE ? Compare.scrollComparison(contact.name, prev) : Compare.linearComparison(contact.name, prev); - if(rate >= min_contacts_rate) { - suggestions.add(new Suggestion(before, contact.name, true, NO_RATE, Suggestion.TYPE_CONTACT, contact)); + if(Thread.currentThread().isInterrupted()) return; + + int rate = Compare.matches(contact.name, prev, true); + if(rate != -1) { + suggestions.add(new Suggestion(before, contact.name, true, rate, Suggestion.TYPE_CONTACT, contact)); } } } @@ -344,20 +383,21 @@ public class SuggestionsManager { private void suggestSong(MainPack info, List<Suggestion> suggestions, String prev, String before) { if (prev == null || prev.length() == 0) { for (String s : info.player.getNames()) - suggestions.add(new Suggestion(before, s, true, NO_RATE, Suggestion.TYPE_SONG)); - } else if(prev.length() <= FIRST_INTERVAL) { - prev = prev.trim().toLowerCase(); - List<String> names = info.player.getNames(); - for (String n : names) { - if(n.toLowerCase().trim().startsWith(prev)) { - suggestions.add(new Suggestion(before, n, true, MAX_RATE, Suggestion.TYPE_SONG)); - } - } - } else { - List<Compare.CompareInfo> infos = Compare.compareInfo(info.player.getNames(), prev, min_songs_rate, - MusicManager.USE_SCROLL_COMPARE); - for(Compare.CompareInfo i : infos) { - suggestions.add(new Suggestion(before, i.s, true, i.rate, Suggestion.TYPE_SONG)); + suggestions.add(new Suggestion(before, s, clickToLaunch, NO_RATE, Suggestion.TYPE_SONG)); + } +// else if(prev.length() <= FIRST_INTERVAL) { +// prev = prev.trim().toLowerCase(); +// List<String> names = info.player.getNames(); +// for (String n : names) { +// if(n.toLowerCase().trim().startsWith(prev)) { +// suggestions.add(new Suggestion(before, n, true, MAX_RATE, Suggestion.TYPE_SONG)); +// } +// } +// } + else { + List<SimpleMutableEntry<String, Integer>> infos = Compare.matchesWithRate(info.player.getNames(), prev, true); + for(SimpleMutableEntry<String, Integer> i : infos) { + suggestions.add(new Suggestion(before, i.getKey(), clickToLaunch, i.getValue(), Suggestion.TYPE_SONG)); } } } @@ -372,22 +412,15 @@ public class SuggestionsManager { prev = prev.toLowerCase().trim(); String[] cmds = info.commandGroup.getCommandNames(); for (String s : cmds) { + if(Thread.currentThread().isInterrupted()) return; + if(s.startsWith(prev)) { CommandAbstraction cmd = info.commandGroup.getCommandByName(s); int[] args = cmd.argType(); boolean exec = args == null || args.length == 0; - suggestions.add(new Suggestion(before, s, exec, MAX_RATE, Suggestion.TYPE_COMMAND)); + suggestions.add(new Suggestion(before, s, exec && clickToLaunch, MAX_RATE, Suggestion.TYPE_COMMAND)); } } - return; - } - - List<Compare.CompareInfo> infos = Compare.compareInfo(info.commandGroup.getCommandNames(), prev, min_command_rate, false); - for(Compare.CompareInfo i : infos) { - CommandAbstraction cmd = info.commandGroup.getCommandByName(i.s); - int[] args = cmd.argType(); - boolean exec = args == null || args.length == 0; - suggestions.add(new Suggestion(before, i.s, exec, i.rate, Suggestion.TYPE_COMMAND)); } } @@ -399,11 +432,13 @@ public class SuggestionsManager { private void suggestCommand(MainPack info, List<Suggestion> suggestions, String before) { for (String s : info.commandGroup.getCommandNames()) { + if(Thread.currentThread().isInterrupted()) return; + CommandAbstraction cmd = info.commandGroup.getCommandByName(s); if (cmd != null && cmd.priority() >= MIN_COMMAND_PRIORITY) { int[] args = cmd.argType(); boolean exec = args == null || args.length == 0; - suggestions.add(new Suggestion(before, s, exec, cmd.priority(), Suggestion.TYPE_COMMAND)); + suggestions.add(new Suggestion(before, s, exec && clickToLaunch, cmd.priority(), Suggestion.TYPE_COMMAND)); } } } @@ -412,20 +447,21 @@ public class SuggestionsManager { List<String> names = info.appsManager.getAppLabels(); if (prev == null || prev.length() == 0) { for (String s : names) { - suggestions.add(new Suggestion(before, s, true, NO_RATE, Suggestion.TYPE_APP)); + suggestions.add(new Suggestion(before, s, clickToLaunch, NO_RATE, Suggestion.TYPE_APP)); } - } else if(prev.length() <= FIRST_INTERVAL) { - prev = prev.trim().toLowerCase(); - for (String n : names) { - if(n.toLowerCase().trim().startsWith(prev)) { - suggestions.add(new Suggestion(before, n, true, MAX_RATE, Suggestion.TYPE_APP)); - } - } - } else { - List<Compare.CompareInfo> infos = Compare.compareInfo(names, prev, min_apps_rate, - AppsManager.USE_SCROLL_COMPARE); - for(Compare.CompareInfo i : infos) { - suggestions.add(new Suggestion(before, i.s, true, i.rate, Suggestion.TYPE_APP)); + } +// else if(prev.length() <= FIRST_INTERVAL) { +// prev = prev.trim().toLowerCase(); +// for (String n : names) { +// if(n.toLowerCase().trim().startsWith(prev)) { +// suggestions.add(new Suggestion(before, n, true, MAX_RATE, Suggestion.TYPE_APP)); +// } +// } +// } + else { + List<SimpleMutableEntry<String, Integer>> infos = Compare.matchesWithRate(names, prev, true); + for(SimpleMutableEntry<String, Integer> i : infos) { + suggestions.add(new Suggestion(before, i.getKey(), clickToLaunch, i.getValue(), Suggestion.TYPE_APP)); } } } @@ -434,20 +470,37 @@ public class SuggestionsManager { List<String> names = info.appsManager.getHiddenAppsLabels(); if (prev == null || prev.length() == 0) { for (String s : names) { - suggestions.add(new Suggestion(before, s, true, NO_RATE, Suggestion.TYPE_APP)); + suggestions.add(new Suggestion(before, s, clickToLaunch, NO_RATE, Suggestion.TYPE_APP)); } - } else if(prev.length() <= FIRST_INTERVAL) { - prev = prev.trim().toLowerCase(); - for (String n : names) { - if(n.toLowerCase().trim().startsWith(prev)) { - suggestions.add(new Suggestion(before, n, true, MAX_RATE, Suggestion.TYPE_APP)); - } + } +// else if(prev.length() <= FIRST_INTERVAL) { +// prev = prev.trim().toLowerCase(); +// for (String n : names) { +// if(n.toLowerCase().trim().startsWith(prev)) { +// suggestions.add(new Suggestion(before, n, true, MAX_RATE, Suggestion.TYPE_APP)); +// } +// } +// } + else { + List<SimpleMutableEntry<String, Integer>> infos = Compare.matchesWithRate(names, prev, true); + for(SimpleMutableEntry<String, Integer> i : infos) { + suggestions.add(new Suggestion(before, i.getKey(), clickToLaunch, i.getValue(), Suggestion.TYPE_APP)); + } + } + } + + private void suggestAllPackages(MainPack info, List<Suggestion> suggestions, String prev, String before) { + List<String> apps = info.appsManager.getHiddenAppsLabels(); + apps.addAll(info.appsManager.getAppLabels()); + + if (prev == null || prev.length() == 0) { + for (String s : apps) { + suggestions.add(new Suggestion(before, s, clickToLaunch, NO_RATE, Suggestion.TYPE_APP)); } } else { - List<Compare.CompareInfo> infos = Compare.compareInfo(names, prev, min_apps_rate, - AppsManager.USE_SCROLL_COMPARE); - for(Compare.CompareInfo i : infos) { - suggestions.add(new Suggestion(before, i.s, true, i.rate, Suggestion.TYPE_APP)); + List<SimpleMutableEntry<String, Integer>> infos = Compare.matchesWithRate(apps, prev, true); + for(SimpleMutableEntry<String, Integer> i : infos) { + suggestions.add(new Suggestion(before, i.getKey(), clickToLaunch, i.getValue(), Suggestion.TYPE_APP)); } } } @@ -459,20 +512,21 @@ public class SuggestionsManager { List<String> names = info.appsManager.getAppLabels(); if (prev == null || prev.length() == 0) { for (String s : names) { - suggestions.add(new Suggestion(before, s, true, NO_RATE, Suggestion.TYPE_APP)); + suggestions.add(new Suggestion(before, s, clickToLaunch, NO_RATE, Suggestion.TYPE_APP)); } - } else if(prev.length() <= FIRST_INTERVAL) { - prev = prev.trim().toLowerCase(); - for (String n : names) { - if(n.toLowerCase().trim().startsWith(prev)) { - suggestions.add(new Suggestion(before, n, true, MAX_RATE, Suggestion.TYPE_APP)); - } - } - } else { - List<Compare.CompareInfo> infos = Compare.compareInfo(names, prev, min_apps_rate, - AppsManager.USE_SCROLL_COMPARE); - for(Compare.CompareInfo i : infos) { - suggestions.add(new Suggestion(before, i.s, true, i.rate, Suggestion.TYPE_APP)); + } +// else if(prev.length() <= FIRST_INTERVAL) { +// prev = prev.trim().toLowerCase(); +// for (String n : names) { +// if(n.toLowerCase().trim().startsWith(prev)) { +// suggestions.add(new Suggestion(before, n, true, MAX_RATE, Suggestion.TYPE_APP)); +// } +// } +// } + else { + List<SimpleMutableEntry<String, Integer>> infos = Compare.matchesWithRate(names, prev, true); + for(SimpleMutableEntry<String, Integer> i : infos) { + suggestions.add(new Suggestion(before, i.getKey(), clickToLaunch, i.getValue(), Suggestion.TYPE_APP)); } } } @@ -480,12 +534,11 @@ public class SuggestionsManager { private void suggestConfigEntry(List<Suggestion> suggestions, String prev, String before) { if(xmlPrefsEntrys == null) { xmlPrefsEntrys = new ArrayList<>(); - for(XMLPrefsManager.XMLPrefsRoot element : XMLPrefsManager.XMLPrefsRoot.values()) { - for(XMLPrefsManager.XMLPrefsSave save : element.copy) - xmlPrefsEntrys.add(save); - } - for(XMLPrefsManager.XMLPrefsSave save : AppsManager.Options.values()) xmlPrefsEntrys.add(save); - for(XMLPrefsManager.XMLPrefsSave save : NotificationManager.Options.values()) xmlPrefsEntrys.add(save); + + for(XMLPrefsManager.XMLPrefsRoot element : XMLPrefsManager.XMLPrefsRoot.values()) xmlPrefsEntrys.addAll(element.copy); + + Collections.addAll(xmlPrefsEntrys, AppsManager.Options.values()); + Collections.addAll(xmlPrefsEntrys, NotificationManager.Options.values()); } if(prev == null || prev.length() == 0) { @@ -493,12 +546,24 @@ public class SuggestionsManager { Suggestion sg = new Suggestion(before, s.label(), false, NO_RATE, Suggestion.TYPE_COMMAND); suggestions.add(sg); } - } else if(prev.length() <= FIRST_INTERVAL) { - prev = prev.trim().toLowerCase(); + } +// else if(prev.length() <= FIRST_INTERVAL) { +// prev = prev.trim().toLowerCase(); +// for (XMLPrefsManager.XMLPrefsSave s : xmlPrefsEntrys) { +// String label = s.label(); +// if(label.startsWith(prev)) { +// suggestions.add(new Suggestion(before, label, false, MAX_RATE, Suggestion.TYPE_COMMAND)); +// } +// } +// } + else { for (XMLPrefsManager.XMLPrefsSave s : xmlPrefsEntrys) { + if(Thread.currentThread().isInterrupted()) return; + String label = s.label(); - if(label.startsWith(prev)) { - suggestions.add(new Suggestion(before, label, false, MAX_RATE, Suggestion.TYPE_COMMAND)); + int rate = Compare.matches(label, prev, true); + if(rate != -1) { + suggestions.add(new Suggestion(before, label, false, rate, Suggestion.TYPE_COMMAND)); } } } @@ -521,6 +586,8 @@ public class SuggestionsManager { } else if(prev.length() <= FIRST_INTERVAL) { prev = prev.trim().toLowerCase(); for (String s : xmlPrefsFiles) { + if(Thread.currentThread().isInterrupted()) return; + if(s.startsWith(prev)) { suggestions.add(new Suggestion(before, s, false, MAX_RATE, Suggestion.TYPE_FILE)); } diff --git a/app/src/main/java/ohi/andre/consolelauncher/tuils/Compare.java b/app/src/main/java/ohi/andre/consolelauncher/tuils/Compare.java index 74e34dcc972ef05e3646ef5aa285c31792eb9931..6f6849d63943527b953ef349e7c7fc6a1a1d7cd8 100644 --- a/app/src/main/java/ohi/andre/consolelauncher/tuils/Compare.java +++ b/app/src/main/java/ohi/andre/consolelauncher/tuils/Compare.java @@ -25,9 +25,9 @@ public class Compare { return s; } - public static boolean matches(String compared, String comparator, boolean allowSkip, int minRate) { - compared = removeAccents(compared); - comparator = removeAccents(comparator); + public static int matches(String compared, String comparator, boolean allowSkip) { + compared = removeAccents(compared).toLowerCase().trim(); + comparator = removeAccents(comparator).toLowerCase().trim(); List<String> s = new ArrayList<>(); if(allowSkip) { @@ -38,30 +38,60 @@ public class Compare { } s.add(compared); + float maxRate = -1; for(String st : s) { - int rate = 0; + float rate = 0; for(int i = 0; i < st.length() && i < comparator.length(); i++) { char c1 = st.charAt(i); char c2 = comparator.charAt(i); - if(c1 == c2) rate++; + if(c1 == c2) { + rate += (double) (st.length() - i) / (double) st.length(); + } } - if(rate >= minRate) return true; + if(rate >= (double) comparator.length() / 2d) maxRate = Math.max(maxRate, rate); } - return false; + return Math.round(maxRate); } - public static List<String> matches(List<String> compared, String comparator, boolean allowSkip, int minRate) { - List<String> ms = new ArrayList<>(); + public static List<String> matches(List<String> compared, String comparator, boolean allowSkip) { + List<SimpleMutableEntry<String, Integer>> ms = matchesWithRate(compared, comparator, allowSkip); + + List<String> result = new ArrayList<>(ms.size()); + for(SimpleMutableEntry<String, Integer> e : ms) { + result.add(e.getKey()); + } + + return result; + } + + public static List<String> matches(String[] compared, String comparator, boolean allowSkip) { + return matches(Arrays.asList(compared), comparator, allowSkip); + } + + public static List<SimpleMutableEntry<String, Integer>> matchesWithRate(List<String> compared, String comparator, boolean allowSkip) { + List<SimpleMutableEntry<String, Integer>> ms = new ArrayList<>(); for(String s : compared) { - if(matches(s, comparator, allowSkip, minRate)) { - ms.add(s); - } + if(Thread.currentThread().isInterrupted()) return ms; + + int rate = matches(s, comparator, allowSkip); + if(rate != -1) ms.add(new SimpleMutableEntry<>(s, rate)); } +// Collections.sort(ms, new Comparator<SimpleMutableEntry<String, Integer>>() { +// @Override +// public int compare(SimpleMutableEntry<String, Integer> o1, SimpleMutableEntry<String, Integer> o2) { +// return o1.getValue() - o2.getValue(); +// } +// }); + return ms; } + + public static List<SimpleMutableEntry<String, Integer>> matchesWithRate(String[] compared, String comparator, boolean allowSkip) { + return matchesWithRate(Arrays.asList(compared), comparator, allowSkip); + } } diff --git a/app/src/main/java/ohi/andre/consolelauncher/tuils/ShellUtils.java b/app/src/main/java/ohi/andre/consolelauncher/tuils/ShellUtils.java deleted file mode 100755 index 89b9b7fce882a4c28b3fe14255f5b3a7f6c8cc8b..0000000000000000000000000000000000000000 --- a/app/src/main/java/ohi/andre/consolelauncher/tuils/ShellUtils.java +++ /dev/null @@ -1,150 +0,0 @@ -package ohi.andre.consolelauncher.tuils; - -import android.util.Log; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.InterruptedIOException; - -import ohi.andre.consolelauncher.tuils.interfaces.Outputable; - -/** - * Created by francescoandreuzzi on 24/04/2017. - */ - -public class ShellUtils { - - public static class CommandResult { - public int result; - public String msg; - - public CommandResult(int exit, String msg) { - this.result = exit; - this.msg = msg; - } - - @Override - public String toString() { - return msg; - } - } - - public static CommandResult execCommand(String cmd , boolean root, String path) { - return execCommand(new String[] {cmd}, root, path, null); - } - - public static CommandResult execCommand(String[] cmd , boolean root, String path) { - return execCommand(cmd, root, path, null); - } - - public static CommandResult execCommand(String cmd , boolean root, String path, Outputable outputable) { - return execCommand(new String[] {cmd}, root, path, outputable); - } - -// custom dir doesnt work, and also multiple commands - public static CommandResult execCommand(String[] cmds , boolean root, String path, final Outputable outputable) { -// try { -// Process process = Runtime.getRuntime().exec(cmds[0]); -// process.waitFor(); -// -// BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); -// -// StringBuilder log = new StringBuilder(); -// String line; -// while ((line = bufferedReader.readLine()) != null) { -// Log.e("andre", "uh"); -// log.append(line + "\n"); -// } -// process.destroy(); -// -// return new CommandResult(0, log.toString()); -// } catch (Exception e) { -// return new CommandResult(0, e.toString()); -// } - - if(cmds.length > 1 || cmds.length == 0) return null; - - int result = -1; - - BufferedReader errorResult = null; - StringBuilder errorMsg = null; - final StringBuilder output = new StringBuilder(); - - try { - - final Process process = Runtime.getRuntime().exec((root ? "su -c " : "") + cmds[0], null, path != null ? new File(path) : null); - final Thread externalThread = Thread.currentThread(); - - Thread readerThread = new StoppableThread() { - - @Override - public void run() { - super.run(); - - if (Thread.interrupted() || externalThread.isInterrupted()) return; - - BufferedReader successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); - String s; - try { - while ((s = successResult.readLine()) != null) { - if (Thread.currentThread().isInterrupted() || externalThread.isInterrupted()) - return; - - if (outputable != null) outputable.onOutput(Tuils.NEWLINE + s); - else { - output.append(Tuils.NEWLINE); - output.append(s); - } - } - - sleep(25); - } catch (StackOverflowError | Exception e) { - if(outputable != null && ! (e instanceof InterruptedException)) outputable.onOutput(e.toString()); - - try { - successResult.close(); - } catch (IOException e1) {} - - return; - } - } - }; - readerThread.start(); - - result = process.waitFor(); - readerThread.interrupt(); - - if(output.length() == 0) { - errorMsg = new StringBuilder(); - - errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); - - String s; - while ((s = errorResult.readLine()) != null) { - if(errorMsg.length() > 0) errorMsg.append(Tuils.NEWLINE); - errorMsg.append(s); - } - } - - process.destroy(); - } - catch (Exception e) {} - finally { - try { - if (errorResult != null) { - errorResult.close(); - } - } catch (IOException e) {} - } - - if(output.length() > 0) { - return new CommandResult(result, output.toString()); - } - else if(errorMsg != null) { - return new CommandResult(result, errorMsg.toString()); - } - return null; - } -} diff --git a/app/src/main/java/ohi/andre/consolelauncher/tuils/Tuils.java b/app/src/main/java/ohi/andre/consolelauncher/tuils/Tuils.java index 892ab32df68235fd2101b15855500e8ee6d5f8d5..a5ca000a3d2aed0fa05fe42cd1098507ecd6a107 100755 --- a/app/src/main/java/ohi/andre/consolelauncher/tuils/Tuils.java +++ b/app/src/main/java/ohi/andre/consolelauncher/tuils/Tuils.java @@ -30,13 +30,13 @@ import android.view.ViewGroup; import android.webkit.MimeTypeMap; import java.io.BufferedReader; -import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; import java.math.BigDecimal; @@ -49,9 +49,9 @@ import java.util.regex.Pattern; import dalvik.system.DexFile; import ohi.andre.consolelauncher.BuildConfig; -import ohi.andre.consolelauncher.managers.music.MusicManager; import ohi.andre.consolelauncher.managers.SkinManager; import ohi.andre.consolelauncher.managers.XMLPrefsManager; +import ohi.andre.consolelauncher.managers.music.MusicManager; import ohi.andre.consolelauncher.tuils.stuff.FakeLauncherActivity; public class Tuils { @@ -387,27 +387,6 @@ public class Tuils { return -1; } - public static boolean verifyRoot() { - Process p; - try { - p = Runtime.getRuntime().exec("su"); - - DataOutputStream os = new DataOutputStream(p.getOutputStream()); - os.writeBytes("echo \"root?\" >/system/sd/temporary.txt\n"); - - os.writeBytes("exit\n"); - os.flush(); - try { - p.waitFor(); - return p.exitValue() != 255; - } catch (InterruptedException e) { - return false; - } - } catch (IOException e) { - return false; - } - } - public static void insertHeaders(List<String> s, boolean newLine) { char current = 0; for (int count = 0; count < s.size(); count++) { @@ -466,6 +445,12 @@ public class Tuils { } } + public static void toFile(Throwable e) { + try { + e.printStackTrace(new PrintStream(new FileOutputStream(new File(Tuils.getFolder(), "crash.txt"), true))); + } catch (FileNotFoundException e1) {} + } + static FileOutputStream logStream = null; public static void openLogStream(String name) { closeStream(); @@ -756,4 +741,31 @@ public class Tuils { folder = tuiFolder; return folder; } + + public static int alphabeticCompare(String s1, String s2) { + String cmd1 = removeSpaces(s1); + cmd1 = cmd1.toLowerCase(); + String cmd2 = removeSpaces(s2); + cmd2 = cmd2.toLowerCase(); + + for (int count = 0; count < cmd1.length() && count < cmd2.length(); count++) { + if (cmd1.charAt(count) < cmd2.charAt(count)) { + return -1; + } else if (cmd1.charAt(count) > cmd2.charAt(count)) { + return 1; + } + } + + if (cmd1.length() > cmd2.length()) { + return 1; + } else if (cmd1.length() < cmd2.length()) { + return -1; + } + return 0; + } + + private static final String SPACE_REGEXP = "\\s"; + public static String removeSpaces(String string) { + return string.replaceAll(SPACE_REGEXP, EMPTYSTRING); + } } diff --git a/app/src/main/java/ohi/andre/consolelauncher/tuils/interfaces/Hintable.java b/app/src/main/java/ohi/andre/consolelauncher/tuils/interfaces/Hintable.java new file mode 100644 index 0000000000000000000000000000000000000000..0db778566bfbb6c4f8048383741b64db36503de8 --- /dev/null +++ b/app/src/main/java/ohi/andre/consolelauncher/tuils/interfaces/Hintable.java @@ -0,0 +1,9 @@ +package ohi.andre.consolelauncher.tuils.interfaces; + +/** + * Created by francescoandreuzzi on 31/07/2017. + */ + +public interface Hintable { + void updateHint(); +} diff --git a/app/src/main/java/ohi/andre/consolelauncher/tuils/interfaces/Rooter.java b/app/src/main/java/ohi/andre/consolelauncher/tuils/interfaces/Rooter.java new file mode 100644 index 0000000000000000000000000000000000000000..a104c5f64f5525a6e59d9fa269509f3b356f1fbf --- /dev/null +++ b/app/src/main/java/ohi/andre/consolelauncher/tuils/interfaces/Rooter.java @@ -0,0 +1,10 @@ +package ohi.andre.consolelauncher.tuils.interfaces; + +/** + * Created by francescoandreuzzi on 31/07/2017. + */ + +public interface Rooter { + void onRoot(); + void onStandard(); +} diff --git a/app/src/main/java/ohi/andre/consolelauncher/tuils/libsuperuser/Shell.java b/app/src/main/java/ohi/andre/consolelauncher/tuils/libsuperuser/Shell.java new file mode 100755 index 0000000000000000000000000000000000000000..929a589c4b85614cb960006f61b2b4a8bea512a6 --- /dev/null +++ b/app/src/main/java/ohi/andre/consolelauncher/tuils/libsuperuser/Shell.java @@ -0,0 +1,1747 @@ +/* + * Copyright (C) 2012-2015 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohi.andre.consolelauncher.tuils.libsuperuser; + +import android.os.Handler; +import android.os.Looper; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import ohi.andre.consolelauncher.tuils.Tuils; + +import static ohi.andre.consolelauncher.tuils.libsuperuser.StreamGobbler.*; + +/** + * Class providing functionality to execute commands in a (root) shell + */ +public class Shell { + /** + * <p> + * Runs commands using the supplied shell, and returns the output, or null + * in case of errors. + * </p> + * <p> + * This method is deprecated and only provided for backwards compatibility. + * Use {@link #run(String, String[], String[], boolean)} instead, and see + * that same method for usage notes. + * </p> + * + * @param shell The shell to use for executing the commands + * @param commands The commands to execute + * @param wantSTDERR Return STDERR in the output ? + * @return Output of the commands, or null in case of an error + */ + @Deprecated + public static List<String> run(String shell, String[] commands, boolean wantSTDERR) { + return run(shell, commands, null, wantSTDERR); + } + + /** + * <p> + * Runs commands using the supplied shell, and returns the output, or null + * in case of errors. + * </p> + * <p> + * Note that due to compatibility with older Android versions, wantSTDERR is + * not implemented using redirectErrorStream, but rather appended to the + * output. STDOUT and STDERR are thus not guaranteed to be in the correct + * order in the output. + * </p> + * <p> + * Note as well that this code will intentionally crash when run in debug + * mode from the main thread of the application. You should always execute + * shell commands from a background thread. + * </p> + * <p> + * When in debug mode, the code will also excessively log the commands + * passed to and the output returned from the shell. + * </p> + * <p> + * Though this function uses background threads to gobble STDOUT and STDERR + * so a deadlock does not occur if the shell produces massive output, the + * output is still stored in a List<String>, and as such doing + * something like <em>'ls -lR /'</em> will probably have you run out of + * memory. + * </p> + * + * @param shell The shell to use for executing the commands + * @param commands The commands to execute + * @param environment List of all environment variables (in 'key=value' + * format) or null for defaults + * @param wantSTDERR Return STDERR in the output ? + * @return Output of the commands, or null in case of an error + */ + public static List<String> run(String shell, String[] commands, String[] environment, + boolean wantSTDERR) { + String shellUpper = shell.toUpperCase(Locale.ENGLISH); + + List<String> res = Collections.synchronizedList(new ArrayList<String>()); + + try { + // Combine passed environment with system environment + if (environment != null) { + Map<String, String> newEnvironment = new HashMap<String, String>(); + newEnvironment.putAll(System.getenv()); + int split; + for (String entry : environment) { + if ((split = entry.indexOf("=")) >= 0) { + newEnvironment.put(entry.substring(0, split), entry.substring(split + 1)); + } + } + int i = 0; + environment = new String[newEnvironment.size()]; + for (Map.Entry<String, String> entry : newEnvironment.entrySet()) { + environment[i] = entry.getKey() + "=" + entry.getValue(); + i++; + } + } + + // setup our process, retrieve STDIN stream, and STDOUT/STDERR + // gobblers + Process process = Runtime.getRuntime().exec(shell, environment); + DataOutputStream STDIN = new DataOutputStream(process.getOutputStream()); + StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), + res); + StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), + wantSTDERR ? res : null); + + // start gobbling and write our commands to the shell + STDOUT.start(); + STDERR.start(); + try { + for (String write : commands) { + STDIN.write((write + "\n").getBytes("UTF-8")); + STDIN.flush(); + } + STDIN.write("exit\n".getBytes("UTF-8")); + STDIN.flush(); + } catch (IOException e) { + if (e.getMessage().contains("EPIPE") || e.getMessage().contains("Stream closed")) { + // Method most horrid to catch broken pipe, in which case we + // do nothing. The command is not a shell, the shell closed + // STDIN, the script already contained the exit command, etc. + // these cases we want the output instead of returning null. + } else { + // other issues we don't know how to handle, leads to + // returning null + throw e; + } + } + + // wait for our process to finish, while we gobble away in the + // background + process.waitFor(); + + // make sure our threads are done gobbling, our streams are closed, + // and the process is destroyed - while the latter two shouldn't be + // needed in theory, and may even produce warnings, in "normal" Java + // they are required for guaranteed cleanup of resources, so lets be + // safe and do this on Android as well + try { + STDIN.close(); + } catch (IOException e) { + // might be closed already + } + STDOUT.join(); + STDERR.join(); + process.destroy(); + + // in case of su, 255 usually indicates access denied + if (SU.isSU(shell) && (process.exitValue() == 255)) { + res = null; + } + } catch (IOException e) { + // shell probably not found + Tuils.log(e); + res = null; + } catch (InterruptedException e) { + // this should really be re-thrown + Tuils.log(e); + res = null; + } + + return res; + } + + protected static String[] availableTestCommands = new String[]{ + "echo -BOC-", + "id" + }; + + /** + * See if the shell is alive, and if so, check the UID + * + * @param ret Standard output from running availableTestCommands + * @param checkForRoot true if we are expecting this shell to be running as + * root + * @return true on success, false on error + */ + protected static boolean parseAvailableResult(List<String> ret, boolean checkForRoot) { + if (ret == null) + return false; + + // this is only one of many ways this can be done + boolean echo_seen = false; + + for (String line : ret) { + if (line.contains("uid=")) { + // id command is working, let's see if we are actually root + return !checkForRoot || line.contains("uid=0"); + } else if (line.contains("-BOC-")) { + // if we end up here, at least the su command starts some kind + // of shell, let's hope it has root privileges - no way to know without + // additional native binaries + echo_seen = true; + } + } + + return echo_seen; + } + + /** + * This class provides utility functions to easily execute commands using SH + */ + public static class SH { + /** + * Runs command and return output + * + * @param command The command to run + * @return Output of the command, or null in case of an error + */ + public static List<String> run(String command) { + return Shell.run("sh", new String[]{ + command + }, null, false); + } + + /** + * Runs commands and return output + * + * @param commands The commands to run + * @return Output of the commands, or null in case of an error + */ + public static List<String> run(List<String> commands) { + return Shell.run("sh", commands.toArray(new String[commands.size()]), null, false); + } + + /** + * Runs commands and return output + * + * @param commands The commands to run + * @return Output of the commands, or null in case of an error + */ + public static List<String> run(String[] commands) { + return Shell.run("sh", commands, null, false); + } + } + + /** + * This class provides utility functions to easily execute commands using SU + * (root shell), as well as detecting whether or not root is available, and + * if so which version. + */ + public static class SU { + private static Boolean isSELinuxEnforcing = null; + private static String[] suVersion = new String[]{ + null, null + }; + + /** + * Runs command as root (if available) and return output + * + * @param command The command to run + * @return Output of the command, or null if root isn't available or in + * case of an error + */ + public static List<String> run(String command) { + return Shell.run("su", new String[]{ + command + }, null, false); + } + + /** + * Runs commands as root (if available) and return output + * + * @param commands The commands to run + * @return Output of the commands, or null if root isn't available or in + * case of an error + */ + public static List<String> run(List<String> commands) { + return Shell.run("su", commands.toArray(new String[commands.size()]), null, false); + } + + /** + * Runs commands as root (if available) and return output + * + * @param commands The commands to run + * @return Output of the commands, or null if root isn't available or in + * case of an error + */ + public static List<String> run(String[] commands) { + return Shell.run("su", commands, null, false); + } + + /** + * Detects whether or not superuser access is available, by checking the + * output of the "id" command if available, checking if a shell runs at + * all otherwise + * + * @return True if superuser access available + */ + public static boolean available() { + // this is only one of many ways this can be done + + List<String> ret = run(Shell.availableTestCommands); + return Shell.parseAvailableResult(ret, true); + } + + /** + * <p> + * Detects the version of the su binary installed (if any), if supported + * by the binary. Most binaries support two different version numbers, + * the public version that is displayed to users, and an internal + * version number that is used for version number comparisons. Returns + * null if su not available or retrieving the version isn't supported. + * </p> + * <p> + * Note that su binary version and GUI (APK) version can be completely + * different. + * </p> + * <p> + * This function caches its result to improve performance on multiple + * calls + * </p> + * + * @param internal Request human-readable version or application + * internal version + * @return String containing the su version or null + */ + public static synchronized String version(boolean internal) { + int idx = internal ? 0 : 1; + if (suVersion[idx] == null) { + String version = null; + + List<String> ret = Shell.run( + internal ? "su -V" : "su -v", + new String[] { "exit" }, + null, + false + ); + + if (ret != null) { + for (String line : ret) { + if (!internal) { + if (!line.trim().equals("")) { + version = line; + break; + } + } else { + try { + if (Integer.parseInt(line) > 0) { + version = line; + break; + } + } catch (NumberFormatException e) { + // should be parsable, try next line otherwise + } + } + } + } + + suVersion[idx] = version; + } + return suVersion[idx]; + } + + /** + * Attempts to deduce if the shell command refers to a su shell + * + * @param shell Shell command to run + * @return Shell command appears to be su + */ + public static boolean isSU(String shell) { + // Strip parameters + int pos = shell.indexOf(' '); + if (pos >= 0) { + shell = shell.substring(0, pos); + } + + // Strip path + pos = shell.lastIndexOf('/'); + if (pos >= 0) { + shell = shell.substring(pos + 1); + } + + return shell.equals("su"); + } + + /** + * Constructs a shell command to start a su shell using the supplied uid + * and SELinux context. This is can be an expensive operation, consider + * caching the result. + * + * @param uid Uid to use (0 == root) + * @param context (SELinux) context name to use or null + * @return Shell command + */ + public static String shell(int uid, String context) { + // su[ --context <context>][ <uid>] + String shell = "su"; + + if ((context != null) && isSELinuxEnforcing()) { + String display = version(false); + String internal = version(true); + + // We only know the format for SuperSU v1.90+ right now + //TODO add detection for other su's that support this + if ((display != null) && + (internal != null) && + (display.endsWith("SUPERSU")) && + (Integer.valueOf(internal) >= 190)) { + shell = String.format(Locale.ENGLISH, "%s --context %s", shell, context); + } + } + + // Most su binaries support the "su <uid>" format, but in case + // they don't, lets skip it for the default 0 (root) case + if (uid > 0) { + shell = String.format(Locale.ENGLISH, "%s %d", shell, uid); + } + + return shell; + } + + /** + * Constructs a shell command to start a su shell connected to mount + * master daemon, to perform public mounts on Android 4.3+ (or 4.2+ in + * SELinux enforcing mode) + * + * @return Shell command + */ + public static String shellMountMaster() { + if (android.os.Build.VERSION.SDK_INT >= 17) { + return "su --mount-master"; + } + return "su"; + } + + /** + * Detect if SELinux is set to enforcing, caches result + * + * @return true if SELinux set to enforcing, or false in the case of + * permissive or not present + */ + public static synchronized boolean isSELinuxEnforcing() { + if (isSELinuxEnforcing == null) { + Boolean enforcing = null; + + // First known firmware with SELinux built-in was a 4.2 (17) + // leak + if (android.os.Build.VERSION.SDK_INT >= 17) { + // Detect enforcing through sysfs, not always present + File f = new File("/sys/fs/selinux/enforce"); + if (f.exists()) { + try { + InputStream is = new FileInputStream("/sys/fs/selinux/enforce"); + try { + enforcing = (is.read() == '1'); + } finally { + is.close(); + } + } catch (Exception e) { + // we might not be allowed to read, thanks SELinux + } + } + + // 4.4+ has a new API to detect SELinux mode, so use it + // SELinux is typically in enforced mode, but emulators may have SELinux disabled + if (enforcing == null) { + try { + Class seLinux = Class.forName("android.os.SELinux"); + Method isSELinuxEnforced = seLinux.getMethod("isSELinuxEnforced"); + enforcing = (Boolean) isSELinuxEnforced.invoke(seLinux.newInstance()); + } catch (Exception e) { + // 4.4+ release builds are enforcing by default, take the gamble + enforcing = (android.os.Build.VERSION.SDK_INT >= 19); + } + } + } + + if (enforcing == null) { + enforcing = false; + } + + isSELinuxEnforcing = enforcing; + } + return isSELinuxEnforcing; + } + + /** + * <p> + * Clears results cached by isSELinuxEnforcing() and version(boolean + * internal) calls. + * </p> + * <p> + * Most apps should never need to call this, as neither enforcing status + * nor su version is likely to change on a running device - though it is + * not impossible. + * </p> + */ + public static synchronized void clearCachedResults() { + isSELinuxEnforcing = null; + suVersion[0] = null; + suVersion[1] = null; + } + } + + private interface OnResult { + // for any onCommandResult callback + int WATCHDOG_EXIT = -1; + int SHELL_DIED = -2; + + // for Interactive.open() callbacks only + int SHELL_EXEC_FAILED = -3; + int SHELL_WRONG_UID = -4; + int SHELL_RUNNING = 0; + } + + /** + * Command result callback, notifies the recipient of the completion of a + * command block, including the (last) exit code, and the full output + */ + public interface OnCommandResultListener extends OnResult { + /** + * <p> + * Command result callback + * </p> + * <p> + * Depending on how and on which thread the shell was created, this + * callback may be executed on one of the gobbler threads. In that case, + * it is important the callback returns as quickly as possible, as + * delays in this callback may pause the native process or even result + * in a deadlock + * </p> + * <p> + * See {@link Interactive} for threading details + * </p> + * + * @param commandCode Value previously supplied to addCommand + * @param exitCode Exit code of the last command in the block + * @param output All output generated by the command block + */ + void onCommandResult(int commandCode, int exitCode, List<String> output); + } + + /** + * Command per line callback for parsing the output line by line without + * buffering It also notifies the recipient of the completion of a command + * block, including the (last) exit code. + */ + public interface OnCommandLineListener extends OnResult, OnLineListener { + /** + * <p> + * Command result callback + * </p> + * <p> + * Depending on how and on which thread the shell was created, this + * callback may be executed on one of the gobbler threads. In that case, + * it is important the callback returns as quickly as possible, as + * delays in this callback may pause the native process or even result + * in a deadlock + * </p> + * <p> + * See {@link Interactive} for threading details + * </p> + * + * @param commandCode Value previously supplied to addCommand + * @param exitCode Exit code of the last command in the block + */ + void onCommandResult(int commandCode, int exitCode); + } + + /** + * Internal class to store command block properties + */ + private static class Command { + private static int commandCounter = 0; + + private final String[] commands; + private final int code; + private final OnCommandResultListener onCommandResultListener; + private final OnCommandLineListener onCommandLineListener; + private final String marker; + + public Command(String[] commands, int code, + OnCommandResultListener onCommandResultListener, + OnCommandLineListener onCommandLineListener) { + this.commands = commands; + this.code = code; + this.onCommandResultListener = onCommandResultListener; + this.onCommandLineListener = onCommandLineListener; + this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter); + } + } + + /** + * Builder class for {@link Interactive} + */ + public static class Builder { + private Handler handler = null; + private boolean autoHandler = true; + private String shell = "sh"; + private boolean wantSTDERR = false; + private List<Command> commands = new LinkedList<Command>(); + private Map<String, String> environment = new HashMap<String, String>(); + private OnLineListener onSTDOUTLineListener = null; + private OnLineListener onSTDERRLineListener = null; + private int watchdogTimeout = 0; + + /** + * <p> + * Set a custom handler that will be used to post all callbacks to + * </p> + * <p> + * See {@link Interactive} for further details on threading and + * handlers + * </p> + * + * @param handler Handler to use + * @return This Builder object for method chaining + */ + public Builder setHandler(Handler handler) { + this.handler = handler; + return this; + } + + /** + * <p> + * Automatically create a handler if possible ? Default to true + * </p> + * <p> + * See {@link Interactive} for further details on threading and + * handlers + * </p> + * + * @param autoHandler Auto-create handler ? + * @return This Builder object for method chaining + */ + public Builder setAutoHandler(boolean autoHandler) { + this.autoHandler = autoHandler; + return this; + } + + /** + * Set shell binary to use. Usually "sh" or "su", do not use a full path + * unless you have a good reason to + * + * @param shell Shell to use + * @return This Builder object for method chaining + */ + public Builder setShell(String shell) { + this.shell = shell; + return this; + } + + /** + * Convenience function to set "sh" as used shell + * + * @return This Builder object for method chaining + */ + public Builder useSH() { + return setShell("sh"); + } + + /** + * Convenience function to set "su" as used shell + * + * @return This Builder object for method chaining + */ + public Builder useSU() { + return setShell("su"); + } + + /** + * Set if error output should be appended to command block result output + * + * @param wantSTDERR Want error output ? + * @return This Builder object for method chaining + */ + public Builder setWantSTDERR(boolean wantSTDERR) { + this.wantSTDERR = wantSTDERR; + return this; + } + + /** + * Add or update an environment variable + * + * @param key Key of the environment variable + * @param value Value of the environment variable + * @return This Builder object for method chaining + */ + public Builder addEnvironment(String key, String value) { + environment.put(key, value); + return this; + } + + /** + * Add or update environment variables + * + * @param addEnvironment Map of environment variables + * @return This Builder object for method chaining + */ + public Builder addEnvironment(Map<String, String> addEnvironment) { + environment.putAll(addEnvironment); + return this; + } + + /** + * Add a command to execute + * + * @param command Command to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(String command) { + return addCommand(command, 0, null); + } + + /** + * <p> + * Add a command to execute, with a callback to be called on completion + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * @return This Builder object for method chaining + */ + public Builder addCommand(String command, int code, + OnCommandResultListener onCommandResultListener) { + return addCommand(new String[]{ + command + }, code, onCommandResultListener); + } + + /** + * Add commands to execute + * + * @param commands Commands to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(List<String> commands) { + return addCommand(commands, 0, null); + } + + /** + * <p> + * Add commands to execute, with a callback to be called on completion + * (of all commands) + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * (of all commands) + * @return This Builder object for method chaining + */ + public Builder addCommand(List<String> commands, int code, + OnCommandResultListener onCommandResultListener) { + return addCommand(commands.toArray(new String[commands.size()]), code, + onCommandResultListener); + } + + /** + * Add commands to execute + * + * @param commands Commands to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(String[] commands) { + return addCommand(commands, 0, null); + } + + /** + * <p> + * Add commands to execute, with a callback to be called on completion + * (of all commands) + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * (of all commands) + * @return This Builder object for method chaining + */ + public Builder addCommand(String[] commands, int code, + OnCommandResultListener onCommandResultListener) { + this.commands.add(new Command(commands, code, onCommandResultListener, null)); + return this; + } + + /** + * <p> + * Set a callback called for every line output to STDOUT by the shell + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param onLineListener Callback to be called for each line + * @return This Builder object for method chaining + */ + public Builder setOnSTDOUTLineListener(OnLineListener onLineListener) { + this.onSTDOUTLineListener = onLineListener; + return this; + } + + /** + * <p> + * Set a callback called for every line output to STDERR by the shell + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param onLineListener Callback to be called for each line + * @return This Builder object for method chaining + */ + public Builder setOnSTDERRLineListener(OnLineListener onLineListener) { + this.onSTDERRLineListener = onLineListener; + return this; + } + + /** + * <p> + * Enable command timeout callback + * </p> + * <p> + * This will invoke the onCommandResult() callback with exitCode + * WATCHDOG_EXIT if a command takes longer than watchdogTimeout seconds + * to complete. + * </p> + * <p> + * If a watchdog timeout occurs, it generally means that the Interactive + * session is out of sync with the shell process. The caller should + * close the current session and open a new one. + * </p> + * + * @param watchdogTimeout Timeout, in seconds; 0 to disable + * @return This Builder object for method chaining + */ + public Builder setWatchdogTimeout(int watchdogTimeout) { + this.watchdogTimeout = watchdogTimeout; + return this; + } + + /** + * Construct a {@link Interactive} instance, and start the shell + * + * @return Interactive shell + */ + public Interactive open() { + return new Interactive(this, null); + } + + /** + * Construct a {@link Interactive} instance, try to start the + * shell, and call onCommandResultListener to report success or failure + * + * @param onCommandResultListener Callback to return shell open status + * @return Interactive shell + */ + public Interactive open(OnCommandResultListener onCommandResultListener) { + return new Interactive(this, onCommandResultListener); + } + } + + /** + * <p> + * An interactive shell - initially created with {@link Builder} - + * that executes blocks of commands you supply in the background, optionally + * calling callbacks as each block completes. + * </p> + * <p> + * STDERR output can be supplied as well, but due to compatibility with + * older Android versions, wantSTDERR is not implemented using + * redirectErrorStream, but rather appended to the output. STDOUT and STDERR + * are thus not guaranteed to be in the correct order in the output. + * </p> + * <p> + * Note as well that the close() and waitForIdle() methods will + * intentionally crash when run in debug mode from the main thread of the + * application. Any blocking call should be run from a background thread. + * </p> + * <p> + * When in debug mode, the code will also excessively log the commands + * passed to and the output returned from the shell. + * </p> + * <p> + * Though this function uses background threads to gobble STDOUT and STDERR + * so a deadlock does not occur if the shell produces massive output, the + * output is still stored in a List<String>, and as such doing + * something like <em>'ls -lR /'</em> will probably have you run out of + * memory when using a {@link OnCommandResultListener}. A work-around + * is to not supply this callback, but using (only) + * {@link Builder#setOnSTDOUTLineListener(OnLineListener)}. This way, + * an internal buffer will not be created and wasting your memory. + * </p> + * <h3>Callbacks, threads and handlers</h3> + * <p> + * On which thread the callbacks execute is dependent on your + * initialization. You can supply a custom Handler using + * {@link Builder#setHandler(Handler)} if needed. If you do not supply + * a custom Handler - unless you set + * {@link Builder#setAutoHandler(boolean)} to false - a Handler will + * be auto-created if the thread used for instantiation of the object has a + * Looper. + * </p> + * <p> + * If no Handler was supplied and it was also not auto-created, all + * callbacks will be called from either the STDOUT or STDERR gobbler + * threads. These are important threads that should be blocked as little as + * possible, as blocking them may in rare cases pause the native process or + * even create a deadlock. + * </p> + * <p> + * The main thread must certainly have a Looper, thus if you call + * {@link Builder#open()} from the main thread, a handler will (by + * default) be auto-created, and all the callbacks will be called on the + * main thread. While this is often convenient and easy to code with, you + * should be aware that if your callbacks are 'expensive' to execute, this + * may negatively impact UI performance. + * </p> + * <p> + * Background threads usually do <em>not</em> have a Looper, so calling + * {@link Builder#open()} from such a background thread will (by + * default) result in all the callbacks being executed in one of the gobbler + * threads. You will have to make sure the code you execute in these + * callbacks is thread-safe. + * </p> + */ + public static class Interactive { + private final Handler handler; + private final boolean autoHandler; + private final String shell; + private final boolean wantSTDERR; + private final List<Command> commands; + private final Map<String, String> environment; + private final OnLineListener onSTDOUTLineListener; + private final OnLineListener onSTDERRLineListener; + private int watchdogTimeout; + + private Process process = null; + private DataOutputStream STDIN = null; + private StreamGobbler STDOUT = null; + private StreamGobbler STDERR = null; + private ScheduledThreadPoolExecutor watchdog = null; + + private volatile boolean running = false; + private volatile boolean idle = true; // read/write only synchronized + private volatile boolean closed = true; + private volatile int callbacks = 0; + private volatile int watchdogCount; + + private final Object idleSync = new Object(); + private final Object callbackSync = new Object(); + + private volatile int lastExitCode = 0; + private volatile String lastMarkerSTDOUT = null; + private volatile String lastMarkerSTDERR = null; + private volatile Command command = null; + private volatile List<String> buffer = null; + + /** + * The only way to create an instance: Shell.Builder::open() + * + * @param builder Builder class to take values from + */ + private Interactive(final Builder builder, + final OnCommandResultListener onCommandResultListener) { + autoHandler = builder.autoHandler; + shell = builder.shell; + wantSTDERR = builder.wantSTDERR; + commands = builder.commands; + environment = builder.environment; + onSTDOUTLineListener = builder.onSTDOUTLineListener; + onSTDERRLineListener = builder.onSTDERRLineListener; + watchdogTimeout = builder.watchdogTimeout; + + // If a looper is available, we offload the callbacks from the + // gobbling threads + // to whichever thread created us. Would normally do this in open(), + // but then we could not declare handler as final + if ((Looper.myLooper() != null) && (builder.handler == null) && autoHandler) { + handler = new Handler(); + } else { + handler = builder.handler; + } + + if (onCommandResultListener != null) { + // Allow up to 60 seconds for SuperSU/Superuser dialog, then enable + // the user-specified timeout for all subsequent operations + watchdogTimeout = 60; + commands.add(0, new Command(Shell.availableTestCommands, 0, new OnCommandResultListener() { + public void onCommandResult(int commandCode, int exitCode, List<String> output) { + if ((exitCode == OnCommandResultListener.SHELL_RUNNING) && + !Shell.parseAvailableResult(output, SU.isSU(shell))) { + // shell is up, but it's brain-damaged + exitCode = OnCommandResultListener.SHELL_WRONG_UID; + } + watchdogTimeout = builder.watchdogTimeout; + onCommandResultListener.onCommandResult(0, exitCode, output); + } + }, null)); + } + + if (!open() && (onCommandResultListener != null)) { + onCommandResultListener.onCommandResult(0, + OnCommandResultListener.SHELL_EXEC_FAILED, null); + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + } + + /** + * Add a command to execute + * + * @param command Command to execute + */ + public void addCommand(String command) { + addCommand(command, 0, (OnCommandResultListener) null); + } + + /** + * <p> + * Add a command to execute, with a callback to be called on completion + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + */ + public void addCommand(String command, int code, + OnCommandResultListener onCommandResultListener) { + addCommand(new String[]{ + command + }, code, onCommandResultListener); + } + + /** + * <p> + * Add a command to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback + */ + public void addCommand(String command, int code, OnCommandLineListener onCommandLineListener) { + addCommand(new String[]{ + command + }, code, onCommandLineListener); + } + + /** + * Add commands to execute + * + * @param commands Commands to execute + */ + public void addCommand(List<String> commands) { + addCommand(commands, 0, (OnCommandResultListener) null); + } + + /** + * <p> + * Add commands to execute, with a callback to be called on completion + * (of all commands) + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * (of all commands) + */ + public void addCommand(List<String> commands, int code, + OnCommandResultListener onCommandResultListener) { + addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); + } + + /** + * <p> + * Add commands to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback + */ + public void addCommand(List<String> commands, int code, + OnCommandLineListener onCommandLineListener) { + addCommand(commands.toArray(new String[commands.size()]), code, onCommandLineListener); + } + + /** + * Add commands to execute + * + * @param commands Commands to execute + */ + public void addCommand(String[] commands) { + addCommand(commands, 0, (OnCommandResultListener) null); + } + + /** + * <p> + * Add commands to execute, with a callback to be called on completion + * (of all commands) + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * (of all commands) + */ + public synchronized void addCommand(String[] commands, int code, + OnCommandResultListener onCommandResultListener) { + this.commands.add(new Command(commands, code, onCommandResultListener, null)); + runNextCommand(); + } + + /** + * <p> + * Add commands to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + * </p> + * <p> + * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + * </p> + * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback + */ + public synchronized void addCommand(String[] commands, int code, + OnCommandLineListener onCommandLineListener) { + this.commands.add(new Command(commands, code, null, onCommandLineListener)); + runNextCommand(); + } + + /** + * Run the next command if any and if ready, signals idle state if no + * commands left + */ + private void runNextCommand() { + runNextCommand(true); + } + + /** + * Called from a ScheduledThreadPoolExecutor timer thread every second + * when there is an outstanding command + */ + private synchronized void handleWatchdog() { + final int exitCode; + + if (watchdog == null) + return; + if (watchdogTimeout == 0) + return; + + if (!isRunning()) { + exitCode = OnCommandResultListener.SHELL_DIED; + } else if (watchdogCount++ < watchdogTimeout) { + return; + } else { + exitCode = OnCommandResultListener.WATCHDOG_EXIT; + } + + postCallback(command, exitCode, buffer); + + // prevent multiple callbacks for the same command + command = null; + buffer = null; + idle = true; + + watchdog.shutdown(); + watchdog = null; + kill(); + } + + /** + * Start the periodic timer when a command is submitted + */ + private void startWatchdog() { + if (watchdogTimeout == 0) { + return; + } + watchdogCount = 0; + watchdog = new ScheduledThreadPoolExecutor(1); + watchdog.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + handleWatchdog(); + } + }, 1, 1, TimeUnit.SECONDS); + } + + /** + * Disable the watchdog timer upon command completion + */ + private void stopWatchdog() { + if (watchdog != null) { + watchdog.shutdownNow(); + watchdog = null; + } + } + + /** + * Run the next command if any and if ready + * + * @param notifyIdle signals idle state if no commands left ? + */ + private void runNextCommand(boolean notifyIdle) { + // must always be called from a synchronized method + + boolean running = isRunning(); + if (!running) + idle = true; + + if (running && idle && (commands.size() > 0)) { + Command command = commands.get(0); + commands.remove(0); + + buffer = null; + lastExitCode = 0; + lastMarkerSTDOUT = null; + lastMarkerSTDERR = null; + + if (command.commands.length > 0) { + try { + if (command.onCommandResultListener != null) { + // no reason to store the output if we don't have an + // OnCommandResultListener + // user should catch the output with an + // OnLineListener in this case + buffer = Collections.synchronizedList(new ArrayList<String>()); + } + + idle = false; + this.command = command; + startWatchdog(); + for (String write : command.commands) { + STDIN.write((write + "\n").getBytes("UTF-8")); + } + STDIN.write(("echo " + command.marker + " $?\n").getBytes("UTF-8")); + STDIN.write(("echo " + command.marker + " >&2\n").getBytes("UTF-8")); + STDIN.flush(); + } catch (IOException e) { + // STDIN might have closed + } + } else { + runNextCommand(false); + } + } else if (!running) { + // our shell died for unknown reasons - abort all submissions + while (commands.size() > 0) { + postCallback(commands.remove(0), OnCommandResultListener.SHELL_DIED, null); + } + } + + if (idle && notifyIdle) { + synchronized (idleSync) { + idleSync.notifyAll(); + } + } + } + + /** + * Processes a STDOUT/STDERR line containing an end/exitCode marker + */ + private synchronized void processMarker() { + if (command.marker.equals(lastMarkerSTDOUT) + && (command.marker.equals(lastMarkerSTDERR))) { + postCallback(command, lastExitCode, buffer); + stopWatchdog(); + command = null; + buffer = null; + idle = true; + runNextCommand(); + } + } + + /** + * Process a normal STDOUT/STDERR line + * + * @param line Line to process + * @param listener Callback to call or null + */ + private synchronized void processLine(String line, OnLineListener listener) { + if (listener != null) { + if (handler != null) { + final String fLine = line; + final OnLineListener fListener = listener; + + startCallback(); + handler.post(new Runnable() { + @Override + public void run() { + try { + fListener.onLine(fLine); + } finally { + endCallback(); + } + } + }); + } else { + listener.onLine(line); + } + } + } + + /** + * Add line to internal buffer + * + * @param line Line to add + */ + private synchronized void addBuffer(String line) { + if (buffer != null) { + buffer.add(line); + } + } + + /** + * Increase callback counter + */ + private void startCallback() { + synchronized (callbackSync) { + callbacks++; + } + } + + /** + * Schedule a callback to run on the appropriate thread + */ + private void postCallback(final Command fCommand, final int fExitCode, + final List<String> fOutput) { + if (fCommand.onCommandResultListener == null && fCommand.onCommandLineListener == null) { + return; + } + if (handler == null) { + if (fCommand.onCommandResultListener != null) + fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, + fOutput); + if (fCommand.onCommandLineListener != null) + fCommand.onCommandLineListener.onCommandResult(fCommand.code, fExitCode); + return; + } + startCallback(); + handler.post(new Runnable() { + @Override + public void run() { + try { + if (fCommand.onCommandResultListener != null) + fCommand.onCommandResultListener.onCommandResult(fCommand.code, + fExitCode, fOutput); + if (fCommand.onCommandLineListener != null) + fCommand.onCommandLineListener + .onCommandResult(fCommand.code, fExitCode); + } finally { + endCallback(); + } + } + }); + } + + /** + * Decrease callback counter, signals callback complete state when + * dropped to 0 + */ + private void endCallback() { + synchronized (callbackSync) { + callbacks--; + if (callbacks == 0) { + callbackSync.notifyAll(); + } + } + } + + /** + * Internal call that launches the shell, starts gobbling, and starts + * executing commands. See {@link Interactive} + * + * @return Opened successfully ? + */ + private synchronized boolean open() { + try { + // setup our process, retrieve STDIN stream, and STDOUT/STDERR + // gobblers + if (environment.size() == 0) { + process = Runtime.getRuntime().exec(shell); + } else { + Map<String, String> newEnvironment = new HashMap<String, String>(); + newEnvironment.putAll(System.getenv()); + newEnvironment.putAll(environment); + int i = 0; + String[] env = new String[newEnvironment.size()]; + for (Map.Entry<String, String> entry : newEnvironment.entrySet()) { + env[i] = entry.getKey() + "=" + entry.getValue(); + i++; + } + process = Runtime.getRuntime().exec(shell, env); + } + + STDIN = new DataOutputStream(process.getOutputStream()); + STDOUT = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "-", + process.getInputStream(), new OnLineListener() { + @Override + public void onLine(String line) { + synchronized (Interactive.this) { + if (command == null) { + return; + } + + String contentPart = line; + String markerPart = null; + + int markerIndex = line.indexOf(command.marker); + if (markerIndex == 0) { + contentPart = null; + markerPart = line; + } else if (markerIndex > 0) { + contentPart = line.substring(0, markerIndex); + markerPart = line.substring(markerIndex); + } + + if (contentPart != null) { + addBuffer(contentPart); + processLine(contentPart, onSTDOUTLineListener); + processLine(contentPart, command.onCommandLineListener); + } + + if (markerPart != null) { + try { + lastExitCode = Integer.valueOf( + markerPart.substring(command.marker.length() + 1), 10); + } catch (Exception e) { + // this really shouldn't happen + e.printStackTrace(); + } + lastMarkerSTDOUT = command.marker; + processMarker(); + } + } + } + }); + STDERR = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "*", + process.getErrorStream(), new OnLineListener() { + @Override + public void onLine(String line) { + synchronized (Interactive.this) { + if (command == null) { + return; + } + + String contentPart = line; + + int markerIndex = line.indexOf(command.marker); + if (markerIndex == 0) { + contentPart = null; + } else if (markerIndex > 0) { + contentPart = line.substring(0, markerIndex); + } + + if (contentPart != null) { + if (wantSTDERR) + addBuffer(contentPart); + processLine(contentPart, onSTDERRLineListener); + } + + if (markerIndex >= 0) { + lastMarkerSTDERR = command.marker; + processMarker(); + } + } + } + }); + + // start gobbling and write our commands to the shell + STDOUT.start(); + STDERR.start(); + + running = true; + closed = false; + + runNextCommand(); + + return true; + } catch (IOException e) { + // shell probably not found + return false; + } + } + + /** + * Close shell and clean up all resources. Call this when you are done + * with the shell. If the shell is not idle (all commands completed) you + * should not call this method from the main UI thread because it may + * block for a long time. This method will intentionally crash your app + * (if in debug mode) if you try to do this anyway. + */ + public void close() { + boolean _idle = isIdle(); // idle must be checked synchronized + + synchronized (this) { + if (!running) + return; + running = false; + closed = true; + } + + if (!_idle) + waitForIdle(); + + try { + try { + STDIN.write(("exit\n").getBytes("UTF-8")); + STDIN.flush(); + } catch (IOException e) { + if (e.getMessage().contains("EPIPE") || e.getMessage().contains("Stream closed")) { + // we're not running a shell, the shell closed STDIN, + // the script already contained the exit command, etc. + } else { + throw e; + } + } + + // wait for our process to finish, while we gobble away in the + // background + process.waitFor(); + + // make sure our threads are done gobbling, our streams are + // closed, and the process is destroyed - while the latter two + // shouldn't be needed in theory, and may even produce warnings, + // in "normal" Java they are required for guaranteed cleanup of + // resources, so lets be safe and do this on Android as well + try { + STDIN.close(); + } catch (IOException e) { + // STDIN going missing is no reason to abort + } + STDOUT.join(); + STDERR.join(); + stopWatchdog(); + process.destroy(); + } catch (IOException e) { + // various unforseen IO errors may still occur + } catch (InterruptedException e) { + // this should really be re-thrown + } + } + + /** + * Try to clean up as much as possible from a shell that's gotten itself + * wedged. Hopefully the StreamGobblers will croak on their own when the + * other side of the pipe is closed. + */ + public synchronized void kill() { + running = false; + closed = true; + + try { + STDIN.close(); + } catch (IOException e) { + // in case it was closed + } + try { + process.destroy(); + } catch (Exception e) { + // in case it was already destroyed or can't be + } + + idle = true; + synchronized (idleSync) { + idleSync.notifyAll(); + } + } + + /** + * Is our shell still running ? + * + * @return Shell running ? + */ + public boolean isRunning() { + if (process == null) { + return false; + } + try { + process.exitValue(); + return false; + } catch (IllegalThreadStateException e) { + // if this is thrown, we're still running + } + return true; + } + + /** + * Have all commands completed executing ? + * + * @return Shell idle ? + */ + public synchronized boolean isIdle() { + if (!isRunning()) { + idle = true; + synchronized (idleSync) { + idleSync.notifyAll(); + } + } + return idle; + } + + /** + * <p> + * Wait for idle state. As this is a blocking call, you should not call + * it from the main UI thread. If you do so and debug mode is enabled, + * this method will intentionally crash your app. + * </p> + * <p> + * If not interrupted, this method will not return until all commands + * have finished executing. Note that this does not necessarily mean + * that all the callbacks have fired yet. + * </p> + * <p> + * If no Handler is used, all callbacks will have been executed when + * this method returns. If a Handler is used, and this method is called + * from a different thread than associated with the Handler's Looper, + * all callbacks will have been executed when this method returns as + * well. If however a Handler is used but this method is called from the + * same thread as associated with the Handler's Looper, there is no way + * to know. + * </p> + * <p> + * In practice this means that in most simple cases all callbacks will + * have completed when this method returns, but if you actually depend + * on this behavior, you should make certain this is indeed the case. + * </p> + * <p> + * See {@link Interactive} for further details on threading and + * handlers + * </p> + * + * @return True if wait complete, false if wait interrupted + */ + public boolean waitForIdle() { + if (isRunning()) { + synchronized (idleSync) { + while (!idle) { + try { + idleSync.wait(); + } catch (InterruptedException e) { + return false; + } + } + } + + if ((handler != null) && + (handler.getLooper() != null) && + (handler.getLooper() != Looper.myLooper())) { + // If the callbacks are posted to a different thread than + // this one, we can wait until all callbacks have called + // before returning. If we don't use a Handler at all, the + // callbacks are already called before we get here. If we do + // use a Handler but we use the same Looper, waiting here + // would actually block the callbacks from being called + + synchronized (callbackSync) { + while (callbacks > 0) { + try { + callbackSync.wait(); + } catch (InterruptedException e) { + return false; + } + } + } + } + } + + return true; + } + + /** + * Are we using a Handler to post callbacks ? + * + * @return Handler used ? + */ + public boolean hasHandler() { + return (handler != null); + } + } +} diff --git a/app/src/main/java/ohi/andre/consolelauncher/tuils/libsuperuser/StreamGobbler.java b/app/src/main/java/ohi/andre/consolelauncher/tuils/libsuperuser/StreamGobbler.java new file mode 100755 index 0000000000000000000000000000000000000000..1d1e462e1deec8d1ef5afb6ee7e4c43664dd466f --- /dev/null +++ b/app/src/main/java/ohi/andre/consolelauncher/tuils/libsuperuser/StreamGobbler.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012-2015 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohi.andre.consolelauncher.tuils.libsuperuser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + +/** + * Thread utility class continuously reading from an InputStream + */ +public class StreamGobbler extends Thread { + /** + * Line callback interface + */ + public interface OnLineListener { + /** + * <p>Line callback</p> + * + * <p>This callback should process the line as quickly as possible. + * Delays in this callback may pause the native process or even + * result in a deadlock</p> + * + * @param line String that was gobbled + */ + void onLine(String line); + } + + private String shell = null; + private BufferedReader reader = null; + private List<String> writer = null; + private OnLineListener listener = null; + + /** + * <p>StreamGobbler constructor</p> + * + * <p>We use this class because shell STDOUT and STDERR should be read as quickly as + * possible to prevent a deadlock from occurring, or Process.waitFor() never + * returning (as the buffer is full, pausing the native process)</p> + * + * @param shell Name of the shell + * @param inputStream InputStream to read from + * @param outputList {@literal List<String>} to write to, or null + */ + public StreamGobbler(String shell, InputStream inputStream, List<String> outputList) { + this.shell = shell; + reader = new BufferedReader(new InputStreamReader(inputStream)); + writer = outputList; + } + + /** + * <p>StreamGobbler constructor</p> + * + * <p>We use this class because shell STDOUT and STDERR should be read as quickly as + * possible to prevent a deadlock from occurring, or Process.waitFor() never + * returning (as the buffer is full, pausing the native process)</p> + * + * @param shell Name of the shell + * @param inputStream InputStream to read from + * @param onLineListener OnLineListener callback + */ + public StreamGobbler(String shell, InputStream inputStream, OnLineListener onLineListener) { + this.shell = shell; + reader = new BufferedReader(new InputStreamReader(inputStream)); + listener = onLineListener; + } + + @Override + public void run() { + // keep reading the InputStream until it ends (or an error occurs) + try { + String line; + while ((line = reader.readLine()) != null) { + if (writer != null) writer.add(line); + if (listener != null) listener.onLine(line); + } + } catch (IOException e) { + // reader probably closed, expected exit condition + } + + // make sure our stream is closed and resources will be freed + try { + reader.close(); + } catch (IOException e) { + // read already closed + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4b15629d8ad084d7d476c0c1a4ef959b23a81a18..420929d5a719cd77c1bc4844cf8103b298b3166f 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ <string name="location_off">Turn on GPS or network location</string> <string name="invalid_integer">Invalid integer</string> <string name="activity_not_found">Ativity not found</string> + <string name="use_suc">su: use "su -c" instead</string> <!-- busy --> <string name="busy">T-UI is busy, wait until the execution finishes or use the command \"ctrlc\"</string> @@ -20,7 +21,7 @@ <string name="rate_donate_text">\nDo you like my work? Rate on Play Store (command: >>rate) or offer me a coffee (command: >>donate). Thank you for using T-UI. \n\nYou can disable this message with the command\nconfig -set donation_message false</string> - <string name="firsthelp_text">First time with t-ui? Use the command "tutorial" or "help"</string> + <string name="firsthelp_text">First time with t-ui? Use the command \"tutorial\" or \"help\"</string> <!-- app mgr --> <string name="output_appnotfound">Application not found</string> @@ -73,7 +74,7 @@ <string name="output_data">Mobile Data active:</string> <string name="output_bluetooth">Bluetooth active:</string> <string name="output_airplane">Airplane Mode active:</string> - <string name="output_nofeature">Can\'t use this feature on your Android version</string> + <string name="output_nofeature">This feature isn\'t available on your Android version</string> <!-- files --> <string name="output_isfile">Cant\'t write here. It\'s a file</string> @@ -300,7 +301,7 @@ <string name="help_exit">Close T-UI and reset launcher preferences</string> <!-- linux --> - <string name="help_ctrlc">Send SIGINT to the current shell process</string> + <string name="help_ctrlc">Interrupt the current shell process and create a new one</string> <string name="help_open">Open a file with the default application \nUsage: \n>>open [pathToFile] diff --git a/settings.gradle b/settings.gradle index e7b4def49cb53d9aa04228dd3edb14c9e635e003..92098412bad323815a6aa08244e63f4816e6e993 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app', ':emulatorview'