Created a new ShadowApplication#setBindServiceCallsOnServiceConnectedDirectly which will make it so onServiceConnected is called before returning from bindService.  This allows testing of a synchronous call that waits for the Service binding.

PiperOrigin-RevId: 345743796
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java
index d77fff4..3f98abb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java
@@ -8,6 +8,7 @@
 import static android.os.Build.VERSION_CODES.O;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -49,6 +50,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -217,6 +219,108 @@
   }
 
   @Test
+  public void
+      setBindServiceCallsOnServiceConnectedDirectly_setToTrue_onServiceConnectedCalledDuringCall() {
+    TestService service = new TestService();
+    ComponentName expectedComponentName = new ComponentName("", "");
+    Binder expectedBinder = new Binder();
+    Shadows.shadowOf(context)
+        .setComponentNameAndServiceForBindService(expectedComponentName, expectedBinder);
+    Shadows.shadowOf(context).setBindServiceCallsOnServiceConnectedDirectly(true);
+
+    context.bindService(new Intent("").setPackage("package"), service, Context.BIND_AUTO_CREATE);
+
+    assertThat(service.service).isNotNull();
+  }
+
+  @Test
+  public void
+      setBindServiceCallsOnServiceConnectedDirectly_setToTrue_locksUntilBound_onServiceConnectedCalledDuringCall()
+          throws InterruptedException {
+    final CountDownLatch latch = new CountDownLatch(1);
+    TestService service =
+        new TestService() {
+          @Override
+          public void onServiceConnected(ComponentName name, IBinder service) {
+            super.onServiceConnected(name, service);
+            latch.countDown();
+          }
+        };
+    ComponentName expectedComponentName = new ComponentName("", "");
+    Binder expectedBinder = new Binder();
+    Shadows.shadowOf(context)
+        .setComponentNameAndServiceForBindService(expectedComponentName, expectedBinder);
+    Shadows.shadowOf(context).setBindServiceCallsOnServiceConnectedDirectly(true);
+
+    context.bindService(new Intent("").setPackage("package"), service, Context.BIND_AUTO_CREATE);
+
+    // Lock waiting for onService connected to finish
+    assertThat(latch.await(1000, MILLISECONDS)).isTrue();
+    assertThat(service.service).isNotNull();
+  }
+
+  @Test
+  public void
+      setBindServiceCallsOnServiceConnectedDirectly_setToFalse_onServiceConnectedNotCalledDuringCall() {
+    TestService service = new TestService();
+    ComponentName expectedComponentName = new ComponentName("", "");
+    Binder expectedBinder = new Binder();
+    Shadows.shadowOf(context)
+        .setComponentNameAndServiceForBindService(expectedComponentName, expectedBinder);
+    Shadows.shadowOf(context).setBindServiceCallsOnServiceConnectedDirectly(false);
+
+    context.bindService(new Intent("").setPackage("package"), service, Context.BIND_AUTO_CREATE);
+
+    assertThat(service.service).isNull();
+  }
+
+  @Test
+  public void
+      setBindServiceCallsOnServiceConnectedDirectly_setToFalse_locksUntilBound_onServiceConnectedCalledDuringCall()
+          throws InterruptedException {
+    final CountDownLatch latch = new CountDownLatch(1);
+    TestService service =
+        new TestService() {
+          @Override
+          public void onServiceConnected(ComponentName name, IBinder service) {
+            super.onServiceConnected(name, service);
+            latch.countDown();
+          }
+        };
+    ComponentName expectedComponentName = new ComponentName("", "");
+    Binder expectedBinder = new Binder();
+    Shadows.shadowOf(context)
+        .setComponentNameAndServiceForBindService(expectedComponentName, expectedBinder);
+    Shadows.shadowOf(context).setBindServiceCallsOnServiceConnectedDirectly(false);
+
+    context.bindService(new Intent("").setPackage("package"), service, Context.BIND_AUTO_CREATE);
+
+    // Lock waiting for onService connected to finish
+    assertThat(latch.await(1000, MILLISECONDS)).isFalse();
+    assertThat(service.service).isNull();
+
+    // After idling the callback has been made.
+    ShadowLooper.idleMainLooper();
+
+    assertThat(latch.await(1000, MILLISECONDS)).isTrue();
+    assertThat(service.service).isNotNull();
+  }
+
+  @Test
+  public void
+      setBindServiceCallsOnServiceConnectedDirectly_notSet_onServiceConnectedNotCalledDuringCall() {
+    TestService service = new TestService();
+    ComponentName expectedComponentName = new ComponentName("", "");
+    Binder expectedBinder = new Binder();
+    Shadows.shadowOf(context)
+        .setComponentNameAndServiceForBindService(expectedComponentName, expectedBinder);
+
+    context.bindService(new Intent("").setPackage("package"), service, Context.BIND_AUTO_CREATE);
+
+    assertThat(service.service).isNull();
+  }
+
+  @Test
   public void bindServiceShouldCallOnServiceConnectedWithDefaultValues_ifFlagUnset() {
     Shadows.shadowOf(context).setUnbindServiceCallsOnServiceDisconnected(false);
     TestService service = new TestService();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java
index be19e33..6072156 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java
@@ -158,6 +158,14 @@
     getShadowInstrumentation().setThrowInBindService(e);
   }
 
+  /**
+   * Configures the ShadowApplication so that calls to bindService will call
+   * ServiceConnection#onServiceConnected before returning.
+   */
+  public void setBindServiceCallsOnServiceConnectedDirectly(boolean callDirectly) {
+    getShadowInstrumentation().setBindServiceCallsOnServiceConnectedDirectly(callDirectly);
+  }
+
   public List<ServiceConnection> getUnboundServiceConnections() {
     return getShadowInstrumentation().getUnboundServiceConnections();
   }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
index 1caaedf..d78d061 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
@@ -94,6 +94,7 @@
       Collections.synchronizedMap(new HashMap<>());
   private boolean unbindServiceShouldThrowIllegalArgument = false;
   private SecurityException exceptionForBindService = null;
+  private boolean bindServiceCallsOnServiceConnectedInline;
   private final Map<Intent.FilterComparison, ServiceConnectionDataWrapper>
       serviceConnectionDataForIntent = Collections.synchronizedMap(new HashMap<>());
   // default values for bindService
@@ -670,14 +671,20 @@
     }
     startedServices.add(filterComparison);
     Handler handler = new Handler(Looper.getMainLooper());
-    handler.post(
+    Runnable onServiceConnectedRunnable =
         () -> {
           serviceConnectionDataForServiceConnection.put(
               serviceConnection, serviceConnectionDataWrapper);
           serviceConnection.onServiceConnected(
               serviceConnectionDataWrapper.componentNameForBindService,
               serviceConnectionDataWrapper.binderForBindService);
-        });
+        };
+
+    if (bindServiceCallsOnServiceConnectedInline) {
+      onServiceConnectedRunnable.run();
+    } else {
+      handler.post(onServiceConnectedRunnable);
+    }
     return true;
   }
 
@@ -727,6 +734,11 @@
     exceptionForBindService = e;
   }
 
+  void setBindServiceCallsOnServiceConnectedDirectly(
+      boolean bindServiceCallsOnServiceConnectedInline) {
+    this.bindServiceCallsOnServiceConnectedInline = bindServiceCallsOnServiceConnectedInline;
+  }
+
   protected List<ServiceConnection> getUnboundServiceConnections() {
     return unboundServiceConnections;
   }