Question

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.

Was it helpful?

Solution

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();
}

OTHER TIPS

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

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()
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top