Search code examples
javaandroidruntimebufferedreaderoutputstream

Continuously reading output from program and displaying that data elsewhere


I am trying to read from a program that outputs data and then have that data displayed continuously on my GUI. Specifically, I have connected a rotary encoder as an input device to my Raspberry Pi 3, which runs on Android OS (4.4 - KitKat). When I physically interact with the rotary encoder and when I'm reading from the tool "getevent" in /dev/input/eventX in the terminal, I see data pop up as the rotary encoder is moving.

I am trying to read that data as it is outputted and almost immediately read that data in and display it on my GUI in real-time (or with a very small delay).

So what I've tried to do is as follows:

1) In my GetEventRecorder class, I write to open the getevent tool and then I read the output from it. I use the scheduleAtFixedRate to repeat this process (with small delay). This tool stays open until I close it. I believe I need to close the tool to be able to read the output from it. So I close it with the intention of opening it up again right after. I think this may lead to part of the problem I'm having which I describe below.

2) In my MainActivity(), I have a textView that I update periodically (small delay) and it takes in the read data from part 1. I do this in a RunThread.

Problem: Only after the tool closes, I see the data displayed on the screen. However, I do not seem to see the data until it closes (which I believe is normal). However, I cannot seem to read any new inputs thereafter, only the inputs before the tool closes. But I want to keep continuously reading in the data and cannot seem to do this. Please find my code below. As this is my first post, please ask any questions so I can help further clarify my situation, etc.

MainActivity:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

TextView textElement;
private String myOutput;
private GetEventRecorder getEventRecorder;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    textElement = (TextView) findViewById(R.id.firstTextView);
    getEventRecorder = new GetEventRecorder();

    init_all();
    runThread();
}

public void init_all() {
    getEventRecorder.start();
}

private void runThread() {
    new Thread() {
        public void run() {
            while (true) {
                try {
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            myOutput= getEventRecorder.logGetEventData();
                            textElement.append(myOutput);
                        }
                    });
                    Thread.sleep(1000);
                    }
                    catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }.start();
}
}

GetEventRecorder:

import java.io.OutputStream;
import java.util.Timer;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.TimerTask;

public class GetEventRecorder {

private GetEventRecorder mRecorder = null;
Timer timer;
String err = "initial";

// active su session
private Process mProcess;
// Byte Writer
private DataOutputStream outputStream;
private BufferedReader br;
private StringBuilder text = new StringBuilder();
private int i = 0;

{
    try {
        mProcess = Runtime.getRuntime().exec("su");

        outputStream = new DataOutputStream(mProcess.getOutputStream());
        String comm1 = "getevent -c 2 /dev/input/event1";
        String newLine = "\n";

        outputStream.writeBytes(comm1);
        outputStream.writeBytes(newLine);

    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void start() {
    try {

        timer = new Timer();
        timer.scheduleAtFixedRate(new GetEventRecorder.RecorderTask(), 0, 
1000);
    } catch (Exception e) {

    }
}

public void stop() {
    if (mRecorder != null) {
        mRecorder.stop();
        mRecorder = null;
        timer.cancel();
    }
}

private class RecorderTask extends TimerTask {


    @Override
    public void run() {
        try {
            String close = "^C";

            outputStream.writeBytes(close);
            outputStream.flush();

            br = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));

            String line;

            if (br != null) {
                if (br.ready()) {
                    while ((line = br.readLine()) != null) {
                        text.append(line);
                        text.append("\n");
                    }
                }
            }
        } catch (Exception e) {
            err = "error_1";
        }
    }
}

public String logGetEventData() {
    if (text != null) {
        String retour = text.toString();
        return retour;
    } else {
        return "err_log";
    }
}
}

Solution

  • I'm confused by your terminology, since you refer to "opening the getevent file". I believe getevent is a tool (a program) on Android. See https://android.googlesource.com/platform/system/core.git/+/android-4.2.2_r1/toolbox/getevent.c and https://source.android.com/devices/input/getevent

    Based on your code, it looks like you're creating a superuser shell as a subprocess, running getevent by sending a command to the subprocess (which will go on to create a sub-subprocess), then looping sending ^C to the subprocess.

    • You pass -c 2 to getevent, which means it will read two values and exit.

    • ^C to a shell process kills the process. So you're not really closing a file here.

    • The shell you're spawning isn't interactive, so job control functions are likely not enabled. So ^C probably doesn't work anyway. (Not 100% sure on this one.)

    • You aren't rerunning getevent anywhere, so if it's stopping output for any reason (e.g. the previous list), you won't see anything more.

    So, to achieve what you want the way you're doing it, you'd need to respawn getevent. That's the code you have in your static initialization block right now.

    Just as further commentary, this seems like overkill for what you're doing. You really want to read from the event device directly. The only issue you seem to be working around with this approach is possibly the permissions on the device file.