[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
This commit is contained in:
jasonwshsu
2023-02-07 17:23:11 +08:00
parent 23417c3aca
commit 723c385c18
5 changed files with 521 additions and 335 deletions

View File

@@ -27,7 +27,7 @@
<com.android.settings.bluetooth.BluetoothProgressCategory <com.android.settings.bluetooth.BluetoothProgressCategory
android:key="available_devices" android:key="available_devices"
android:title="@string/bluetooth_paired_device_title"/> android:title="@string/bluetooth_preference_found_media_devices"/>
<com.android.settingslib.widget.FooterPreference/> <com.android.settingslib.widget.FooterPreference/>

View File

@@ -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();
}
}

View File

@@ -16,31 +16,25 @@
package com.android.settings.bluetooth; package com.android.settings.bluetooth;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context; import android.content.Context;
import android.os.Bundle; 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 androidx.annotation.VisibleForTesting;
import com.android.settings.R; 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.BluetoothDeviceFilter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
import com.android.settingslib.search.Indexable; import com.android.settingslib.search.Indexable;
import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.FooterPreference;
/** /**
* BluetoothPairingDetail is a page to scan bluetooth devices and pair them. * BluetoothPairingDetail is a page to scan bluetooth devices and pair them.
*/ */
public class BluetoothPairingDetail extends DeviceListPreferenceFragment implements public class BluetoothPairingDetail extends BluetoothDevicePairingDetailBase implements
Indexable { Indexable {
private static final String TAG = "BluetoothPairingDetail"; private static final String TAG = "BluetoothPairingDetail";
@@ -49,35 +43,13 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
@VisibleForTesting @VisibleForTesting
static final String KEY_FOOTER_PREF = "footer_preference"; static final String KEY_FOOTER_PREF = "footer_preference";
@VisibleForTesting
BluetoothProgressCategory mAvailableDevicesCategory;
@VisibleForTesting @VisibleForTesting
FooterPreference mFooterPreference; FooterPreference mFooterPreference;
@VisibleForTesting @VisibleForTesting
AlwaysDiscoverable mAlwaysDiscoverable; AlwaysDiscoverable mAlwaysDiscoverable;
private boolean mInitialScanStarted;
public BluetoothPairingDetail() { public BluetoothPairingDetail() {
super(DISALLOW_CONFIG_BLUETOOTH); super();
}
@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());
} }
@Override @Override
@@ -86,32 +58,29 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
use(BluetoothDeviceRenamePreferenceController.class).setFragment(this); use(BluetoothDeviceRenamePreferenceController.class).setFragment(this);
} }
@VisibleForTesting @Override
void updateBluetooth() { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (mBluetoothAdapter.isEnabled()) { super.onViewCreated(view, savedInstanceState);
updateContent(mBluetoothAdapter.getState()); mAlwaysDiscoverable = new AlwaysDiscoverable(getContext());
} else {
// Turn on bluetooth if it is disabled
mBluetoothAdapter.enable();
} }
@Override
public void onStart() {
super.onStart();
mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering());
} }
@Override @Override
public void onStop() { public void onStop() {
super.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. // Make the device only visible to connected devices.
mAlwaysDiscoverable.stop(); mAlwaysDiscoverable.stop();
disableScanning();
} }
@Override @Override
void initPreferencesFromPreferenceScreen() { public void initPreferencesFromPreferenceScreen() {
mAvailableDevicesCategory = (BluetoothProgressCategory) findPreference(KEY_AVAIL_DEVICES); super.initPreferencesFromPreferenceScreen();
mFooterPreference = (FooterPreference) findPreference(KEY_FOOTER_PREF); mFooterPreference = findPreference(KEY_FOOTER_PREF);
mFooterPreference.setSelectable(false); mFooterPreference.setSelectable(false);
} }
@@ -120,23 +89,25 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
return SettingsEnums.BLUETOOTH_PAIRING; return SettingsEnums.BLUETOOTH_PAIRING;
} }
/**
* {@inheritDoc}
*
* Will update footer and keep the device discoverable as long as the page is visible.
*/
@VisibleForTesting
@Override @Override
void enableScanning() { public void updateContent(int bluetoothState) {
// Clear all device states before first scan super.updateContent(bluetoothState);
if (!mInitialScanStarted) { if (bluetoothState == BluetoothAdapter.STATE_ON) {
if (mAvailableDevicesCategory != null) { if (mInitialScanStarted) {
removeAllDevices(); // Don't show bonded devices when screen turned back on
setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
addCachedDevices();
} }
mLocalManager.getCachedDeviceManager().clearNonBondedDevices(); setFilter(BluetoothDeviceFilter.ALL_FILTER);
mInitialScanStarted = true; updateFooterPreference(mFooterPreference);
mAlwaysDiscoverable.start();
} }
super.enableScanning();
}
@Override
void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
disableScanning();
super.onDevicePreferenceClick(btPreference);
} }
@Override @Override
@@ -146,78 +117,6 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
mAvailableDevicesCategory.setProgress(started); 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 @Override
public int getHelpResource() { public int getHelpResource() {
return R.string.help_url_bluetooth; return R.string.help_url_bluetooth;
@@ -237,10 +136,4 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
public String getDeviceListKey() { public String getDeviceListKey() {
return KEY_AVAIL_DEVICES; return KEY_AVAIL_DEVICES;
} }
@VisibleForTesting
void showBluetoothTurnedOnToast() {
Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
Toast.LENGTH_SHORT).show();
}
} }

