Added implementations of getProfileProxy() and closeProfileProxy(), which operate on a map of active proxies set up by the newly added function setProfileProxy(). One proxy can be set per a BluetoothProfile id.
Added function hasActiveProfileProxy(), which checks whether there is an active proxy for the given BluetoothProfile id. Call to closeProfileProxy() "deactivates" the corresponding proxy.
PiperOrigin-RevId: 225846133
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
index 42e9f61..d67d7eb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
@@ -3,6 +3,9 @@
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothAdapter;
@@ -15,16 +18,19 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@RunWith(AndroidJUnit4.class)
public class ShadowBluetoothAdapterTest {
+ private static final int MOCK_PROFILE1 = 17;
+ private static final int MOCK_PROFILE2 = 21;
+
private BluetoothAdapter bluetoothAdapter;
private ShadowBluetoothAdapter shadowBluetoothAdapter;
- @Rule
- public ExpectedException thrown = ExpectedException.none();
+ @Rule public ExpectedException thrown = ExpectedException.none();
@Before
public void setUp() throws Exception {
@@ -163,6 +169,94 @@
.isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
}
+ @Test
+ public void getProfileProxy_afterSetProfileProxy_callsServiceListener() throws Exception {
+ BluetoothProfile mockProxy = mock(BluetoothProfile.class);
+ BluetoothProfile.ServiceListener mockServiceListener =
+ mock(BluetoothProfile.ServiceListener.class);
+ shadowBluetoothAdapter.setProfileProxy(MOCK_PROFILE1, mockProxy);
+
+ boolean result =
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.application, mockServiceListener, MOCK_PROFILE1);
+
+ assertThat(result).isTrue();
+ verify(mockServiceListener).onServiceConnected(MOCK_PROFILE1, mockProxy);
+ }
+
+ @Test
+ public void getProfileProxy_withoutSetProfileProxy_doesNotCallServiceListener() throws Exception {
+ BluetoothProfile.ServiceListener mockServiceListener =
+ mock(BluetoothProfile.ServiceListener.class);
+
+ boolean result =
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.application, mockServiceListener, MOCK_PROFILE1);
+
+ assertThat(result).isFalse();
+ verifyZeroInteractions(mockServiceListener);
+ }
+
+ @Test
+ public void getProfileProxy_forMultipleProfiles_callsServiceListenerMultipleTimes()
+ throws Exception {
+ BluetoothProfile mockProxy1 = mock(BluetoothProfile.class);
+ shadowBluetoothAdapter.setProfileProxy(MOCK_PROFILE1, mockProxy1);
+ BluetoothProfile mockProxy2 = mock(BluetoothProfile.class);
+ shadowBluetoothAdapter.setProfileProxy(MOCK_PROFILE2, mockProxy2);
+ BluetoothProfile.ServiceListener mockServiceListener =
+ mock(BluetoothProfile.ServiceListener.class);
+
+ boolean result1 =
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.application, mockServiceListener, MOCK_PROFILE1);
+ boolean result2 =
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.application, mockServiceListener, MOCK_PROFILE2);
+
+ assertThat(result1).isTrue();
+ assertThat(result2).isTrue();
+ verify(mockServiceListener).onServiceConnected(MOCK_PROFILE1, mockProxy1);
+ verify(mockServiceListener).onServiceConnected(MOCK_PROFILE2, mockProxy2);
+ }
+
+ @Test
+ public void hasActiveProfileProxy_reflectsSetProfileProxy() throws Exception {
+ BluetoothProfile mockProxy = mock(BluetoothProfile.class);
+ shadowBluetoothAdapter.setProfileProxy(MOCK_PROFILE1, mockProxy);
+
+ assertThat(shadowBluetoothAdapter.hasActiveProfileProxy(MOCK_PROFILE1)).isTrue();
+ assertThat(shadowBluetoothAdapter.hasActiveProfileProxy(MOCK_PROFILE2)).isFalse();
+ }
+
+ @Test
+ public void closeProfileProxy_reversesSetProfileProxy() throws Exception {
+ BluetoothProfile mockProxy = mock(BluetoothProfile.class);
+ BluetoothProfile.ServiceListener mockServiceListener =
+ mock(BluetoothProfile.ServiceListener.class);
+ shadowBluetoothAdapter.setProfileProxy(MOCK_PROFILE1, mockProxy);
+
+ bluetoothAdapter.closeProfileProxy(MOCK_PROFILE1, mockProxy);
+ boolean result =
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.application, mockServiceListener, MOCK_PROFILE1);
+
+ assertThat(result).isFalse();
+ verifyZeroInteractions(mockServiceListener);
+ assertThat(shadowBluetoothAdapter.hasActiveProfileProxy(MOCK_PROFILE1)).isFalse();
+ }
+
+ @Test
+ public void closeProfileProxy_mismatchedProxy_noOp() throws Exception {
+ BluetoothProfile mockProxy1 = mock(BluetoothProfile.class);
+ BluetoothProfile mockProxy2 = mock(BluetoothProfile.class);
+ shadowBluetoothAdapter.setProfileProxy(MOCK_PROFILE1, mockProxy1);
+
+ bluetoothAdapter.closeProfileProxy(MOCK_PROFILE1, mockProxy2);
+
+ assertThat(shadowBluetoothAdapter.hasActiveProfileProxy(MOCK_PROFILE1)).isTrue();
+ }
+
private BluetoothAdapter.LeScanCallback newLeScanCallback() {
return new BluetoothAdapter.LeScanCallback() {
@Override
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java
index e184e4d..b3c0616 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java
@@ -9,6 +9,7 @@
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
+import android.content.Context;
import android.os.ParcelUuid;
import java.util.Collections;
import java.util.HashMap;
@@ -33,7 +34,8 @@
private String name = "DefaultBluetoothDeviceName";
private int scanMode = BluetoothAdapter.SCAN_MODE_NONE;
private boolean isMultipleAdvertisementSupported = true;
- private Map<Integer, Integer> profileConnectionStateData = new HashMap<>();
+ private final Map<Integer, Integer> profileConnectionStateData = new HashMap<>();
+ private final Map<Integer, BluetoothProfile> profileProxies = new HashMap<>();
@Implementation
protected static BluetoothAdapter getDefaultAdapter() {
@@ -224,10 +226,61 @@
isMultipleAdvertisementSupported = supported;
}
- /**
- *Sets the connection state {@code state} for the given BLuetoothProfile {@code profile}
- */
+ /** Sets the connection state {@code state} for the given BLuetoothProfile {@code profile} */
public void setProfileConnectionState(int profile, int state) {
profileConnectionStateData.put(profile, state);
}
+
+ /**
+ * Sets the active BluetoothProfile {@code proxy} for the given {@code profile}. Will affect
+ * behavior of {@link BluetoothAdapter#getProfileProxy} and {@link
+ * BluetoothAdapter#closeProfileProxy}.
+ *
+ * <p>Call to {@link BluetoothAdapter#closeProfileProxy} can remove the set active proxy.
+ */
+ public void setProfileProxy(int profile, BluetoothProfile proxy) {
+ profileProxies.put(profile, proxy);
+ }
+
+ /**
+ * @return True if active proxy has been set by {@link ShadowBluetoothAdapter#setProfileProxy} for
+ * the given BluetoothProfile {@code profile} AND it has not been "deactivated" by a call to
+ * {@link BluetoothAdapter#closeProfileProxy}.
+ */
+ public boolean hasActiveProfileProxy(int profile) {
+ return profileProxies.get(profile) != null;
+ }
+
+ /**
+ * Overrides behavior of {@link BluetoothAdapter#getProfileProxy} to return pre-set result. If
+ * active proxy has been set by {@link ShadowBluetoothAdapter#setProfileProxy} for the given
+ * {@code profile}, getProfileProxy() will immediately call {@code onServiceConnected} of the
+ * given BluetoothProfile.ServiceListener {@code listener}.
+ *
+ * @return True if active proxy has been set by {@link ShadowBluetoothAdapter#setProfileProxy} for
+ * the given BluetoothProfile {@code profile}
+ */
+ @Implementation
+ protected boolean getProfileProxy(
+ Context context, BluetoothProfile.ServiceListener listener, int profile) {
+ BluetoothProfile proxy = profileProxies.get(profile);
+ if (proxy == null) {
+ return false;
+ } else {
+ listener.onServiceConnected(profile, proxy);
+ return true;
+ }
+ }
+
+ /**
+ * Overrides behavior of {@link BluetoothAdapter#closeProfileProxy}. If the given BluetoothProfile
+ * {@code proxy} was previously set for the given {@code profile} by {@link
+ * ShadowBluetoothAdapter#setProfileProxy}, this proxy will be "deactivated".
+ */
+ @Implementation
+ protected void closeProfileProxy(int profile, BluetoothProfile proxy) {
+ if (profileProxies.get(profile).equals(proxy)) {
+ profileProxies.remove(profile);
+ }
+ }
}