From 723c385c18acebb3287617667702655c9537db64 Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Tue, 7 Feb 2023 17:23:11 +0800 Subject: [PATCH] [Pair hearing devices] Extract common behavior in BluetoothPairingDetail into Base class * Before adding new hearing device feature, we will extract the common part into Base class first. They will share most of the UI component. Bug: 237625815 Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDevicePairingDetailBaseTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDevicePairingDetailTest Change-Id: I3a44c4c464d630fdcafa151afc82d3000fd728a3 --- res/xml/bluetooth_pairing_detail.xml | 2 +- .../BluetoothDevicePairingDetailBase.java | 200 +++++++++++++ .../bluetooth/BluetoothPairingDetail.java | 175 +++--------- .../BluetoothDevicePairingDetailBaseTest.java | 266 ++++++++++++++++++ .../bluetooth/BluetoothPairingDetailTest.java | 213 ++------------ 5 files changed, 521 insertions(+), 335 deletions(-) create mode 100644 src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java diff --git a/res/xml/bluetooth_pairing_detail.xml b/res/xml/bluetooth_pairing_detail.xml index 86fb9e43d44..460a7083fc8 100644 --- a/res/xml/bluetooth_pairing_detail.xml +++ b/res/xml/bluetooth_pairing_detail.xml @@ -27,7 +27,7 @@ + android:title="@string/bluetooth_preference_found_media_devices"/> diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java new file mode 100644 index 00000000000..c71decb0ab8 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2023 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 android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.settings.R; +import com.android.settings.accessibility.AccessibilityStatsLogUtils; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidStatsLogUtils; + +/** + * Abstract class for providing basic interaction for a list of Bluetooth devices in bluetooth + * device pairing detail page. + */ +public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment { + + protected boolean mInitialScanStarted; + @VisibleForTesting + protected BluetoothProgressCategory mAvailableDevicesCategory; + + public BluetoothDevicePairingDetailBase() { + super(DISALLOW_CONFIG_BLUETOOTH); + } + + @Override + public void initPreferencesFromPreferenceScreen() { + mAvailableDevicesCategory = findPreference(getDeviceListKey()); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + mInitialScanStarted = false; + super.onViewCreated(view, savedInstanceState); + } + + @Override + public void onStart() { + super.onStart(); + if (mLocalManager == null) { + Log.e(getLogTag(), "Bluetooth is not supported on this device"); + return; + } + updateBluetooth(); + } + + @Override + public void onStop() { + super.onStop(); + if (mLocalManager == null) { + Log.e(getLogTag(), "Bluetooth is not supported on this device"); + return; + } + disableScanning(); + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + super.onBluetoothStateChanged(bluetoothState); + updateContent(bluetoothState); + if (bluetoothState == BluetoothAdapter.STATE_ON) { + showBluetoothTurnedOnToast(); + } + } + + @Override + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + if (bondState == BluetoothDevice.BOND_BONDED) { + // If one device is connected(bonded), then close this fragment. + finish(); + return; + } else if (bondState == BluetoothDevice.BOND_BONDING) { + // Set the bond entry where binding process starts for logging hearing aid device info + final int pageId = FeatureFactory.getFactory( + getContext()).getMetricsFeatureProvider().getAttribution(getActivity()); + final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry( + pageId); + HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice); + } + if (mSelectedDevice != null && cachedDevice != null) { + BluetoothDevice device = cachedDevice.getDevice(); + if (device != null && mSelectedDevice.equals(device) + && bondState == BluetoothDevice.BOND_NONE) { + // If currently selected device failed to bond, restart scanning + enableScanning(); + } + } + } + + @Override + public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, + int bluetoothProfile) { + // This callback is used to handle the case that bonded device is connected in pairing list. + // 1. If user selected multiple bonded devices in pairing list, after connected + // finish this page. + // 2. If the bonded devices auto connected in paring list, after connected it will be + // removed from paring list. + if (cachedDevice != null && cachedDevice.isConnected()) { + final BluetoothDevice device = cachedDevice.getDevice(); + if (device != null && mSelectedList.contains(device)) { + finish(); + } else if (mDevicePreferenceMap.containsKey(cachedDevice)) { + onDeviceDeleted(cachedDevice); + } + } + } + + @Override + public void enableScanning() { + // Clear all device states before first scan + if (!mInitialScanStarted) { + if (mAvailableDevicesCategory != null) { + removeAllDevices(); + } + mLocalManager.getCachedDeviceManager().clearNonBondedDevices(); + mInitialScanStarted = true; + } + super.enableScanning(); + } + + @Override + public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { + disableScanning(); + super.onDevicePreferenceClick(btPreference); + } + + @VisibleForTesting + void updateBluetooth() { + if (mBluetoothAdapter.isEnabled()) { + updateContent(mBluetoothAdapter.getState()); + } else { + // Turn on bluetooth if it is disabled + mBluetoothAdapter.enable(); + } + } + + /** + * Enables the scanning when {@code bluetoothState} is on, or finish the page when + * {@code bluetoothState} is off. + * + * @param bluetoothState the current Bluetooth state, the possible values that will handle here: + * {@link android.bluetooth.BluetoothAdapter#STATE_OFF}, + * {@link android.bluetooth.BluetoothAdapter#STATE_ON}, + */ + @VisibleForTesting + public void updateContent(int bluetoothState) { + switch (bluetoothState) { + case BluetoothAdapter.STATE_ON: + mDevicePreferenceMap.clear(); + clearPreferenceGroupCache(); + mBluetoothAdapter.enable(); + enableScanning(); + break; + + case BluetoothAdapter.STATE_OFF: + finish(); + break; + } + } + + /** + * Clears all cached preferences in {@code preferenceGroup}. + */ + private void clearPreferenceGroupCache() { + cacheRemoveAllPrefs(mAvailableDevicesCategory); + removeCachedPrefs(mAvailableDevicesCategory); + } + + @VisibleForTesting + void showBluetoothTurnedOnToast() { + Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast, + Toast.LENGTH_SHORT).show(); + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java index 9a92783e18a..a78bf27101c 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java @@ -16,31 +16,25 @@ package com.android.settings.bluetooth; -import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; - import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; import android.content.Context; import android.os.Bundle; -import android.util.Log; -import android.widget.Toast; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.settings.R; -import com.android.settings.accessibility.AccessibilityStatsLogUtils; -import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.BluetoothDeviceFilter; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.bluetooth.HearingAidStatsLogUtils; import com.android.settingslib.search.Indexable; import com.android.settingslib.widget.FooterPreference; /** * BluetoothPairingDetail is a page to scan bluetooth devices and pair them. */ -public class BluetoothPairingDetail extends DeviceListPreferenceFragment implements +public class BluetoothPairingDetail extends BluetoothDevicePairingDetailBase implements Indexable { private static final String TAG = "BluetoothPairingDetail"; @@ -49,35 +43,13 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme @VisibleForTesting static final String KEY_FOOTER_PREF = "footer_preference"; - @VisibleForTesting - BluetoothProgressCategory mAvailableDevicesCategory; @VisibleForTesting FooterPreference mFooterPreference; @VisibleForTesting AlwaysDiscoverable mAlwaysDiscoverable; - private boolean mInitialScanStarted; - public BluetoothPairingDetail() { - super(DISALLOW_CONFIG_BLUETOOTH); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mInitialScanStarted = false; - mAlwaysDiscoverable = new AlwaysDiscoverable(getContext()); - } - - @Override - public void onStart() { - super.onStart(); - if (mLocalManager == null){ - Log.e(TAG, "Bluetooth is not supported on this device"); - return; - } - updateBluetooth(); - mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering()); + super(); } @Override @@ -86,32 +58,29 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme use(BluetoothDeviceRenamePreferenceController.class).setFragment(this); } - @VisibleForTesting - void updateBluetooth() { - if (mBluetoothAdapter.isEnabled()) { - updateContent(mBluetoothAdapter.getState()); - } else { - // Turn on bluetooth if it is disabled - mBluetoothAdapter.enable(); - } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mAlwaysDiscoverable = new AlwaysDiscoverable(getContext()); + } + + @Override + public void onStart() { + super.onStart(); + mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering()); } @Override public void onStop() { super.onStop(); - if (mLocalManager == null){ - Log.e(TAG, "Bluetooth is not supported on this device"); - return; - } // Make the device only visible to connected devices. mAlwaysDiscoverable.stop(); - disableScanning(); } @Override - void initPreferencesFromPreferenceScreen() { - mAvailableDevicesCategory = (BluetoothProgressCategory) findPreference(KEY_AVAIL_DEVICES); - mFooterPreference = (FooterPreference) findPreference(KEY_FOOTER_PREF); + public void initPreferencesFromPreferenceScreen() { + super.initPreferencesFromPreferenceScreen(); + mFooterPreference = findPreference(KEY_FOOTER_PREF); mFooterPreference.setSelectable(false); } @@ -120,23 +89,25 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme return SettingsEnums.BLUETOOTH_PAIRING; } + /** + * {@inheritDoc} + * + * Will update footer and keep the device discoverable as long as the page is visible. + */ + @VisibleForTesting @Override - void enableScanning() { - // Clear all device states before first scan - if (!mInitialScanStarted) { - if (mAvailableDevicesCategory != null) { - removeAllDevices(); + public void updateContent(int bluetoothState) { + super.updateContent(bluetoothState); + if (bluetoothState == BluetoothAdapter.STATE_ON) { + if (mInitialScanStarted) { + // Don't show bonded devices when screen turned back on + setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER); + addCachedDevices(); } - mLocalManager.getCachedDeviceManager().clearNonBondedDevices(); - mInitialScanStarted = true; + setFilter(BluetoothDeviceFilter.ALL_FILTER); + updateFooterPreference(mFooterPreference); + mAlwaysDiscoverable.start(); } - super.enableScanning(); - } - - @Override - void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { - disableScanning(); - super.onDevicePreferenceClick(btPreference); } @Override @@ -146,78 +117,6 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme mAvailableDevicesCategory.setProgress(started); } - @VisibleForTesting - void updateContent(int bluetoothState) { - switch (bluetoothState) { - case BluetoothAdapter.STATE_ON: - mDevicePreferenceMap.clear(); - mBluetoothAdapter.enable(); - - addDeviceCategory(mAvailableDevicesCategory, - R.string.bluetooth_preference_found_media_devices, - BluetoothDeviceFilter.ALL_FILTER, mInitialScanStarted); - updateFooterPreference(mFooterPreference); - mAlwaysDiscoverable.start(); - enableScanning(); - break; - - case BluetoothAdapter.STATE_OFF: - finish(); - break; - } - } - - @Override - public void onBluetoothStateChanged(int bluetoothState) { - super.onBluetoothStateChanged(bluetoothState); - updateContent(bluetoothState); - if (bluetoothState == BluetoothAdapter.STATE_ON) { - showBluetoothTurnedOnToast(); - } - } - - @Override - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { - if (bondState == BluetoothDevice.BOND_BONDED) { - // If one device is connected(bonded), then close this fragment. - finish(); - return; - } else if (bondState == BluetoothDevice.BOND_BONDING) { - // Set the bond entry where binding process starts for logging hearing aid device info - final int pageId = FeatureFactory.getFactory( - getContext()).getMetricsFeatureProvider().getAttribution(getActivity()); - final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry( - pageId); - HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice); - } - if (mSelectedDevice != null && cachedDevice != null) { - BluetoothDevice device = cachedDevice.getDevice(); - if (device != null && mSelectedDevice.equals(device) - && bondState == BluetoothDevice.BOND_NONE) { - // If currently selected device failed to bond, restart scanning - enableScanning(); - } - } - } - - @Override - public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, - int bluetoothProfile) { - // This callback is used to handle the case that bonded device is connected in pairing list. - // 1. If user selected multiple bonded devices in pairing list, after connected - // finish this page. - // 2. If the bonded devices auto connected in paring list, after connected it will be - // removed from paring list. - if (cachedDevice != null && cachedDevice.isConnected()) { - final BluetoothDevice device = cachedDevice.getDevice(); - if (device != null && mSelectedList.contains(device)) { - finish(); - } else if (mDevicePreferenceMap.containsKey(cachedDevice)) { - onDeviceDeleted(cachedDevice); - } - } - } - @Override public int getHelpResource() { return R.string.help_url_bluetooth; @@ -237,10 +136,4 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme public String getDeviceListKey() { return KEY_AVAIL_DEVICES; } - - @VisibleForTesting - void showBluetoothTurnedOnToast() { - Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast, - Toast.LENGTH_SHORT).show(); - } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java new file mode 100644 index 00000000000..184f5212e77 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2023 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.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Pair; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +/** Tests for {@link BluetoothDevicePairingDetailBase}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) +public class BluetoothDevicePairingDetailBaseTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + public static final String KEY_DEVICE_LIST_GROUP = "test_key"; + + private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; + private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1"; + private final Context mContext = ApplicationProvider.getApplicationContext(); + + @Mock + private Resources mResource; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private LocalBluetoothManager mLocalManager; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private Drawable mDrawable; + private BluetoothAdapter mBluetoothAdapter; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private BluetoothProgressCategory mAvailableDevicesCategory; + private BluetoothDevice mBluetoothDevice; + private TestBluetoothDevicePairingDetailBase mFragment; + + @Before + public void setUp() { + mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext)); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); + final Pair pairs = new Pair<>(mDrawable, "fake_device"); + when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); + mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + + mFragment = spy(new TestBluetoothDevicePairingDetailBase()); + when(mFragment.findPreference(KEY_DEVICE_LIST_GROUP)).thenReturn(mAvailableDevicesCategory); + doReturn(mContext).when(mFragment).getContext(); + doReturn(mResource).when(mFragment).getResources(); + mFragment.mDeviceListGroup = mAvailableDevicesCategory; + mFragment.mLocalManager = mLocalManager; + mFragment.mBluetoothAdapter = mBluetoothAdapter; + mFragment.initPreferencesFromPreferenceScreen(); + + } + + @Test + public void startScanning_startScanAndRemoveDevices() { + mFragment.enableScanning(); + + verify(mFragment).startScanning(); + verify(mAvailableDevicesCategory).removeAll(); + } + + @Test + public void updateContent_stateOn() { + mFragment.updateContent(BluetoothAdapter.STATE_ON); + + assertThat(mBluetoothAdapter.isEnabled()).isTrue(); + verify(mFragment).enableScanning(); + } + + @Test + public void updateContent_stateOff_finish() { + mFragment.updateContent(BluetoothAdapter.STATE_OFF); + + verify(mFragment).finish(); + } + + @Test + public void updateBluetooth_bluetoothOff_turnOnBluetooth() { + mShadowBluetoothAdapter.setEnabled(false); + + mFragment.updateBluetooth(); + + assertThat(mBluetoothAdapter.isEnabled()).isTrue(); + } + + @Test + public void updateBluetooth_bluetoothOn_updateState() { + mShadowBluetoothAdapter.setEnabled(true); + doNothing().when(mFragment).updateContent(anyInt()); + + mFragment.updateBluetooth(); + + verify(mFragment).updateContent(anyInt()); + } + + @Test + public void onBluetoothStateChanged_whenTurnedOnBTShowToast() { + doNothing().when(mFragment).updateContent(anyInt()); + + mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); + + verify(mFragment).showBluetoothTurnedOnToast(); + } + + @Test + public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() { + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); + mFragment.mSelectedList.add(mBluetoothDevice); + mFragment.mSelectedList.add(device); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(device); + + mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); + + verify(mFragment).finish(); + } + + @Test + public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() { + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); + mFragment.mSelectedList.add(device); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + + mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); + + // not crash + } + + @Test + public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() { + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); + mFragment.mSelectedList.add(mBluetoothDevice); + mFragment.mSelectedList.add(device); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(false); + when(mCachedBluetoothDevice.getDevice()).thenReturn(device); + + mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED); + + // not crash + } + + @Test + public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() { + final BluetoothDevicePreference preference = + new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, + true, BluetoothDevicePreference.SortType.TYPE_FIFO); + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(device); + + mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); + + assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0); + } + + @Test + public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() { + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + final BluetoothDevicePreference preference = + new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, + true, BluetoothDevicePreference.SortType.TYPE_FIFO); + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); + mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(device); + when(cachedDevice.isConnected()).thenReturn(true); + when(cachedDevice.getDevice()).thenReturn(device2); + when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); + when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); + + mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP, + BluetoothAdapter.STATE_CONNECTED); + + // not crash + } + + private static class TestBluetoothDevicePairingDetailBase extends + BluetoothDevicePairingDetailBase { + + TestBluetoothDevicePairingDetailBase() { + super(); + } + + @Override + public int getMetricsCategory() { + return 0; + } + + @Override + public String getDeviceListKey() { + return KEY_DEVICE_LIST_GROUP; + } + + @Override + protected int getPreferenceScreenResId() { + return 0; + } + + @Override + protected String getLogTag() { + return "test_tag"; + } + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java index bac868f4585..865ed81e367 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java @@ -18,166 +18,86 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.Pair; +import android.os.Bundle; -import androidx.preference.PreferenceGroup; +import androidx.test.core.app.ApplicationProvider; -import com.android.settings.R; -import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; -import com.android.settingslib.bluetooth.BluetoothDeviceFilter; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.widget.FooterPreference; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowBluetoothAdapter.class}) public class BluetoothPairingDetailTest { - private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; - private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1"; + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private final Context mContext = ApplicationProvider.getApplicationContext(); - @Mock - private Resources mResource; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private LocalBluetoothManager mLocalManager; - @Mock - private PreferenceGroup mPreferenceGroup; - @Mock - private CachedBluetoothDevice mCachedBluetoothDevice; - @Mock - private Drawable mDrawable; - private BluetoothPairingDetail mFragment; - private Context mContext; private BluetoothProgressCategory mAvailableDevicesCategory; private FooterPreference mFooterPreference; private BluetoothAdapter mBluetoothAdapter; - private ShadowBluetoothAdapter mShadowBluetoothAdapter; - private BluetoothDevice mBluetoothDevice; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - - Pair pairs = new Pair<>(mDrawable, "fake_device"); - mContext = RuntimeEnvironment.application; mFragment = spy(new BluetoothPairingDetail()); doReturn(mContext).when(mFragment).getContext(); - doReturn(mResource).when(mFragment).getResources(); - when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); - when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); - mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext)); mFooterPreference = new FooterPreference(mContext); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); - mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + + doReturn(mAvailableDevicesCategory).when(mFragment) + .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES); + doReturn(mFooterPreference).when(mFragment) + .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF); mFragment.mBluetoothAdapter = mBluetoothAdapter; mFragment.mLocalManager = mLocalManager; - mFragment.mDeviceListGroup = mPreferenceGroup; - mFragment.mAlwaysDiscoverable = new AlwaysDiscoverable(mContext); + mFragment.mDeviceListGroup = mAvailableDevicesCategory; + mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY); } - +// @Test public void initPreferencesFromPreferenceScreen_findPreferences() { - doReturn(mAvailableDevicesCategory).when(mFragment) - .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES); - doReturn(mFooterPreference).when(mFragment) - .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF); - mFragment.initPreferencesFromPreferenceScreen(); assertThat(mFragment.mAvailableDevicesCategory).isEqualTo(mAvailableDevicesCategory); assertThat(mFragment.mFooterPreference).isEqualTo(mFooterPreference); } - @Test - public void startScanning_startScanAndRemoveDevices() { - mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; - mFragment.mDeviceListGroup = mAvailableDevicesCategory; - - mFragment.enableScanning(); - - verify(mFragment).startScanning(); - verify(mAvailableDevicesCategory).removeAll(); - } - @Test public void updateContent_stateOn_addDevices() { - mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; - mFragment.mFooterPreference = mFooterPreference; - doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean()); + mFragment.initPreferencesFromPreferenceScreen(); mFragment.updateContent(BluetoothAdapter.STATE_ON); - verify(mFragment).addDeviceCategory(mAvailableDevicesCategory, - R.string.bluetooth_preference_found_media_devices, - BluetoothDeviceFilter.ALL_FILTER, false); + assertThat(mFragment.mAlwaysDiscoverable.mStarted).isEqualTo(true); assertThat(mBluetoothAdapter.getScanMode()) .isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); } - @Test - public void updateContent_stateOff_finish() { - mFragment.updateContent(BluetoothAdapter.STATE_OFF); - - verify(mFragment).finish(); - } - - @Test - public void updateBluetooth_bluetoothOff_turnOnBluetooth() { - mShadowBluetoothAdapter.setEnabled(false); - - mFragment.updateBluetooth(); - - assertThat(mBluetoothAdapter.isEnabled()).isTrue(); - } - - @Test - public void updateBluetooth_bluetoothOn_updateState() { - mShadowBluetoothAdapter.setEnabled(true); - doNothing().when(mFragment).updateContent(anyInt()); - - mFragment.updateBluetooth(); - - verify(mFragment).updateContent(anyInt()); - } - @Test public void onScanningStateChanged_restartScanAfterInitialScanning() { - mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; - mFragment.mFooterPreference = mFooterPreference; - mFragment.mDeviceListGroup = mAvailableDevicesCategory; - doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean()); + mFragment.initPreferencesFromPreferenceScreen(); // Initial Bluetooth ON will trigger scan enable, list clear and scan start mFragment.updateContent(BluetoothAdapter.STATE_ON); @@ -219,97 +139,4 @@ public class BluetoothPairingDetailTest { // Verify that clean up only happen once at initialization verify(mAvailableDevicesCategory, times(1)).removeAll(); } - - @Test - public void onBluetoothStateChanged_whenTurnedOnBTShowToast() { - doNothing().when(mFragment).updateContent(anyInt()); - - mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); - - verify(mFragment).showBluetoothTurnedOnToast(); - } - - @Test - public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() { - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); - mFragment.mSelectedList.add(mBluetoothDevice); - mFragment.mSelectedList.add(device); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(true); - when(mCachedBluetoothDevice.getDevice()).thenReturn(device); - - mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); - - verify(mFragment).finish(); - } - - @Test - public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() { - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); - mFragment.mSelectedList.add(device); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(true); - when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); - - mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); - - // not crash - } - - @Test - public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() { - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); - mFragment.mSelectedList.add(mBluetoothDevice); - mFragment.mSelectedList.add(device); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(false); - when(mCachedBluetoothDevice.getDevice()).thenReturn(device); - - mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED); - - // not crash - } - - @Test - public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() { - final BluetoothDevicePreference preference = - new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, - true, BluetoothDevicePreference.SortType.TYPE_FIFO); - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); - mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(true); - when(mCachedBluetoothDevice.getDevice()).thenReturn(device); - - mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); - - assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0); - } - - @Test - public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() { - final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); - final BluetoothDevicePreference preference = - new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, - true, BluetoothDevicePreference.SortType.TYPE_FIFO); - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); - final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); - mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(true); - when(mCachedBluetoothDevice.getDevice()).thenReturn(device); - when(cachedDevice.isConnected()).thenReturn(true); - when(cachedDevice.getDevice()).thenReturn(device2); - when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); - when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); - - mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP, - BluetoothAdapter.STATE_CONNECTED); - - // not crash - } } \ No newline at end of file