Fix @Test(timeout) so it still runs tests on the main sandbox thread.
diff --git a/junit/src/main/java/org/robolectric/internal/SandboxTestRunner.java b/junit/src/main/java/org/robolectric/internal/SandboxTestRunner.java
index 1ffefbb..bc38f07 100644
--- a/junit/src/main/java/org/robolectric/internal/SandboxTestRunner.java
+++ b/junit/src/main/java/org/robolectric/internal/SandboxTestRunner.java
@@ -13,6 +13,8 @@
 import javax.annotation.Nonnull;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.internal.runners.statements.FailOnTimeout;
 import org.junit.runner.notification.RunNotifier;
 import org.junit.runners.BlockJUnit4ClassRunner;
 import org.junit.runners.model.FrameworkMethod;
@@ -196,7 +198,7 @@
   }
 
   @Override protected Statement methodBlock(final FrameworkMethod method) {
-    return withPotentialTimeoutAroundSandboxThread(method, null, new Statement() {
+    return new Statement() {
       @Override
       public void evaluate() throws Throwable {
         PerfStatsCollector perfStatsCollector = PerfStatsCollector.getInstance();
@@ -212,57 +214,56 @@
         // not available once we install the Robolectric class loader.
         configureSandbox(sandbox, method);
 
-        final ClassLoader priorContextClassLoader = Thread.currentThread().getContextClassLoader();
-        Thread.currentThread().setContextClassLoader(sandbox.getRobolectricClassLoader());
-
-        Class bootstrappedTestClass = sandbox.bootstrappedClass(getTestClass().getJavaClass());
-        HelperTestRunner helperTestRunner = getHelperTestRunner(bootstrappedTestClass);
-        helperTestRunner.frameworkMethod = method;
-
-        final Method bootstrappedMethod;
-        try {
-          //noinspection unchecked
-          bootstrappedMethod = bootstrappedTestClass.getMethod(method.getMethod().getName());
-        } catch (NoSuchMethodException e) {
-          throw new RuntimeException(e);
-        }
-
         sandbox.runOnMainThread(() -> {
+          ClassLoader priorContextClassLoader = Thread.currentThread().getContextClassLoader();
+          Thread.currentThread().setContextClassLoader(sandbox.getRobolectricClassLoader());
+
+          Class bootstrappedTestClass =
+              sandbox.bootstrappedClass(getTestClass().getJavaClass());
+          HelperTestRunner helperTestRunner = getHelperTestRunner(bootstrappedTestClass);
+          helperTestRunner.frameworkMethod = method;
+
+          final Method bootstrappedMethod;
           try {
+            //noinspection unchecked
+            bootstrappedMethod = bootstrappedTestClass.getMethod(method.getMethod().getName());
+          } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+          }
+
+          try {
+            // Only invoke @BeforeClass once per class
+            invokeBeforeClass(bootstrappedTestClass);
+
+            beforeTest(sandbox, method, bootstrappedMethod);
+
+            initialization.finished();
+
+            Statement statement =
+                helperTestRunner.methodBlock(new FrameworkMethod(bootstrappedMethod));
+
+            // todo: this try/finally probably isn't right -- should mimic RunAfters? [xw]
             try {
-              // Only invoke @BeforeClass once per class
-              invokeBeforeClass(bootstrappedTestClass);
-
-              beforeTest(sandbox, method, bootstrappedMethod);
-
-              initialization.finished();
-
-              final Statement statement = helperTestRunner.methodBlock(new FrameworkMethod(bootstrappedMethod));
-
-              // todo: this try/finally probably isn't right -- should mimic RunAfters? [xw]
-              try {
-                statement.evaluate();
-              } finally {
-                afterTest(method, bootstrappedMethod);
-              }
+              statement.evaluate();
             } finally {
-              Thread.currentThread().setContextClassLoader(priorContextClassLoader);
-
-              try {
-                finallyAfterTest(method);
-              } catch (Exception e) {
-                e.printStackTrace();
-              }
+              afterTest(method, bootstrappedMethod);
             }
           } catch (Throwable throwable) {
             UnsafeAccess.throwException(throwable);
+          } finally {
+            Thread.currentThread().setContextClassLoader(priorContextClassLoader);
+            try {
+              finallyAfterTest(method);
+            } catch (Exception e) {
+              e.printStackTrace();
+            }
           }
         });
 
         reportPerfStats(perfStatsCollector);
         perfStatsCollector.reset();
       }
-    });
+    };
   }
 
   private void reportPerfStats(PerfStatsCollector perfStatsCollector) {
@@ -306,21 +307,47 @@
       super(klass);
     }
 
