@@ -23,6 +23,14 @@ import org.robolectric.annotation.Config;
2323import org.robolectric.annotation.LooperMode;
2424import org.robolectric.shadows.ShadowLooper;
2525
26+ import java.util.concurrent.Callable;
27+ import java.util.concurrent.ExecutionException;
28+ import java.util.concurrent.ExecutorService;
29+ import java.util.concurrent.Executors;
30+ import java.util.concurrent.Future;
31+ import java.util.concurrent.TimeUnit;
32+ import java.util.concurrent.TimeoutException;
33+
2634@RunWith(RobolectricTestRunner.class)
2735@Config(sdk = 30)
2836@LooperMode(LooperMode.Mode.LEGACY)
@@ -37,8 +45,8 @@ public class @MAIN_NAME@UiTest {
3745 log("Activity created (Display.isInitialized=" + Display.isInitialized() + ")");
3846 controller.start();
3947 log("Activity started");
40- controller.resume( );
41- log("Activity resumed" );
48+ resumeWithPumpedMainLooper(controller );
49+ log("Display initialized after resume: " + Display.isInitialized() );
4250 controller.visible();
4351 log("Activity made visible");
4452
@@ -70,12 +78,48 @@ public class @MAIN_NAME@UiTest {
7078 assertTrue("Screenshot file should exist", screenshotFile.isFile());
7179 assertTrue("Screenshot file should not be empty", screenshotFile.length() > 0L);
7280 } finally {
73- controller.pause();
74- log("Activity paused");
75- controller.stop();
76- log("Activity stopped");
77- controller.destroy();
78- log("Activity destroyed");
81+ safelyInvokeLifecycle(controller::pause, "pause");
82+ safelyInvokeLifecycle(controller::stop, "stop");
83+ safelyInvokeLifecycle(controller::destroy, "destroy");
84+ }
85+ }
86+
87+ private static void resumeWithPumpedMainLooper(ActivityController<@MAIN_NAME@Stub> controller)
88+ throws InterruptedException, ExecutionException, TimeoutException {
89+ log("Preparing to resume activity with pumped main looper");
90+ ExecutorService executor = Executors.newSingleThreadExecutor();
91+ Future<Void> future = executor.submit(new Callable<Void>() {
92+ @Override
93+ public Void call() {
94+ log("Invoking controller.resume()");
95+ controller.resume();
96+ log("controller.resume() returned");
97+ return null;
98+ }
99+ });
100+
101+ long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(60);
102+ int iterations = 0;
103+ try {
104+ while (!future.isDone()) {
105+ if (System.nanoTime() >= deadlineNanos) {
106+ log("controller.resume() did not finish before deadline; cancelling");
107+ future.cancel(true);
108+ throw new TimeoutException("controller.resume() timed out");
109+ }
110+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
111+ if (++iterations % 50 == 0) {
112+ log("Still waiting for controller.resume() after " + iterations + " pump iterations");
113+ }
114+ Thread.sleep(20L);
115+ }
116+ future.get(5L, TimeUnit.SECONDS);
117+ log("Activity resumed");
118+ } catch (TimeoutException | InterruptedException | ExecutionException ex) {
119+ log("Failed while resuming activity: " + ex);
120+ throw ex;
121+ } finally {
122+ executor.shutdownNow();
79123 }
80124 }
81125
@@ -170,6 +214,15 @@ public class @MAIN_NAME@UiTest {
170214 appendLogLine(formatted);
171215 }
172216
217+ private static void safelyInvokeLifecycle(Runnable invocation, String name) {
218+ try {
219+ invocation.run();
220+ log("Activity " + name + " invoked");
221+ } catch (RuntimeException ex) {
222+ log("Lifecycle invocation " + name + " failed: " + ex);
223+ }
224+ }
225+
173226 private static synchronized void appendLogLine(String message) {
174227 File directory = resolveArtifactDirectoryForLogging();
175228 if (directory == null) {
0 commit comments