View File

@@ -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<Drawable, String> 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";
}
}
}

View File

@@ -18,166 +18,86 @@ package com.android.settings.bluetooth;
import static com.google.common.truth.Truth.assertThat; 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.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.os.Bundle;
import android.graphics.drawable.Drawable;
import android.util.Pair;
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.bluetooth.LocalBluetoothManager;
import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.FooterPreference;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.Mock; 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.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class BluetoothPairingDetailTest { public class BluetoothPairingDetailTest {
private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; @Rule
private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1"; public final MockitoRule mockito = MockitoJUnit.rule();
private final Context mContext = ApplicationProvider.getApplicationContext();
@Mock
private Resources mResource;
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private LocalBluetoothManager mLocalManager; private LocalBluetoothManager mLocalManager;
@Mock
private PreferenceGroup mPreferenceGroup;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private Drawable mDrawable;
private BluetoothPairingDetail mFragment; private BluetoothPairingDetail mFragment;
private Context mContext;
private BluetoothProgressCategory mAvailableDevicesCategory; private BluetoothProgressCategory mAvailableDevicesCategory;
private FooterPreference mFooterPreference; private FooterPreference mFooterPreference;
private BluetoothAdapter mBluetoothAdapter; private BluetoothAdapter mBluetoothAdapter;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private BluetoothDevice mBluetoothDevice;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this);
Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
mContext = RuntimeEnvironment.application;
mFragment = spy(new BluetoothPairingDetail()); mFragment = spy(new BluetoothPairingDetail());
doReturn(mContext).when(mFragment).getContext(); 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)); mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
mFooterPreference = new FooterPreference(mContext); mFooterPreference = new FooterPreference(mContext);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
mFragment.mBluetoothAdapter = mBluetoothAdapter;
mFragment.mLocalManager = mLocalManager;
mFragment.mDeviceListGroup = mPreferenceGroup;
mFragment.mAlwaysDiscoverable = new AlwaysDiscoverable(mContext);
}
@Test
public void initPreferencesFromPreferenceScreen_findPreferences() {
doReturn(mAvailableDevicesCategory).when(mFragment) doReturn(mAvailableDevicesCategory).when(mFragment)
.findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES); .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
doReturn(mFooterPreference).when(mFragment) doReturn(mFooterPreference).when(mFragment)
.findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF); .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
mFragment.mBluetoothAdapter = mBluetoothAdapter;
mFragment.mLocalManager = mLocalManager;
mFragment.mDeviceListGroup = mAvailableDevicesCategory;
mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY);
}
//
@Test
public void initPreferencesFromPreferenceScreen_findPreferences() {
mFragment.initPreferencesFromPreferenceScreen(); mFragment.initPreferencesFromPreferenceScreen();
assertThat(mFragment.mAvailableDevicesCategory).isEqualTo(mAvailableDevicesCategory); assertThat(mFragment.mAvailableDevicesCategory).isEqualTo(mAvailableDevicesCategory);
assertThat(mFragment.mFooterPreference).isEqualTo(mFooterPreference); 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 @Test
public void updateContent_stateOn_addDevices() { public void updateContent_stateOn_addDevices() {
mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; mFragment.initPreferencesFromPreferenceScreen();
mFragment.mFooterPreference = mFooterPreference;
doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean());
mFragment.updateContent(BluetoothAdapter.STATE_ON); mFragment.updateContent(BluetoothAdapter.STATE_ON);
verify(mFragment).addDeviceCategory(mAvailableDevicesCategory, assertThat(mFragment.mAlwaysDiscoverable.mStarted).isEqualTo(true);
R.string.bluetooth_preference_found_media_devices,
BluetoothDeviceFilter.ALL_FILTER, false);
assertThat(mBluetoothAdapter.getScanMode()) assertThat(mBluetoothAdapter.getScanMode())
.isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); .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 @Test
public void onScanningStateChanged_restartScanAfterInitialScanning() { public void onScanningStateChanged_restartScanAfterInitialScanning() {
mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; mFragment.initPreferencesFromPreferenceScreen();
mFragment.mFooterPreference = mFooterPreference;
mFragment.mDeviceListGroup = mAvailableDevicesCategory;
doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean());
// Initial Bluetooth ON will trigger scan enable, list clear and scan start // Initial Bluetooth ON will trigger scan enable, list clear and scan start
mFragment.updateContent(BluetoothAdapter.STATE_ON); mFragment.updateContent(BluetoothAdapter.STATE_ON);
@@ -219,97 +139,4 @@ public class BluetoothPairingDetailTest {
// Verify that clean up only happen once at initialization // Verify that clean up only happen once at initialization
verify(mAvailableDevicesCategory, times(1)).removeAll(); 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
}
} }