-    // cuz accessibility
+    // for visibility from SandboxTestRunner.methodBlock()
     @Override
     protected Statement methodBlock(FrameworkMethod method) {
       return super.methodBlock(method);
     }
 
     /**
+     * For tests with a timeout, we need to wrap the test method execution (but not befores or
+     * afters) in a {@link TimeLimitedStatement}. We can't use JUnit's built-in
+     * {@link FailOnTimeout} statement because it causes the test to be run on a new thread, but
+     * tests should be run on the sandbox's main thread in all cases.
+     */
+    @Override
+    protected Statement methodInvoker(FrameworkMethod method, Object test) {
+      Statement delegate = super.methodInvoker(method, test);
+      long timeout = getTimeout(method.getAnnotation(Test.class));
+
+      if (timeout == 0) {
+        return delegate;
+      } else {
+        return new TimeLimitedStatement(timeout, delegate);
+      }
+    }
+
+    /**
      * Disables JUnit's normal timeout mode.
      *
-     * @see #withPotentialTimeoutAroundSandboxThread(FrameworkMethod, Object, Statement)
+     * @see TimeLimitedStatement
      */
     @Override
     protected Statement withPotentialTimeout(FrameworkMethod method, Object test, Statement next) {
       return next;
     }
+
+    private long getTimeout(Test annotation) {
+      if (annotation == null) {
+        return 0;
+      }
+      return annotation.timeout();
+    }
+
   }
 
   @Nonnull
