diff --git a/res/values/strings.xml b/res/values/strings.xml index 7331d7265f7..45aadf38d12 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -393,6 +393,8 @@ Your devices Pair new device + + Allow device to pair and connect to bluetooth devices Currently connected diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml index 946151f297e..57a2580c20e 100644 --- a/res/xml/connected_devices_advanced.xml +++ b/res/xml/connected_devices_advanced.xml @@ -19,10 +19,11 @@ android:key="connected_devices_screen" android:title="@string/connected_devices_dashboard_title"> - { + if (connected) { + mUsbPreference.setSummary( + UsbModePreferenceController.getSummary(mUsbBackend.getCurrentMode())); + mDevicePreferenceCallback.onDeviceAdded(mUsbPreference); + } else { + mDevicePreferenceCallback.onDeviceRemoved(mUsbPreference); + } + }; + + public ConnectedUsbDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback) { + this(context, devicePreferenceCallback, new UsbBackend(context)); + } + + @VisibleForTesting + ConnectedUsbDeviceUpdater(Context context, DevicePreferenceCallback devicePreferenceCallback, + UsbBackend usbBackend) { + mContext = context; + mDevicePreferenceCallback = devicePreferenceCallback; + mUsbBackend = usbBackend; + mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener); + } + + public void registerCallback() { + // This method could handle multiple register + mUsbReceiver.register(); + } + + public void unregisterCallback() { + mUsbReceiver.unregister(); + } + + public void initUsbPreference(Context context) { + mUsbPreference = new GearPreference(context, null /* AttributeSet */); + mUsbPreference.setTitle(R.string.usb_pref); + mUsbPreference.setIcon(R.drawable.ic_usb); + mUsbPreference.setSelectable(false); + mUsbPreference.setOnGearClickListener((GearPreference p) -> { + final Intent intent = new Intent(mContext, UsbModeChooserActivity.class); + mContext.startActivity(intent); + }); + + forceUpdate(); + } + + private void forceUpdate() { + // Register so we can get the connection state from sticky intent. + //TODO(b/70336520): Use an API to get data instead of sticky intent + mUsbReceiver.register(); + mUsbConnectionListener.onUsbConnectionChanged(mUsbReceiver.isConnected()); + } +} diff --git a/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java b/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java new file mode 100644 index 00000000000..07a76915af4 --- /dev/null +++ b/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.connecteddevice; + + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbManager; + +/** + * Receiver to receive usb update and use {@link UsbConnectionListener} to invoke callback + */ +public class UsbConnectionBroadcastReceiver extends BroadcastReceiver { + private Context mContext; + private UsbConnectionListener mUsbConnectionListener; + private boolean mListeningToUsbEvents; + private boolean mConnected; + + public UsbConnectionBroadcastReceiver(Context context, + UsbConnectionListener usbConnectionListener) { + mContext = context; + mUsbConnectionListener = usbConnectionListener; + } + + @Override + public void onReceive(Context context, Intent intent) { + mConnected = intent != null + && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); + if (mUsbConnectionListener != null) { + mUsbConnectionListener.onUsbConnectionChanged(mConnected); + } + } + + public void register() { + if (!mListeningToUsbEvents) { + final IntentFilter intentFilter = new IntentFilter(UsbManager.ACTION_USB_STATE); + final Intent intent = mContext.registerReceiver(this, intentFilter); + mConnected = intent != null + && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); + mListeningToUsbEvents = true; + } + } + + public void unregister() { + if (mListeningToUsbEvents) { + mContext.unregisterReceiver(this); + mListeningToUsbEvents = false; + } + } + + public boolean isConnected() { + return mConnected; + } + + /** + * Interface definition for a callback to be invoked when usb connection is changed. + */ + interface UsbConnectionListener { + void onUsbConnectionChanged(boolean connected); + } +} diff --git a/src/com/android/settings/connecteddevice/UsbModePreferenceController.java b/src/com/android/settings/connecteddevice/UsbModePreferenceController.java index a6cb9be1e7f..869352006c5 100644 --- a/src/com/android/settings/connecteddevice/UsbModePreferenceController.java +++ b/src/com/android/settings/connecteddevice/UsbModePreferenceController.java @@ -15,17 +15,12 @@ */ package com.android.settings.connecteddevice; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.UsbManager; -import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; -import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.deviceinfo.UsbBackend; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -44,19 +39,21 @@ public class UsbModePreferenceController extends AbstractPreferenceController public UsbModePreferenceController(Context context, UsbBackend usbBackend) { super(context); mUsbBackend = usbBackend; - mUsbReceiver = new UsbConnectionBroadcastReceiver(); + mUsbReceiver = new UsbConnectionBroadcastReceiver(mContext, (connected) -> { + updateSummary(mUsbPreference); + }); } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mUsbPreference = screen.findPreference(KEY_USB_MODE); - updataSummary(mUsbPreference); + updateSummary(mUsbPreference); } @Override public void updateState(Preference preference) { - updataSummary(preference); + updateSummary(preference); } @Override @@ -79,8 +76,7 @@ public class UsbModePreferenceController extends AbstractPreferenceController mUsbReceiver.register(); } - @VisibleForTesting - int getSummary(int mode) { + public static int getSummary(int mode) { switch (mode) { case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_NONE: return R.string.usb_summary_charging_only; @@ -96,11 +92,11 @@ public class UsbModePreferenceController extends AbstractPreferenceController return 0; } - private void updataSummary(Preference preference) { - updataSummary(preference, mUsbBackend.getCurrentMode()); + private void updateSummary(Preference preference) { + updateSummary(preference, mUsbBackend.getCurrentMode()); } - private void updataSummary(Preference preference, int mode) { + private void updateSummary(Preference preference, int mode) { if (preference != null) { if (mUsbReceiver.isConnected()) { preference.setEnabled(true); @@ -112,40 +108,4 @@ public class UsbModePreferenceController extends AbstractPreferenceController } } - private class UsbConnectionBroadcastReceiver extends BroadcastReceiver { - private boolean mListeningToUsbEvents; - private boolean mConnected; - - @Override - public void onReceive(Context context, Intent intent) { - boolean connected = intent != null - && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); - if (connected != mConnected) { - mConnected = connected; - updataSummary(mUsbPreference); - } - } - - public void register() { - if (!mListeningToUsbEvents) { - IntentFilter intentFilter = new IntentFilter(UsbManager.ACTION_USB_STATE); - Intent intent = mContext.registerReceiver(this, intentFilter); - mConnected = intent != null - && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); - mListeningToUsbEvents = true; - } - } - - public void unregister() { - if (mListeningToUsbEvents) { - mContext.unregisterReceiver(this); - mListeningToUsbEvents = false; - } - } - - public boolean isConnected() { - return mConnected; - } - } - } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java new file mode 100644 index 00000000000..aa9d26624e4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.bluetooth.LocalBluetoothAdapter; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class BluetoothSwitchPreferenceControllerTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private LocalBluetoothManager mBluetoothManager; + @Mock + private PreferenceScreen mScreen; + @Mock + private SwitchPreference mPreference; + @Mock + private RestrictionUtils mRestrictionUtils; + @Mock + private LocalBluetoothAdapter mLocalBluetoothAdapter; + + private Context mContext; + private BluetoothSwitchPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application.getApplicationContext()); + FakeFeatureFactory.setupForTest(); + + mController = new BluetoothSwitchPreferenceController( + mContext, mBluetoothManager, mRestrictionUtils); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); + } + + @Test + public void testGetAvailabilityStatus_adapterNull_returnDisabled() { + mController.mBluetoothAdapter = null; + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.DISABLED_UNSUPPORTED); + } + + @Test + public void testGetAvailabilityStatus_adapterExisted_returnAvailable() { + mController.mBluetoothAdapter = mLocalBluetoothAdapter; + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void testOnStart_shouldRegisterPreferenceChangeListener() { + mController.displayPreference(mScreen); + mController.onStart(); + + verify(mPreference).setOnPreferenceChangeListener( + any(BluetoothSwitchPreferenceController.SwitchController.class)); + } + + @Test + public void testOnStop_shouldRegisterPreferenceChangeListener() { + mController.displayPreference(mScreen); + mController.onStart(); + + mController.onStop(); + + verify(mPreference).setOnPreferenceChangeListener(null); + } + + @Test + public void testIsChecked_adapterNull_returnFalse() { + mController.mBluetoothAdapter = null; + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void testIsChecked_adapterExisted_returnFromAdapter() { + mController.mBluetoothAdapter = mLocalBluetoothAdapter; + doReturn(true).when(mLocalBluetoothAdapter).isEnabled(); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void testSetChecked_adapterExisted() { + mController.mBluetoothAdapter = mLocalBluetoothAdapter; + + mController.setChecked(true); + + verify(mLocalBluetoothAdapter).setBluetoothEnabled(true); + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java index f9efc0bf64d..aa5eb67342f 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java @@ -37,6 +37,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; @@ -45,13 +46,17 @@ import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class ConnectedDeviceGroupControllerTest { + private static final String PREFERENCE_KEY_1 = "pref_key_1"; + @Mock private DashboardFragment mDashboardFragment; @Mock private ConnectedBluetoothDeviceUpdater mConnectedBluetoothDeviceUpdater; @Mock - private PreferenceScreen mPreferenceScreen; + private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater; @Mock + private PreferenceScreen mPreferenceScreen; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private PreferenceManager mPreferenceManager; private PreferenceGroup mPreferenceGroup; @@ -66,30 +71,33 @@ public class ConnectedDeviceGroupControllerTest { mContext = RuntimeEnvironment.application; mPreference = new Preference(mContext); + mPreference.setKey(PREFERENCE_KEY_1); mLifecycle = new Lifecycle(() -> mLifecycle); mPreferenceGroup = spy(new PreferenceScreen(mContext, null)); doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager(); doReturn(mContext).when(mDashboardFragment).getContext(); mConnectedDeviceGroupController = new ConnectedDeviceGroupController(mDashboardFragment, - mLifecycle, mConnectedBluetoothDeviceUpdater); + mLifecycle, mConnectedBluetoothDeviceUpdater, mConnectedUsbDeviceUpdater); mConnectedDeviceGroupController.mPreferenceGroup = mPreferenceGroup; } @Test - public void testOnDeviceAdded_firstAdd_becomeVisible() { + public void testOnDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded() { mConnectedDeviceGroupController.onDeviceAdded(mPreference); assertThat(mPreferenceGroup.isVisible()).isTrue(); + assertThat(mPreferenceGroup.findPreference(PREFERENCE_KEY_1)).isEqualTo(mPreference); } @Test - public void testOnDeviceRemoved_lastRemove_becomeInvisible() { + public void testOnDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved() { mPreferenceGroup.addPreference(mPreference); mConnectedDeviceGroupController.onDeviceRemoved(mPreference); assertThat(mPreferenceGroup.isVisible()).isFalse(); + assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0); } @Test @@ -117,9 +125,11 @@ public class ConnectedDeviceGroupControllerTest { // register the callback in onStart() mLifecycle.handleLifecycleEvent(android.arch.lifecycle.Lifecycle.Event.ON_START); verify(mConnectedBluetoothDeviceUpdater).registerCallback(); + verify(mConnectedUsbDeviceUpdater).registerCallback(); // unregister the callback in onStop() mLifecycle.handleLifecycleEvent(android.arch.lifecycle.Lifecycle.Event.ON_STOP); verify(mConnectedBluetoothDeviceUpdater).unregisterCallback(); + verify(mConnectedUsbDeviceUpdater).unregisterCallback(); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java new file mode 100644 index 00000000000..16cd3a7a94f --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.settings.connecteddevice; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.deviceinfo.UsbBackend; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class ConnectedUsbDeviceUpdaterTest { + private Context mContext; + private ConnectedUsbDeviceUpdater mDeviceUpdater; + + @Mock + private UsbConnectionBroadcastReceiver mUsbReceiver; + @Mock + private DevicePreferenceCallback mDevicePreferenceCallback; + @Mock + private UsbBackend mUsbBackend; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mDeviceUpdater = new ConnectedUsbDeviceUpdater(mContext, mDevicePreferenceCallback, + mUsbBackend); + mDeviceUpdater.mUsbReceiver = mUsbReceiver; + } + + @Test + public void testInitUsbPreference_preferenceInit() { + mDeviceUpdater.initUsbPreference(mContext); + + assertThat(mDeviceUpdater.mUsbPreference.getTitle()).isEqualTo("USB"); + assertThat(mDeviceUpdater.mUsbPreference.getIcon()).isEqualTo(mContext.getDrawable( + R.drawable.ic_usb)); + assertThat(mDeviceUpdater.mUsbPreference.isSelectable()).isFalse(); + } + + @Test + public void testInitUsbPreference_usbConnected_preferenceAdded() { + doReturn(true).when(mUsbReceiver).isConnected(); + + mDeviceUpdater.initUsbPreference(mContext); + + verify(mDevicePreferenceCallback).onDeviceAdded(mDeviceUpdater.mUsbPreference); + } + + @Test + public void testInitUsbPreference_usbDisconnected_preferenceRemoved() { + doReturn(false).when(mUsbReceiver).isConnected(); + + mDeviceUpdater.initUsbPreference(mContext); + + verify(mDevicePreferenceCallback).onDeviceRemoved(mDeviceUpdater.mUsbPreference); + } + +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java new file mode 100644 index 00000000000..06bd5b7834d --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.settings.connecteddevice; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.hardware.usb.UsbManager; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class UsbConnectionBroadcastReceiverTest { + private Context mContext; + private UsbConnectionBroadcastReceiver mReceiver; + private ShadowApplication mShadowApplication; + + @Mock + private UsbConnectionBroadcastReceiver.UsbConnectionListener mListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mShadowApplication = ShadowApplication.getInstance(); + mContext = RuntimeEnvironment.application; + mReceiver = new UsbConnectionBroadcastReceiver(mContext, mListener); + } + + @Test + public void testOnReceive_usbConnected_invokeCallback() { + final Intent intent = new Intent(); + intent.putExtra(UsbManager.USB_CONNECTED, true); + + mReceiver.onReceive(mContext, intent); + + verify(mListener).onUsbConnectionChanged(true); + } + + @Test + public void testOnReceive_usbDisconnected_invokeCallback() { + final Intent intent = new Intent(); + intent.putExtra(UsbManager.USB_CONNECTED, false); + + mReceiver.onReceive(mContext, intent); + + verify(mListener).onUsbConnectionChanged(false); + } + + @Test + public void testRegister_invokeMethodTwice_registerOnce() { + mReceiver.register(); + mReceiver.register(); + + final List receivers = mShadowApplication.getReceiversForIntent( + new Intent(UsbManager.ACTION_USB_STATE)); + assertHasOneUsbConnectionBroadcastReceiver(receivers); + } + + @Test + public void testUnregister_invokeMethodTwice_unregisterOnce() { + mReceiver.register(); + mReceiver.unregister(); + mReceiver.unregister(); + + final List receivers = mShadowApplication.getReceiversForIntent( + new Intent(UsbManager.ACTION_USB_STATE)); + assertHasNoUsbConnectionBroadcastReceiver(receivers); + } + + private void assertHasOneUsbConnectionBroadcastReceiver(List receivers) { + boolean hasReceiver = false; + for (final BroadcastReceiver receiver : receivers) { + if (receiver instanceof UsbConnectionBroadcastReceiver) { + // If hasReceiver is true, then we're at the second copy of it so fail. + assertWithMessage( + "Only one instance of UsbConnectionBroadcastReceiver should be " + + "registered").that( + hasReceiver).isFalse(); + hasReceiver = true; + } + } + assertThat(hasReceiver).isTrue(); + } + + private void assertHasNoUsbConnectionBroadcastReceiver(List receivers) { + for (final BroadcastReceiver receiver : receivers) { + assertThat(receiver instanceof UsbConnectionBroadcastReceiver).isFalse(); + } + } +} \ No newline at end of file