/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.stetho.common.android;
import android.os.Handler;
import android.os.Looper;
import com.facebook.stetho.common.UncheckedCallable;
import com.facebook.stetho.common.Util;
public final class HandlerUtil {
private HandlerUtil() {
}
/**
* Checks whether the current thread is the same thread that the {@link Handler} is associated
* with.
* @return true if the current thread is the same thread that the {@link Handler} is associated
* with; otherwise false.
*/
public static boolean checkThreadAccess(Handler handler) {
return Looper.myLooper() == handler.getLooper();
}
/**
* Enforces that the current thread is the same thread that the {@link Handler} is associated
* with.
* @throws IllegalStateException if the current thread is not the same thread that the
* {@link Handler} is associated with.
*/
public static void verifyThreadAccess(Handler handler) {
Util.throwIfNot(checkThreadAccess(handler));
}
/**
* Synchronously executes an {@link UncheckedCallable} on the thread that this Handler is
* associated with, and returns its result.
* @param c the {@link UncheckedCallable} to execute
* @param <V> the return type of the {@link UncheckedCallable}
* @return the return value from {@link UncheckedCallable#call()}
* @throws RuntimeException if the {@link UncheckedCallable} could not be executed (the cause
* will be null), or if {@link UncheckedCallable#call()} threw an exception (the cause will be the
* exception that it threw).
*/
public static <V> V postAndWait(Handler handler, final UncheckedCallable<V> c) {
if (checkThreadAccess(handler)) {
try {
return c.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
WaitableRunnable<V> wrapper = new WaitableRunnable<V>() {
@Override
protected V onRun() {
return c.call();
}
};
return wrapper.invoke(handler);
}
/**
* Synchronously executes a {@link Runnable} on the thread that this Handler is associated with.
* @param r the {@link Runnable} to execute
* @throws RuntimeException if the {@link Runnable} could not be executed (the cause will be
* null), or if {@link Runnable#run()} threw an exception (the cause will be the exception that
* it threw).
*/
public static void postAndWait(Handler handler, final Runnable r) {
if (checkThreadAccess(handler)) {
try {
r.run();
return;
} catch (RuntimeException e) {
throw new RuntimeException(e);
}
}
WaitableRunnable<Void> wrapper = new WaitableRunnable<Void>() {
@Override
protected Void onRun() {
r.run();
return null;
}
};
wrapper.invoke(handler);
}
private static abstract class WaitableRunnable<V> implements Runnable {
private boolean mIsDone;
private V mValue;
private Exception mException;
protected WaitableRunnable() {
}
@Override
public final void run() {
try {
mValue = onRun();
mException = null;
} catch (Exception e) {
mValue = null;
mException = e;
} finally {
synchronized (this) {
mIsDone = true;
notifyAll();
}
}
}
protected abstract V onRun();
public V invoke(Handler handler) {
if (!handler.post(this)) {
throw new RuntimeException("Handler.post() returned false");
}
join();
if (mException != null) {
throw new RuntimeException(mException);
}
return mValue;
}
private void join() {
synchronized (this) {
while (!mIsDone) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
}
}
}