[SuperSU] Chainfire:
You have not fully understood the docs, and you are lacking knowledge of some basic Android principles (blocking operations, handlers, loopers).

You should not perform blocking operations on the main thread. Adding a command to a Shell.Interactive instance is generally not a blocking operation. #run(), #waitForIdle(), and #close() are blocking operations. Disk/file operations are blocking operations. Network operations are blocking operations. Any operation that runs in unknown time is a blocking operation.

As with many other APIs, callbacks are posted to a Handler. A Handler requires a Looper. You may provide your own Handler, or if you do not, libsuperuser will create one for you (unless you call #setAutoHandler(false)). Each Looper can have multiple Handlers, this is not an issue. A Handler will be associated with the thread on which new Handler() is called, and callbacks posted to that Handler will be executed on that thread - this call is made by libsuperuser on the thread on which you call #open().

setHandler(yourHandler)

- callbacks executed on thread from yourHandler

setHandler(null) && setAutoHandler(true)

- callbacks executed on thread that called #open()

- if you call #open() from the main thread, your callbacks may block the main thread, which is bad

setHandler(null) && setAutoHandler(false)

- callbacks executed on one of the Gobbler (internal libsuperuser) threads

- Gobbler threads are dangerous to block, could lead to deadlock. Very simple code like setting a synchronized variable based on parsed lines is usually not a problem.

Nowhere do I write that only the main thread has a Looper, but it indeed has one. It's entire operation is built around it. Background threads like AsyncTask#doInBackground() do not generally have a Looper, but it is certainly possible to create a background thread that has one. In fact, there is a special class for this: HandlerThread:

HandlerThread ht = new HandlerThread("threads must have a name");
ht.start();
Handler h = new Handler(ht.getLooper());

.... Shell.Builder#setHandler(h) ...

All callbacks will then be called on the ht thread. You can run your own Runnable's on that thread as well, by using h.post(Runnable).

The exact reasons why are lengthy and complicated, but you should not create many different threads with Loopers, and you should keep them around and re-use them. For example, in most of my own apps, there is the Looper on the main thread, and I have one (two in rare cases) additional HandlerThread to perform all sorts of background operations, such as callbacks I do not want to run on the main thread. While it is technically possible to create a Looper and a Handler in an AsyncTask's thread and use them, this is strongly recommended against.

Due to the asynchronous nature of libsuperuser, it technically unfeasable to call callbacks on a thread directly, this is why Loopers and Handlers are used, which exist exactly for this purpose. It is important to understand that these are basically just a message loop/pump, and messages are processed serially. On the main thread, all your code is already run inside this message loop, this is just hidden from you. Imagine doing this:

public void onStart() {
(new Shell.Builder())
.addCommand("echo test", 0, new Shell.OnCommandResultListener() {
public void onCommandResult(int commandCode, int exitCode, List<String> output) {
// point A
}
})
.open()
// point B
.waitForIdle();
// point C
}

If you want to use results from a command in a procedural fashion, rather than event-based, you will need the #waitForIdle() call.

In this case, a Handler is auto-created on the main thread, and thus the callback (point A) becomes a message in the main thread's pump, once the command is done executing. But, as I mentioned before, all code in your activity is itself run inside a message in the same pump. #waitForIdle() waits for point A to be reached, but the message containing point A will not be run until point C is reached. This is a deadlock situation (A waits for C to happen, while C waits for A to happen).

If your AsyncTask had a Looper and a Handler, and you would run the callbacks on the same thread as the #doInBackground() call, the same thing would happen.

In other words: if you want to use results from a shell call in procedural fashion, you want the callbacks to run on a different thread than the calling code!

Something like this works:

private HandlerThread backgroundHandlerThread;
private Handler backgroundHandler;
private Shell.Interactive shell;

private void ensureShell() {
if (shell != null) return;
backgroundHandlerThread = new HandlerThread("BackgroundHandlerThread");
backgroundHandlerThread.start();
backgroundHandler = new Handler(backgroundHandlerThread.getLooper());
shell = (new Shell.Builder()).useSU().useHandler(backgroundHandler).open();
}

@Override
public void onStart() {
ensureShell();
(new MyTask()).execute();
}

private class MyTask extends AsyncTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
final Boolean[] result = new Boolean[] { false };
shell.addCommand("echo foobar", 0, new Shell.OnCommandResultListener() {
public void onCommandResult(int commandCode, int exitCode, List<String> output) {
for (String line : output) {
if (output.contains("foobar")) {
result[0] = true;
break;
}
}
}
});
shell.waitForIdle();
return result[0];
}
}

This is the most elaborate that I have ever explained this, and I do not think I will comment on it further. Read it (and d.android.com) until you understand it.

Disclaimer: all code from memory, may not actually compile.