result = new Mutable<>();
boolean runWithScissorsSuccess = runWithScissors(mHandler,
() -> result.value = supplier.get(),
RUN_WITH_SCISSORS_TIMEOUT_MILLIS);
if (runWithScissorsSuccess) {
return result.value;
} else {
Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:");
Throwable wifiThreadThrowable = new Throwable("Wifi thread Stack trace:");
wifiThreadThrowable.setStackTrace(mHandler.getLooper().getThread().getStackTrace());
Log.e(TAG, "WifiThreadRunner.call() timed out!", callerThreadThrowable);
Log.e(TAG, "WifiThreadRunner.call() timed out!", wifiThreadThrowable);
if (mTimeoutsAreErrors) {
throw new RuntimeException("WifiThreadRunner.call() timed out!");
}
return valueToReturnOnTimeout;
}
}
/**
* Runs a Runnable on the main Wifi thread and blocks the calling thread until the
* Runnable completes execution on the main Wifi thread.
*
* BEWARE OF DEADLOCKS!!!
*
* @return true if the runnable executed successfully, false otherwise
*/
public boolean run(@NonNull Runnable runnable) {
boolean runWithScissorsSuccess =
runWithScissors(mHandler, runnable, RUN_WITH_SCISSORS_TIMEOUT_MILLIS);
if (runWithScissorsSuccess) {
return true;
} else {
Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:");
Throwable wifiThreadThrowable = new Throwable("Wifi thread Stack trace:");
wifiThreadThrowable.setStackTrace(mHandler.getLooper().getThread().getStackTrace());
Log.e(TAG, "WifiThreadRunner.run() timed out!", callerThreadThrowable);
Log.e(TAG, "WifiThreadRunner.run() timed out!", wifiThreadThrowable);
if (mTimeoutsAreErrors) {
throw new RuntimeException("WifiThreadRunner.run() timed out!");
}
return false;
}
}
/**
* Sets whether or not a RuntimeError should be thrown when a timeout occurs.
*
* For test purposes only!
*/
@VisibleForTesting
public void setTimeoutsAreErrors(boolean timeoutsAreErrors) {
Log.d(TAG, "setTimeoutsAreErrors " + timeoutsAreErrors);
mTimeoutsAreErrors = timeoutsAreErrors;
}
/**
* Prepares to run on a different thread.
*
* Useful when TestLooper#startAutoDispatch is used to test code that uses #call or #run,
* because in this case the messages are dispatched by a thread that is not actually the
* thread associated with the looper. Should be called before each call
* to TestLooper#startAutoDispatch, without intervening calls to other TestLooper dispatch
* methods.
*
* For test purposes only!
*/
@VisibleForTesting
public void prepareForAutoDispatch() {
mHandler.postAtFrontOfQueue(() -> {
mDispatchThread = Thread.currentThread();
});
}
/**
* Asynchronously runs a Runnable on the main Wifi thread.
*
* @return true if the runnable was successfully posted (not executed) to the main Wifi
* thread, false otherwise
*/
public boolean post(@NonNull Runnable runnable) {
return mHandler.post(runnable);
}
/**
* Asynchronously runs a Runnable on the main Wifi thread with delay.
*
* @param runnable The Runnable that will be executed.
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
* @return true if the runnable was successfully posted (not executed) to the main Wifi
* thread, false otherwise
*/
public boolean postDelayed(@NonNull Runnable runnable, long delayMillis) {
return mHandler.postDelayed(runnable, delayMillis);
}
/**
* Remove any pending posts of Runnable r that are in the message queue.
*
* @param r The Runnable that will be removed.
*/
public final void removeCallbacks(@NonNull Runnable r) {
mHandler.removeCallbacks(r);
}
// Note: @hide methods copied from android.os.Handler
/**
* Runs the specified task synchronously.
*
* If the current thread is the same as the handler thread, then the runnable
* runs immediately without being enqueued. Otherwise, posts the runnable
* to the handler and waits for it to complete before returning.
*
* This method is dangerous! Improper use can result in deadlocks.
* Never call this method while any locks are held or use it in a
* possibly re-entrant manner.
*
* This method is occasionally useful in situations where a background thread
* must synchronously await completion of a task that must run on the
* handler's thread. However, this problem is often a symptom of bad design.
* Consider improving the design (if possible) before resorting to this method.
*
* One example of where you might want to use this method is when you just
* set up a Handler thread and need to perform some initialization steps on
* it before continuing execution.
*
* If timeout occurs then this method returns false
but the runnable
* will remain posted on the handler and may already be in progress or
* complete at a later time.
*
* When using this method, be sure to use {@link Looper#quitSafely} when
* quitting the looper. Otherwise {@link #runWithScissors} may hang indefinitely.
* (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
*
*
* @param r The Runnable that will be executed synchronously.
* @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
*
* @return Returns true if the Runnable was successfully executed.
* Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @hide This method is prone to abuse and should probably not be in the API.
* If we ever do make it part of the API, we might want to rename it to something
* less funny like runUnsafe().
*/
private boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r,
long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
if (Looper.myLooper() == handler.getLooper()) {
r.run();
return true;
}
if (Thread.currentThread() == mDispatchThread) {
r.run();
return true;
}
BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(handler, timeout);
}
private static final class BlockingRunnable implements Runnable {
private final Runnable mTask;
private boolean mDone;
BlockingRunnable(Runnable task) {
mTask = task;
}
@Override
public void run() {
try {
mTask.run();
} finally {
synchronized (this) {
mDone = true;
notifyAll();
}
}
}
public boolean postAndWait(Handler handler, long timeout) {
if (!handler.post(this)) {
return false;
}
synchronized (this) {
if (timeout > 0) {
final long expirationTime = SystemClock.uptimeMillis() + timeout;
while (!mDone) {
long delay = expirationTime - SystemClock.uptimeMillis();
if (delay <= 0) {
return false; // timeout
}
try {
wait(delay);
} catch (InterruptedException ex) {
}
}
} else {
while (!mDone) {
try {
wait();
} catch (InterruptedException ex) {
}
}
}
}
return true;
}
}
}