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;
}