2

I've been trying to build a Terminal Emulator for Android. Being pretty new to this, my idea was to execute each command and store the output in a file, whose contents would be displayed after each execution. Pseudo Code :

public Boolean execCommands(String command) {
        try {
            rt = Runtime.getRuntime();
            process = rt.exec("su");
            DataOutputStream os = new DataOutputStream(process.getOutputStream());
            os.writeBytes("echo $ \""+command+ "\" >> /sdcard/Android/data/terminalemulatorlog.txt\n\n\n"); 
            /**** Note :  String command = (EditText)findViewById(R.id.command).getText().toString(); ****/
            os.flush();
            os.writeBytes("exit\n");
            os.flush();
            process.waitFor();
            }
        // Error Handling
        displayOutput(); //Loads and displays the Text File (/sdcard/Android/data/terminalemulatorlog.txt)
        return true;
        }

This piece of code works except for a few special commands (Eg. 'clear'). But what I'm more concerned about are the following problems :

  1. Each time a command is to be executed, I end up seeking SuperUser permissions (second line of code). And I'd like to do away with this.
  2. In cases when the user enters one command followed by another,
    Such as :

    cd /sdcard    
    touch File.txt    
    

    The File.txt is created in '/' and not in '/sdcard'. As of now to avoid this, I'm keeping a track of all the 'cd' commands to figure out what the present working directory is. And I'm hoping that there is a better way around this.

I'd be grateful if someone could help me out here.

Vishnu
  • 2,024
  • 3
  • 28
  • 34
  • Why not execute each command one by one just like in a terminal? Also just get rid of "su". If the user needs root priv's they should execute "sudo mycommand" just like they would in a terminal, instead of forcing everything to execute as root. – Jack Dec 29 '11 at 20:35
  • I'm not sure why, but the application force-closes without the 'su'. And I have no clue about how to 'execute each command one by one just like in a terminal'. Could you please provide an alternate code? Thanks! – Vishnu Dec 29 '11 at 20:44
  • When the user enters a command and presses enter, you should execute the command. It seems you are executing multiple commands or something? – Jack Dec 29 '11 at 21:52
  • That is what I'm doing here. The execCommands() function is called each time the user enters the command and presses the 'Execute' button. And yes, I was trying to execute multiple commands ([Source](http://stackoverflow.com/questions/7543700/android-run-bash-command-in-app)). Is there a way to get past the 2 problems I've stated in the question? – Vishnu Dec 30 '11 at 13:11
  • i never knew this could be possible in android thx to make this question – Aarush Kumar Jun 28 '21 at 00:33
  • hey can you pls show the displayoutput code function's code to me i am also trying to make it – Aarush Kumar Jun 28 '21 at 00:40

3 Answers3

2

This a bit late but here a few ways of doing this.

1)

Instead of using su as a starting point use /system/bin/sh.

and after calling

rt.exec("/system/bin/sh");

You should hold onto the Output Stream and Input Stream to give further commands.

After you issued a command you should echo a magic line like "---EOF---" and stop reading input after reading that line. If you don't have this you'll end up with the read function from the InputStream blocking.

2) Pipe the data to a native process you've written that simply moves the data on to your Android Application with a terminating character or string attached to the end.

I am not entirely sure how to do this, but it is essentially the same as the previous method just relies on you native application as a middle man.

This will get you close to a functioning "Terminal Emulator".

3)If you wan't a true Ternimal Emulator then there's no other way to do it than : using a native application that opens a connection to a psuedoterminal.

Here's some basic information of how to open a pty : link

Terminal Emulator is a open source project that uses this technique.

Have a look here

Xonar
  • 1,296
  • 1
  • 14
  • 21
2

Not sure if you are still needing this or not, but here is how I am issuing multiple commands at one time and not using "su" to have them run.

try {
    String[] commands = {           
            "dumpstate > /sdcard/LogFiles/dumpstate.txt",
            "dumpsys > /sdcard/LogFiles/dumpsys.txt",
            "logcat -d > /sdcard/LogFiles/log.txt",
            "cat /sdcard/LogFiles/dumpstate.txt /sdcard/LogFiles/dumpsys.txt /sdcard/LogFiles/log.txt > /sdcard/LogFiles/bugreport.rtf" };
    Process p = Runtime.getRuntime().exec("/system/bin/sh -");
    DataOutputStream os = new DataOutputStream(p.getOutputStream());
    for (String tmpCmd : commands) {
        os.writeBytes(tmpCmd + "\n");
    }
} catch (IOException e) {
    e.printStackTrace();
}
mavrosxristoforos
  • 3,573
  • 2
  • 25
  • 40
Jasonwilliams10
  • 292
  • 1
  • 4
  • 17
0

Regarding problem 1:

Each time a command is to be executed, I end up seeking SuperUser permissions (second line of code). And I'd like to do away with this.

Thanks to Xonar's suggestion from another answer:

After you issued a command you should echo a magic line like "---EOF---" and stop reading input after reading that line.

Solution in Kotlin:

private lateinit var suProcess: Process
private lateinit var outputStream: DataOutputStream

private fun getSu(): Boolean {
    return try {
        suProcess = Runtime.getRuntime().exec("su")
        outputStream = DataOutputStream(suProcess.outputStream)
        true
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

private fun sudo(command: String): List<String>? {
    return try {
        outputStream.writeBytes("$command\n")
        outputStream.flush()

        outputStream.writeBytes("echo ---EOF---\n")
        outputStream.flush()

        val reader = suProcess.inputStream.bufferedReader()
        val result = mutableListOf<String>()
        while (true) {
            val line = reader.readLine()
            if (line == "---EOF---") break
            result += line
        }

        result
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

private fun exitTerminal() {
    try {
        outputStream.writeBytes("exit\n")
        outputStream.flush()

        suProcess.waitFor()
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        outputStream.close()
    }
}

//Activity method
override fun onDestroy() {
    super.onDestroy()

    exitTerminal()
}
D31
  • 112
  • 1
  • 10