am b9cf8750: Merge "runOnMainSync on a single thread"

* commit 'b9cf8750ebf4394555f9ef801b2114d4901682a5':
  runOnMainSync on a single thread
diff --git a/src/io/appium/droiddriver/base/BaseUiElement.java b/src/io/appium/droiddriver/base/BaseUiElement.java
index 293ee9c..e98ee44 100644
--- a/src/io/appium/droiddriver/base/BaseUiElement.java
+++ b/src/io/appium/droiddriver/base/BaseUiElement.java
@@ -26,7 +26,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
 
 import io.appium.droiddriver.UiElement;
@@ -201,14 +200,8 @@
 
     try {
       return futureTask.get();
-    } catch (ExecutionException e) {
-      Throwable cause = e.getCause();
-      if (cause instanceof RuntimeException) {
-        throw (RuntimeException) cause;
-      }
-      throw new DroidDriverException(cause);
-    } catch (InterruptedException e) {
-      throw new DroidDriverException(e);
+    } catch (Throwable t) {
+      throw DroidDriverException.propagate(t);
     }
   }
 
diff --git a/src/io/appium/droiddriver/exceptions/DroidDriverException.java b/src/io/appium/droiddriver/exceptions/DroidDriverException.java
index e7ba2b7..482130f 100644
--- a/src/io/appium/droiddriver/exceptions/DroidDriverException.java
+++ b/src/io/appium/droiddriver/exceptions/DroidDriverException.java
@@ -34,4 +34,37 @@
   public DroidDriverException(String message, Throwable cause) {
     super(message, cause);
   }
+
+  /**
+   * Adapted from <a href="http://guava-libraries.googlecode.com">Guava libraries</a>. <p>
+   * Propagates {@code throwable} as-is if it is an instance of {@link RuntimeException} or {@link
+   * Error}, or else as a last resort, wraps it in a {@code DroidDriverException} and then
+   * propagates. <p> This method always throws an exception. The {@code DroidDriverException} return
+   * type is only for client code to make Java type system happy in case a return value is required
+   * by the enclosing method. Example usage:
+   * <pre>
+   *   T doSomething() {
+   *     try {
+   *       return someMethodThatCouldThrowAnything();
+   *     } catch (IKnowWhatToDoWithThisException e) {
+   *       return handle(e);
+   *     } catch (Throwable t) {
+   *       throw DroidDriverException.propagate(t);
+   *     }
+   *   }
+   * </pre>
+   *
+   * @param throwable the Throwable to propagate
+   * @return nothing will ever be returned; this return type is only for your convenience, as
+   * illustrated in the example above
+   */
+  public static DroidDriverException propagate(Throwable throwable) {
+    if (throwable instanceof RuntimeException) {
+      throw (RuntimeException) throwable;
+    }
+    if (throwable instanceof Error) {
+      throw (Error) throwable;
+    }
+    throw new DroidDriverException(throwable);
+  }
 }
diff --git a/src/io/appium/droiddriver/helpers/DroidDrivers.java b/src/io/appium/droiddriver/helpers/DroidDrivers.java
index 5cddb4f..7725bf5 100644
--- a/src/io/appium/droiddriver/helpers/DroidDrivers.java
+++ b/src/io/appium/droiddriver/helpers/DroidDrivers.java
@@ -20,8 +20,6 @@
 import android.app.Instrumentation;
 import android.os.Build;
 
-import java.lang.reflect.InvocationTargetException;
-
 import io.appium.droiddriver.DroidDriver;
 import io.appium.droiddriver.exceptions.DroidDriverException;
 import io.appium.droiddriver.instrumentation.InstrumentationDriver;
@@ -77,18 +75,8 @@
       try {
         return (DroidDriver) Class.forName(driverClass).getConstructor(Instrumentation.class)
             .newInstance(instrumentation);
-      } catch (ClassNotFoundException e) {
-        throw new DroidDriverException(e);
-      } catch (NoSuchMethodException e) {
-        throw new DroidDriverException(e);
-      } catch (InstantiationException e) {
-        throw new DroidDriverException(e);
-      } catch (IllegalAccessException e) {
-        throw new DroidDriverException(e);
-      } catch (IllegalArgumentException e) {
-        throw new DroidDriverException(e);
-      } catch (InvocationTargetException e) {
-        throw new DroidDriverException(e);
+      } catch (Throwable t) {
+        throw DroidDriverException.propagate(t);
       }
     }
 
diff --git a/src/io/appium/droiddriver/util/InstrumentationUtils.java b/src/io/appium/droiddriver/util/InstrumentationUtils.java
index a019008..95eb48c 100644
--- a/src/io/appium/droiddriver/util/InstrumentationUtils.java
+++ b/src/io/appium/droiddriver/util/InstrumentationUtils.java
@@ -22,6 +22,8 @@
 import android.util.Log;
 
 import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
@@ -41,6 +43,7 @@
     public void run() {
     }
   };
+  private static final Executor RUN_ON_MAIN_SYNC_EXECUTOR = Executors.newSingleThreadExecutor();
 
   /**
    * Initializes this class. If you use a runner that is not DroidDriver-aware, you need to call
@@ -112,8 +115,8 @@
       Logs.log(Log.INFO,
           "Timed out after " + timeoutMillis + " milliseconds waiting for idle on main looper");
       return false;
-    } catch (Throwable e) {
-      throw new DroidDriverException(e);
+    } catch (Throwable t) {
+      throw DroidDriverException.propagate(t);
     }
     return true;
   }
@@ -132,12 +135,10 @@
    * Runs {@code callable} on the main thread on best-effort basis up to a time limit, which
    * defaults to {@code 10000L} and can be set as an am instrument option under the key {@code
    * dd.runOnMainSyncTimeout}. <p>This is a safer variation of {@link Instrumentation#runOnMainSync}
-   * because the latter may hang. But it is heavy because a new thread is created for each call. You
-   * may turn off this behavior by setting {@code "-e dd.runOnMainSyncTimeout 0"} on the am command
-   * line.</p>The {@code callable} may never run, for example, in case that the main Looper has
-   * exited due to uncaught exception.
+   * because the latter may hang. You may turn off this behavior by setting {@code "-e
+   * dd.runOnMainSyncTimeout 0"} on the am command line.</p>The {@code callable} may never run, for
+   * example, if the main Looper has exited due to uncaught exception.
    */
-  // TODO: call runOnMainSync on a single worker thread?
   public static <V> V runOnMainSyncWithTimeout(Callable<V> callable) {
     validateNotAppThread();
     final RunOnMainSyncFutureTask<V> futureTask = new RunOnMainSyncFutureTask<>(callable);
@@ -146,12 +147,12 @@
       // Call runOnMainSync on current thread without time limit.
       futureTask.runOnMainSyncNoThrow();
     } else {
-      new Thread() {
+      RUN_ON_MAIN_SYNC_EXECUTOR.execute(new Runnable() {
         @Override
         public void run() {
           futureTask.runOnMainSyncNoThrow();
         }
-      }.start();
+      });
     }
 
     try {
@@ -159,8 +160,8 @@
     } catch (java.util.concurrent.TimeoutException e) {
       throw new TimeoutException("Timed out after " + runOnMainSyncTimeoutMillis
           + " milliseconds waiting for Instrumentation.runOnMainSync", e);
-    } catch (Throwable e) {
-      throw new DroidDriverException(e);
+    } catch (Throwable t) {
+      throw DroidDriverException.propagate(t);
     } finally {
       futureTask.cancel(false);
     }