Q:

Java runtime.exec user input race condition

I want my app to be able to use a global su instance. I have code that does that, but I have encountered a race condition, I believe.
I am storing some variables for su like so:
public static List<Object> rootObjects = Collections.synchronizedList(new ArrayList<>());

protected void onCreate(Bundle savedInstanceState) {
    ...
    if(PreferenceManager.getDefaultSharedPreferences(
            getApplicationContext()).getBoolean("use_su", false) && rootObjects.isEmpty())
    {
        try {
            Process process = Runtime.getRuntime().exec("su");
            rootObjects.add(process);
            InputStream inputStream = new DataInputStream(process.getInputStream());
            rootObjects.add(inputStream);
            OutputStream outputStream = new DataOutputStream(process.getOutputStream());
            rootObjects.add(outputStream);
        } catch (IOException e) {
            Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
        }
        finally {
            synchronized (rootObjects) {
                rootObjects.notifyAll();
            }
        }
    }
}

and using them like so:
byte[] getPrivateKeyAsSuperUser() {
    byte[] data = null;
    DataInputStream inputStream = null;
    DataOutputStream outputStream = null;

    if(MainActivity.rootObjects.size() != 3)
        synchronized (MainActivity.rootObjects)
        {
            try {
                MainActivity.rootObjects.wait();
            } catch (InterruptedException e) {
                Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
            }
        }

    for(Object rootObj : MainActivity.rootObjects)
    {
        if(rootObj instanceof DataInputStream)
            inputStream = (DataInputStream) rootObj;
        else if(rootObj instanceof DataOutputStream)
            outputStream = (DataOutputStream) rootObj;
    }
    try {
        outputStream.writeBytes(String.format("cat \"%s\"\n", sshPrivateKey.getAbsolutePath()));
        outputStream.flush();
        data = readStream(inputStream);
    } catch (IOException e) {
        Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
    }
    return data;
}

private byte[] readStream(InputStream stream) {
    byte[] data = null;
    try {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte buff[] = new byte[1024];
        int count = 0;

        while (stream.available() != 0 && (count = stream.read(buff)) != -1) {
            bos.write(buff, 0, count);
        }
        data = bos.toByteArray();
        //System.out.println(new String(data));
    } catch (IOException e) {
        Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
    }
    return data;
}

But it does not wait like I expect, and I instantly receive a Toast that the returned private key is not valid with my sanity check (It's probably null).
The code works if I let Process finish initializing, but I'd like the program to do that for me.
I've tried some other synchronization techniques such as locks, but apparently as soon as you know if an object has a lock your info is stale.
What is the best thread safe approach to have the caller of getPrivateKeyAsSuperUser() wait if Process is not initialized properly?
EDIT:
I would like to add that through some debugging, I have found that I do not want be waiting for Process to initialize (because what I have DOES that), but rather, that the shell spawned by su is valid to accept further commands. I suppose I could have a thread pipe something like echo DONE and loop until I get DONE back, but that seems like that would waste CPU horsepower. If someone could lend some knowledge on the subject, I would be extremely grateful.

A:

I have solved it. I did end up having to echo and check for done, but I have done it without a loop, or sleeping in my thread, so it will fire as soon as it can, without hogging the CPU. The concurrent class I was looking for was CountDownLatch as well.
The assignment look like this:
process = Runtime.getRuntime().exec("su");
outputStream = new DataOutputStream(process.getOutputStream());
outputStream.writeBytes("echo DONE\n");
outputStream.flush();
inputStream = new DataInputStream(process.getInputStream());
byte[] buff = new byte[4];
inputStream.read(buff);
if(new String(buff).equals("DONE"));
     MainActivity.rootLatch.countDown();

and getPrivateKeyAsSuperUser() became:
byte[] getPrivateKeyAsSuperUser() {

    byte[] data = null;
    try {
        MainActivity.rootLatch.await();
    } catch (InterruptedException e) {
        Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
    }
    Su su = Su.getStaticInstance();
        try {
            su.outputStream.writeBytes(String.format("cat \"%s\"\n", sshPrivateKey.getAbsolutePath()));
            su.outputStream.flush();
            data = readStream(su.inputStream);
        } catch (IOException e) {
            Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
        }
    return data;

}

Although, this feels slightly sloppy, I may end up posting this on Code Review.