@@ -347,19 +374,9 @@
   }
 
   /**
-   * For tests with a timeout, we need to wrap the test execution in a FailOnTimeout statement
-   * *before* we switch to the sandbox main thread, otherwise tests will be running on
-   * FailOnTimeout's thread instead of the sandbox main thread.
-   */
-  protected Statement withPotentialTimeoutAroundSandboxThread(FrameworkMethod method,
-      Object test, Statement next) {
-    return super.withPotentialTimeout(method, test, next);
-  }
-
-  /**
    * Disables JUnit's normal timeout mode.
    *
-   * @see #withPotentialTimeoutAroundSandboxThread(FrameworkMethod, Object, Statement)
+   * @see TimeLimitedStatement
    */
   protected Statement withPotentialTimeout(FrameworkMethod method, Object test, Statement next) {
     return next;
diff --git a/junit/src/main/java/org/robolectric/internal/TimeLimitedStatement.java b/junit/src/main/java/org/robolectric/internal/TimeLimitedStatement.java
new file mode 100644
index 0000000..53cdf98
--- /dev/null
+++ b/junit/src/main/java/org/robolectric/internal/TimeLimitedStatement.java
@@ -0,0 +1,48 @@
+package org.robolectric.internal;
+
+import java.util.concurrent.TimeUnit;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestTimedOutException;
+
+/**
+ * Similar to JUnit's {@link org.junit.internal.runners.statements.FailOnTimeout}, but runs the
+ * test on the current thread (with a timer on a new thread) rather than the other way around.
+ */
+class TimeLimitedStatement extends Statement {
+
+  private final long timeout;
+  private final Statement delegate;
+
+  public TimeLimitedStatement(long timeout, Statement delegate) {
+    this.timeout = timeout;
+    this.delegate = delegate;
+  }
+
+  @Override
+  public void evaluate() throws Throwable {
+    Thread testThread = Thread.currentThread();
+    Thread timeoutThread =
+        new Thread(
+            () -> {
+              try {
+                Thread.sleep(timeout);
+                testThread.interrupt();
+              } catch (InterruptedException e) {
+                // ok
+              }
+            },
+            "Robolectric time-limited test");
+    timeoutThread.start();
+
+    try {
+      delegate.evaluate();
+    } catch (InterruptedException e) {
+      Exception e2 = new TestTimedOutException(timeout, TimeUnit.MILLISECONDS);
+      e2.setStackTrace(e.getStackTrace());
+      throw e2;
+    } finally {
+      timeoutThread.interrupt();
+      timeoutThread.join();
+    }
+  }
+}
diff --git a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
index 802ebe8..254be7e 100644
--- a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
+++ b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
@@ -541,17 +541,17 @@
   /**
    * Fields in this class must be serializable using [XStream](https://x-stream.github.io/).
    */
-  static class RobolectricFrameworkMethod extends FrameworkMethod {
+  static final class RobolectricFrameworkMethod extends FrameworkMethod {
 
     private static final AtomicInteger NEXT_ID = new AtomicInteger();
     private static final Map<Integer, TestExecutionContext> CONTEXT = new HashMap<>();
     
     private final int id;
 
-    private final @Nonnull AndroidManifest appManifest;
-    private final @Nonnull Configuration configuration;
-    private final @Nonnull ResourcesMode resourcesMode;
-    private final @Nonnull ResModeStrategy defaultResModeStrategy;
+    @Nonnull private final AndroidManifest appManifest;
+    @Nonnull private final Configuration configuration;
+    @Nonnull private final ResourcesMode resourcesMode;
+    @Nonnull private final ResModeStrategy defaultResModeStrategy;
     private final boolean alwaysIncludeVariantMarkersInName;
 
     private boolean includeVariantMarkersInTestName = true;
@@ -620,7 +620,8 @@
     }
 
     Environment getEnvironment() {
-      return getContext().environment;
+      TestExecutionContext context = getContext();
+      return context == null ? null : context.environment;
     }
 
     public boolean isLegacy() {
diff --git a/robolectric/src/main/java/org/robolectric/android/internal/AndroidEnvironment.java b/robolectric/src/main/java/org/robolectric/android/internal/AndroidEnvironment.java
old mode 100755
new mode 100644
index c50609d..d284d9c
--- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidEnvironment.java
+++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidEnvironment.java
@@ -87,8 +87,8 @@
   private final int apiLevel;
 
   private boolean loggingInitialized = false;
-  private Path sdkJarPath;
-  private ApkLoader apkLoader;
+  private final Path sdkJarPath;
+  private final ApkLoader apkLoader;
   private PackageResourceTable systemResourceTable;
 
   public AndroidEnvironment(
@@ -394,7 +394,9 @@
   static String getTestApplicationName(String applicationName) {
     int lastDot = applicationName.lastIndexOf('.');
     if (lastDot > -1) {
-      return applicationName.substring(0, lastDot) + ".Test" + applicationName.substring(lastDot + 1);
+      return applicationName.substring(0, lastDot)
+          + ".Test"
+          + applicationName.substring(lastDot + 1);
     } else {
       return "Test" + applicationName;
     }
@@ -424,7 +426,9 @@
    * Create a file system safe directory path name for the current test.
    */
   private String createTestDataDirRootPath(Method method) {
-    return method.getClass().getSimpleName() + "_" + method.getName().replaceAll("[^a-zA-Z0-9.-]", "_");
+    return method.getClass().getSimpleName()
+        + "_"
+        + method.getName().replaceAll("[^a-zA-Z0-9.-]", "_");
   }
 
   @Override
diff --git a/robolectric/src/main/java/org/robolectric/internal/AndroidSandbox.java b/robolectric/src/main/java/org/robolectric/internal/AndroidSandbox.java
old mode 100755
new mode 100644
diff --git a/robolectric/src/main/java/org/robolectric/internal/SandboxFactory.java b/robolectric/src/main/java/org/robolectric/internal/SandboxFactory.java
index e0e178a..f483429 100644
--- a/robolectric/src/main/java/org/robolectric/internal/SandboxFactory.java
+++ b/robolectric/src/main/java/org/robolectric/internal/SandboxFactory.java
@@ -129,7 +129,7 @@
       if (this == o) {
         return true;
       }
-      if (o == null || getClass() != o.getClass()) {
+      if (!(o instanceof SandboxKey)) {
         return false;
       }
       SandboxKey that = (SandboxKey) o;
diff --git a/robolectric/src/test/java/org/robolectric/BootstrapDeferringRobolectricTestRunner.java b/robolectric/src/test/java/org/robolectric/BootstrapDeferringRobolectricTestRunner.java
index abfffc2..4b039f0 100644
--- a/robolectric/src/test/java/org/robolectric/BootstrapDeferringRobolectricTestRunner.java
+++ b/robolectric/src/test/java/org/robolectric/BootstrapDeferringRobolectricTestRunner.java
@@ -143,6 +143,7 @@
       return wrapped;
     }
 
+    @Override
     public void callSetUpApplicationState() {
       wrapped.setUpApplicationState(method, config, appManifest);
     }
diff --git a/robolectric/src/test/java/org/robolectric/android/internal/AndroidEnvironmentCreateApplicationTest.java b/robolectric/src/test/java/org/robolectric/android/internal/AndroidEnvironmentCreateApplicationTest.java
index 0281fbd..65a8cc9 100644
--- a/robolectric/src/test/java/org/robolectric/android/internal/AndroidEnvironmentCreateApplicationTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/internal/AndroidEnvironmentCreateApplicationTest.java
@@ -32,27 +32,33 @@
   @Test(expected = RuntimeException.class)
   public void shouldThrowWhenManifestContainsBadApplicationClassName() throws Exception {
     AndroidEnvironment.createApplication(
-        newConfigWith("<application android:name=\"org.robolectric.BogusTestApplication\"/>)"), null);
+        newConfigWith("<application android:name=\"org.robolectric.BogusTestApplication\"/>)"),
+        null);
   }
 
   @Test
-  public void shouldReturnDefaultAndroidApplicationWhenManifestDeclaresNoAppName() throws Exception {
-    assertThat(AndroidEnvironment.createApplication(newConfigWith(""), null))
-        .isInstanceOf(Application.class);
+  public void shouldReturnDefaultAndroidApplicationWhenManifestDeclaresNoAppName()
+      throws Exception {
+    Application application = AndroidEnvironment.createApplication(newConfigWith(""), null);
+    assertThat(application.getClass()).isEqualTo(Application.class);
   }
 
   @Test
   public void shouldReturnSpecifiedApplicationWhenManifestDeclaresAppName() throws Exception {
-    assertThat(AndroidEnvironment.createApplication(
-        newConfigWith("<application android:name=\"org.robolectric.shadows.testing.TestApplication\"/>"), null))
-        .isInstanceOf(TestApplication.class);
+    Application application =
+        AndroidEnvironment.createApplication(
+            newConfigWith(
+                "<application android:name=\"org.robolectric.shadows.testing.TestApplication\"/>"),
+            null);
+    assertThat(application.getClass()).isEqualTo(TestApplication.class);
   }
 
-  @Test public void shouldAssignThePackageNameFromTheManifest() throws Exception {
+  @Test
+  public void shouldAssignThePackageNameFromTheManifest() throws Exception {
     Application application = ApplicationProvider.getApplicationContext();
 
     assertThat(application.getPackageName()).isEqualTo("org.robolectric");
-    assertThat(application).isInstanceOf(TestApplication.class);
+    assertThat(application.getClass()).isEqualTo(TestApplication.class);
   }
 
   @Test
@@ -62,51 +68,65 @@
         .getRegisteredReceivers()
         .clear();
 
-    AndroidManifest appManifest = newConfigWith(
-        "<application>"
-            + "    <receiver android:name=\"org.robolectric.fakes.ConfigTestReceiver\">"
-            + "      <intent-filter>\n"
-            + "        <action android:name=\"org.robolectric.ACTION_SUPERSET_PACKAGE\"/>\n"
-            + "      </intent-filter>"
-            + "    </receiver>"
-            + "</application>");
+    AndroidManifest appManifest =
+        newConfigWith(
+            "<application>"
+                + "    <receiver android:name=\"org.robolectric.fakes.ConfigTestReceiver\">"
+                + "      <intent-filter>\n"
+                + "        <action android:name=\"org.robolectric.ACTION_SUPERSET_PACKAGE\"/>\n"
+                + "      </intent-filter>"
+                + "    </receiver>"
+                + "</application>");
     Application application = AndroidEnvironment.createApplication(appManifest, null);
     shadowOf(application).callAttach(RuntimeEnvironment.systemContext);
     registerBroadcastReceivers(application, appManifest);
 
     List<ShadowApplication.Wrapper> receivers = shadowOf(application).getRegisteredReceivers();
     assertThat(receivers).hasSize(1);
-    assertThat(receivers.get(0).intentFilter.matchAction("org.robolectric.ACTION_SUPERSET_PACKAGE")).isTrue();
+    assertThat(receivers.get(0).intentFilter.matchAction("org.robolectric.ACTION_SUPERSET_PACKAGE"))
+        .isTrue();
   }
 
-  @Test public void shouldDoTestApplicationNameTransform() throws Exception {
-    assertThat(AndroidEnvironment.getTestApplicationName(".Applicationz")).isEqualTo(".TestApplicationz");
-    assertThat(AndroidEnvironment.getTestApplicationName("Applicationz")).isEqualTo("TestApplicationz");
-    assertThat(AndroidEnvironment.getTestApplicationName("com.foo.Applicationz")).isEqualTo("com.foo.TestApplicationz");
+  @Test
+  public void shouldDoTestApplicationNameTransform() throws Exception {
+    assertThat(AndroidEnvironment.getTestApplicationName(".Applicationz"))
+        .isEqualTo(".TestApplicationz");
+    assertThat(AndroidEnvironment.getTestApplicationName("Applicationz"))
+        .isEqualTo("TestApplicationz");
+    assertThat(AndroidEnvironment.getTestApplicationName("com.foo.Applicationz"))
+        .isEqualTo("com.foo.TestApplicationz");
   }
 
-  @Test public void shouldLoadConfigApplicationIfSpecified() throws Exception {
-    Application application = AndroidEnvironment.createApplication(
-        newConfigWith("<application android:name=\"" + "ClassNameToIgnore" + "\"/>"),
-        new Config.Builder().setApplication(TestFakeApp.class).build());
-    assertThat(application).isInstanceOf(TestFakeApp.class);
+  @Test
+  public void shouldLoadConfigApplicationIfSpecified() throws Exception {
+    Application application =
+        AndroidEnvironment.createApplication(
+            newConfigWith("<application android:name=\"" + "ClassNameToIgnore" + "\"/>"),
+            new Config.Builder().setApplication(TestFakeApp.class).build());
+    assertThat(application.getClass()).isEqualTo(TestFakeApp.class);
   }
 
-  @Test public void shouldLoadConfigInnerClassApplication() throws Exception {
-    Application application = AndroidEnvironment.createApplication(
-        newConfigWith("<application android:name=\"" + "ClassNameToIgnore" + "\"/>"),
-        new Config.Builder().setApplication(TestFakeAppInner.class).build());
-    assertThat(application).isInstanceOf(TestFakeAppInner.class);
+  @Test
+  public void shouldLoadConfigInnerClassApplication() throws Exception {
+    Application application =
+        AndroidEnvironment.createApplication(
+            newConfigWith("<application android:name=\"" + "ClassNameToIgnore" + "\"/>"),
+            new Config.Builder().setApplication(TestFakeAppInner.class).build());
+    assertThat(application.getClass()).isEqualTo(TestFakeAppInner.class);
   }
 
-  @Test public void shouldLoadTestApplicationIfClassIsPresent() throws Exception {
-    Application application = AndroidEnvironment.createApplication(
-        newConfigWith("<application android:name=\"" + FakeApp.class.getName() + "\"/>"), null);
-    assertThat(application).isInstanceOf(TestFakeApp.class);
+  @Test
+  public void shouldLoadTestApplicationIfClassIsPresent() throws Exception {
+    Application application =
+        AndroidEnvironment.createApplication(
+            newConfigWith("<application android:name=\"" + FakeApp.class.getName() + "\"/>"), null);
+    assertThat(application.getClass()).isEqualTo(TestFakeApp.class);
   }
 
-  @Test public void whenNoAppManifestPresent_shouldCreateGenericApplication() throws Exception {
-    assertThat(AndroidEnvironment.createApplication(null, null)).isInstanceOf(Application.class);
+  @Test
+  public void whenNoAppManifestPresent_shouldCreateGenericApplication() throws Exception {
+    Application application = AndroidEnvironment.createApplication(null, null);
+    assertThat(application.getClass()).isEqualTo(Application.class);
   }
 
   /////////////////////////////
@@ -116,16 +136,21 @@
   }
 
   private AndroidManifest newConfigWith(String packageName, String contents) throws IOException {
-    String fileContents = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
-        "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
-        "          package=\"" + packageName + "\">\n" +
-        "    " + contents + "\n" +
-        "</manifest>\n";
+    String fileContents =
+        "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+            + "          package=\""
+            + packageName
+            + "\">\n"
+            + "    "
+            + contents
+            + "\n"
+            + "</manifest>\n";
     File f = temporaryFolder.newFile("whatever.xml");
 
     Files.asCharSink(f, Charsets.UTF_8).write(fileContents);
     return new AndroidManifest(f.toPath(), null, null);
   }
 
-  public static class TestFakeAppInner extends Application { }
+  public static class TestFakeAppInner extends Application {}
 }
diff --git a/robolectric/src/test/java/org/robolectric/android/internal/AndroidEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/android/internal/AndroidEnvironmentTest.java
index c759f2e..15bfe90 100644
--- a/robolectric/src/test/java/org/robolectric/android/internal/AndroidEnvironmentTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/internal/AndroidEnvironmentTest.java
@@ -4,8 +4,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 
 import android.app.Application;
 import android.content.pm.ApplicationInfo;
@@ -16,6 +14,8 @@
 import java.io.File;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -94,7 +94,7 @@
           res.set(RuntimeEnvironment.isMainThread());
         });
     t.start();
-    t.join(0);
+    t.join();
     assertThat(res.get()).isTrue();
     assertThat(RuntimeEnvironment.isMainThread()).isFalse();
   }
@@ -165,9 +165,17 @@
 
   @Test
   public void tearDownApplication_invokesOnTerminate() {
-    RuntimeEnvironment.application = mock(Application.class);
+    List<String> events = new ArrayList<>();
+    RuntimeEnvironment.application =
+        new Application() {
+          @Override
+          public void onTerminate() {
+            super.onTerminate();
+            events.add("terminated");
+          }
+        };
     bootstrapWrapper.tearDownApplication();
-    verify(RuntimeEnvironment.application).onTerminate();
+    assertThat(events).containsExactly("terminated");
   }
 
   @Test
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java
index e09d92d..2de6db0 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java
@@ -8,6 +8,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.ThreadFactory;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.util.reflector.UnsafeAccess;
@@ -76,23 +77,22 @@
   }
 
   public void runOnMainThread(Runnable runnable) {
-    try {
-      executorService.submit(runnable).get();
-    } catch (InterruptedException e) {
-      throw new RuntimeException(e);
-    } catch (ExecutionException e) {
-      UnsafeAccess.throwException(e.getCause());
-    }
+    runOnMainThread(() -> {
+      runnable.run();
+      return null;
+    });
   }
 
   public <T> T runOnMainThread(Callable<T> callable) {
+    Future<T> future = executorService.submit(callable);
     try {
-      return executorService.submit(callable).get();
+      return future.get();
     } catch (InterruptedException e) {
+      future.cancel(true);
       throw new RuntimeException(e);
     } catch (ExecutionException e) {
       UnsafeAccess.throwException(e.getCause());
-      throw new IllegalStateException("we shouldn't get here");
+      throw new IllegalStateException("we won't get here");
     }
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
index 180288c..834baa4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
@@ -1495,7 +1495,7 @@
 
   /** Set value to be returned by {@link PackageManager#isSafeMode}. */
   public void setSafeMode(boolean safeMode) {
-    this.safeMode = safeMode;
+    ShadowPackageManager.safeMode = safeMode;
   }
 
   @Resetter