Add a new Bluetooth device details page

Bug: 35877479
Test: make RunSettingsRoboTests

The existing behavior is to bring up a dialog with Bluetooth device
details with checkboxes for each supported profile. This adds a new page
that serves the same purpose with a switch for each profile and a footer
containing the MAC address.

Whether to use the new page or old dialog is controlled by a flag
accessible via BluetoothFeatureProvider.

Change-Id: I026c363d4cd33932a84017a67cbef51c258bad10
This commit is contained in:
Antony Sargent
2017-05-04 15:06:32 -07:00
parent 8d9177a06e
commit 04a3b2199e
19 changed files with 1823 additions and 63 deletions

View File

@@ -0,0 +1,160 @@
/*
* 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.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.widget.Button;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.testutils.shadow.SettingsShadowBluetoothDevice;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
shadows=SettingsShadowBluetoothDevice.class)
public class BluetoothDetailsButtonsControllerTest extends BluetoothDetailsControllerTestBase {
private BluetoothDetailsButtonsController mController;
private LayoutPreference mLayoutPreference;
private Button mLeftButton;
private Button mRightButton;
@Override
public void setUp() {
super.setUp();
mController = new BluetoothDetailsButtonsController(mContext, mFragment, mCachedDevice,
mLifecycle);
mLeftButton = new Button(mContext);
mRightButton = new Button(mContext);
mLayoutPreference = new LayoutPreference(mContext, R.layout.app_action_buttons);
mLayoutPreference.setKey(mController.getPreferenceKey());
mScreen.addPreference(mLayoutPreference);
mLeftButton = (Button) mLayoutPreference.findViewById(R.id.left_button);
mRightButton = (Button) mLayoutPreference.findViewById(R.id.right_button);
setupDevice(mDeviceConfig);
when(mCachedDevice.isBusy()).thenReturn(false);
}
@Test
public void connected() {
showScreen(mController);
assertThat(mLeftButton.getText()).isEqualTo(
mContext.getString(R.string.bluetooth_device_context_disconnect));
assertThat(mRightButton.getText()).isEqualTo(mContext.getString(R.string.forget));
}
@Test
public void clickOnDisconnect() {
showScreen(mController);
mLeftButton.callOnClick();
verify(mCachedDevice).disconnect();
}
@Test
public void clickOnConnect() {
when(mCachedDevice.isConnected()).thenReturn(false);
showScreen(mController);
assertThat(mLeftButton.getText()).isEqualTo(
mContext.getString(R.string.bluetooth_device_context_connect));
mLeftButton.callOnClick();
verify(mCachedDevice).connect(eq(true));
}
@Test
public void becomeDisconnected() {
showScreen(mController);
// By default we start out with the device connected.
assertThat(mLeftButton.getText()).isEqualTo(
mContext.getString(R.string.bluetooth_device_context_disconnect));
// Now make the device appear to have changed to disconnected.
when(mCachedDevice.isConnected()).thenReturn(false);
mController.onDeviceAttributesChanged();
assertThat(mLeftButton.getText()).isEqualTo(
mContext.getString(R.string.bluetooth_device_context_connect));
// Click the button and make sure that connect (not disconnect) gets called.
mLeftButton.callOnClick();
verify(mCachedDevice).connect(eq(true));
}
@Test
public void becomeConnected() {
// Start out with the device disconnected.
when(mCachedDevice.isConnected()).thenReturn(false);
showScreen(mController);
assertThat(mLeftButton.getText()).isEqualTo(
mContext.getString(R.string.bluetooth_device_context_connect));
// Now make the device appear to have changed to connected.
when(mCachedDevice.isConnected()).thenReturn(true);
mController.onDeviceAttributesChanged();
assertThat(mLeftButton.getText()).isEqualTo(
mContext.getString(R.string.bluetooth_device_context_disconnect));
// Click the button and make sure that disconnnect (not connect) gets called.
mLeftButton.callOnClick();
verify(mCachedDevice).disconnect();
}
@Test
public void forget() {
showScreen(mController);
mRightButton.callOnClick();
verify(mCachedDevice).unpair();
verify(mActivity).finish();
}
@Test
public void startsOutBusy() {
when(mCachedDevice.isBusy()).thenReturn(true);
showScreen(mController);
assertThat(mLeftButton.getText()).isEqualTo(
mContext.getString(R.string.bluetooth_device_context_disconnect));
assertThat(mRightButton.getText()).isEqualTo(mContext.getString(R.string.forget));
assertThat(mLeftButton.isEnabled()).isFalse();
// Now pretend the device became non-busy.
when(mCachedDevice.isBusy()).thenReturn(false);
mController.onDeviceAttributesChanged();
assertThat(mLeftButton.isEnabled()).isTrue();
}
@Test
public void becomesBusy() {
showScreen(mController);
assertThat(mLeftButton.isEnabled()).isTrue();
when(mCachedDevice.isBusy()).thenReturn(true);
mController.onDeviceAttributesChanged();
assertThat(mLeftButton.isEnabled()).isFalse();
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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 org.mockito.Matchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.shadow.SettingsShadowBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
shadows=SettingsShadowBluetoothDevice.class)
public class BluetoothDetailsControllerEventsTest extends BluetoothDetailsControllerTestBase {
static class TestController extends BluetoothDetailsController {
public TestController(Context context, PreferenceFragment fragment,
CachedBluetoothDevice device,
Lifecycle lifecycle) {
super(context, fragment, device, lifecycle);
}
@Override
public String getPreferenceKey() {
return null;
}
@Override
protected void init(PreferenceScreen screen) {}
@Override
protected void refresh() {}
}
@Test
public void pauseResumeEvents() {
TestController controller = spy(new TestController(mContext, mFragment, mCachedDevice,
mLifecycle));
verify(mLifecycle).addObserver(any(BluetoothDetailsController.class));
showScreen(controller);
verify(mCachedDevice, times(1)).registerCallback(controller);
verify(controller, times(1)).refresh();
controller.onPause();
verify(controller, times(1)).refresh();
verify(mCachedDevice).unregisterCallback(controller);
controller.onResume();
verify(controller, times(2)).refresh();
verify(mCachedDevice, times(2)).registerCallback(controller);
// The init function should only have been called once
verify(controller, times(1)).init(mScreen);
}
}

View File

@@ -0,0 +1,157 @@
/*
* 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 org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
public class BluetoothDetailsControllerTestBase {
protected Context mContext = RuntimeEnvironment.application;
protected Lifecycle mLifecycle;
protected DeviceConfig mDeviceConfig;
protected BluetoothDevice mDevice;
protected BluetoothManager mBluetoothManager;
protected BluetoothAdapter mBluetoothAdapter;
protected PreferenceScreen mScreen;
protected PreferenceManager mPreferenceManager;
@Mock
protected BluetoothDeviceDetailsFragment mFragment;
@Mock
protected CachedBluetoothDevice mCachedDevice;
@Mock
protected Activity mActivity;
@Mock
protected BluetoothClass mBluetoothDeviceClass;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mPreferenceManager = new PreferenceManager(mContext);
mScreen = mPreferenceManager.createPreferenceScreen(mContext);
mDeviceConfig = makeDefaultDeviceConfig();
when(mFragment.getActivity()).thenReturn(mActivity);
when(mActivity.getApplicationContext()).thenReturn(mContext);
when(mFragment.getContext()).thenReturn(mContext);
when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
mLifecycle = spy(new Lifecycle());
mBluetoothManager = new BluetoothManager(mContext);
mBluetoothAdapter = mBluetoothManager.getAdapter();
}
protected static class DeviceConfig {
private String name;
private String address;
private int majorDeviceClass;
private boolean connected;
private int connectionSummary;
public DeviceConfig setName(String newValue) {
this.name = newValue;
return this;
}
public DeviceConfig setAddress(String newValue) {
this.address = newValue;
return this;
}
public DeviceConfig setMajorDeviceClass(int newValue) {
this.majorDeviceClass = newValue;
return this;
}
public DeviceConfig setConnected(boolean newValue) {
this.connected = newValue;
return this;
}
public DeviceConfig setConnectionSummary(int newValue) {
this.connectionSummary = newValue;
return this;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public int getMajorDeviceClass() {
return majorDeviceClass;
}
public boolean isConnected() {
return connected;
}
public int getConnectionSummary() {
return connectionSummary;
}
}
protected static DeviceConfig makeDefaultDeviceConfig() {
return new DeviceConfig()
.setName("Mock Device")
.setAddress("B4:B0:34:B5:3B:1B")
.setMajorDeviceClass(BluetoothClass.Device.Major.AUDIO_VIDEO)
.setConnected(true)
.setConnectionSummary(R.string.bluetooth_connected);
}
/**
* Sets up the device mock to return various state based on a test config.
* @param config
*/
protected void setupDevice(DeviceConfig config) {
when(mCachedDevice.getName()).thenReturn(config.getName());
when(mBluetoothDeviceClass.getMajorDeviceClass()).thenReturn(config.getMajorDeviceClass());
when(mCachedDevice.isConnected()).thenReturn(config.isConnected());
when(mCachedDevice.getConnectionSummary()).thenReturn(config.getConnectionSummary());
mDevice = mBluetoothAdapter.getRemoteDevice(mDeviceConfig.getAddress());
when(mCachedDevice.getDevice()).thenReturn(mDevice);
}
/**
* Convenience method to call displayPreference and onResume.
*/
protected void showScreen(BluetoothDetailsController controller) {
controller.displayPreference(mScreen);
controller.onResume();
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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 org.mockito.Matchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.drawable.Drawable;
import android.support.v7.preference.Preference;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.SettingsShadowBluetoothDevice;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.R;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
shadows={SettingsShadowBluetoothDevice.class, ShadowEntityHeaderController.class})
public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsControllerTestBase {
private BluetoothDetailsHeaderController mController;
private Preference mPreference;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private EntityHeaderController mHeaderController;
@Override
public void setUp() {
super.setUp();
FakeFeatureFactory.setupForTest(spy(mContext));
ShadowEntityHeaderController.setUseMock(mHeaderController);
mController = new BluetoothDetailsHeaderController(mContext, mFragment, mCachedDevice,
mLifecycle);
mPreference = new Preference(mContext);
mPreference.setKey(mController.getPreferenceKey());
mScreen.addPreference(mPreference);
setupDevice(mDeviceConfig);
}
@After
public void tearDown() {
ShadowEntityHeaderController.reset();
}
@Test
public void header() {
showScreen(mController);
verify(mHeaderController).setLabel(mDeviceConfig.getName());
verify(mHeaderController).setIcon(any(Drawable.class));
verify(mHeaderController).setIconContentDescription(any(String.class));
verify(mHeaderController).setSummary(any(String.class));
verify(mHeaderController).done(mActivity, mContext);
verify(mHeaderController).done(mActivity, false);
}
@Test
public void connectionStatusChangesWhileScreenOpen() {
ArrayList<LocalBluetoothProfile> profiles = new ArrayList<>();
InOrder inOrder = inOrder(mHeaderController);
when(mCachedDevice.getConnectionSummary()).thenReturn(R.string.bluetooth_connected);
showScreen(mController);
inOrder.verify(mHeaderController).setSummary(mContext.getString(R.string.bluetooth_connected));
when(mCachedDevice.getConnectionSummary()).thenReturn(0);
mController.onDeviceAttributesChanged();
inOrder.verify(mHeaderController).setSummary((CharSequence) null);
when(mCachedDevice.getConnectionSummary()).thenReturn(R.string.bluetooth_connecting);
mController.onDeviceAttributesChanged();
inOrder.verify(mHeaderController).setSummary(
mContext.getString(R.string.bluetooth_connecting));
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.shadow.SettingsShadowBluetoothDevice;
import com.android.settingslib.widget.FooterPreference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
shadows=SettingsShadowBluetoothDevice.class)
public class BluetoothDetailsMacAddressControllerTest extends BluetoothDetailsControllerTestBase {
private BluetoothDetailsMacAddressController mController;
@Override
public void setUp() {
super.setUp();
mController = new BluetoothDetailsMacAddressController(mContext, mFragment, mCachedDevice,
mLifecycle);
setupDevice(mDeviceConfig);
}
@Test
public void macAddress() {
showScreen(mController);
FooterPreference footer = (FooterPreference) mScreen.findPreference(
mController.getPreferenceKey());
assertThat(footer.getTitle().toString()).endsWith(mDeviceConfig.getAddress());
}
}

View File

@@ -0,0 +1,450 @@
/*
* 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.eq;
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.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceManager;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.shadow.SettingsShadowBluetoothDevice;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.MapProfile;
import com.android.settingslib.bluetooth.PbapServerProfile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
shadows=SettingsShadowBluetoothDevice.class)
public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsControllerTestBase {
private BluetoothDetailsProfilesController mController;
private List<LocalBluetoothProfile> mConnectableProfiles;
private PreferenceCategory mProfiles;
@Mock
private LocalBluetoothManager mLocalManager;
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Override
public void setUp() {
super.setUp();
mProfiles = spy(new PreferenceCategory(mContext));
when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager);
mConnectableProfiles = new ArrayList<>();
when(mLocalManager.getProfileManager()).thenReturn(mProfileManager);
when(mCachedDevice.getConnectableProfiles()).thenAnswer(invocation ->
new ArrayList<>(mConnectableProfiles)
);
setupDevice(mDeviceConfig);
mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
mCachedDevice, mLifecycle);
mProfiles.setKey(mController.getPreferenceKey());
mScreen.addPreference(mProfiles);
}
static class FakeBluetoothProfile implements LocalBluetoothProfile {
protected HashSet<BluetoothDevice> mConnectedDevices;
protected HashMap<BluetoothDevice, Boolean> mPreferred;
protected Context mContext;
protected int mNameResourceId;
public FakeBluetoothProfile(Context context, int nameResourceId) {
mConnectedDevices = new HashSet<>();
mPreferred = new HashMap<>();
mContext = context;
mNameResourceId = nameResourceId;
}
@Override
public String toString() {
return mContext.getString(mNameResourceId);
}
@Override
public boolean isConnectable() {
return true;
}
@Override
public boolean isAutoConnectable() {
return true;
}
@Override
public boolean connect(BluetoothDevice device) {
mConnectedDevices.add(device);
return true;
}
@Override
public boolean disconnect(BluetoothDevice device) {
mConnectedDevices.remove(device);
return false;
}
@Override
public int getConnectionStatus(BluetoothDevice device) {
if (mConnectedDevices.contains(device)) {
return BluetoothProfile.STATE_CONNECTED;
} else {
return BluetoothProfile.STATE_DISCONNECTED;
}
}
@Override
public boolean isPreferred(BluetoothDevice device) {
return mPreferred.getOrDefault(device, false);
}
@Override
public int getPreferred(BluetoothDevice device) {
return isPreferred(device) ?
BluetoothProfile.PRIORITY_ON : BluetoothProfile.PRIORITY_OFF;
}
@Override
public void setPreferred(BluetoothDevice device, boolean preferred) {
mPreferred.put(device, preferred);
}
@Override
public boolean isProfileReady() {
return true;
}
@Override
public int getOrdinal() {
return 0;
}
@Override
public int getNameResource(BluetoothDevice device) {
return mNameResourceId;
}
@Override
public int getSummaryResourceForDevice(BluetoothDevice device) {
return Utils.getConnectionStateSummary(getConnectionStatus(device));
}
@Override
public int getDrawableResource(BluetoothClass btClass) {
return 0;
}
}
/**
* Creates and adds a mock LocalBluetoothProfile to the list of connectable profiles for the
* device.
@param profileNameResId the resource id for the name used by this profile
@param deviceIsPreferred whether this profile should start out as enabled for the device
*/
private LocalBluetoothProfile addFakeProfile(int profileNameResId,
boolean deviceIsPreferred) {
LocalBluetoothProfile profile = new FakeBluetoothProfile(mContext, profileNameResId);
profile.setPreferred(mDevice, deviceIsPreferred);
mConnectableProfiles.add(profile);
when(mProfileManager.getProfileByName(eq(profile.toString()))).thenReturn(profile);
return profile;
}
/** Returns the list of SwitchPreference objects added to the screen - there should be one per
* Bluetooth profile.
*/
private List<SwitchPreference> getProfileSwitches(boolean expectOnlyMConnectable) {
if (expectOnlyMConnectable) {
assertThat(mConnectableProfiles).isNotEmpty();
assertThat(mProfiles.getPreferenceCount()).isEqualTo(mConnectableProfiles.size());
}
ArrayList<SwitchPreference> result = new ArrayList<>();
for (int i = 0; i < mProfiles.getPreferenceCount(); i++) {
result.add((SwitchPreference)mProfiles.getPreference(i));
}
return result;
}
private void verifyProfileSwitchTitles(List<SwitchPreference> switches) {
for (int i = 0; i < switches.size(); i++) {
String expectedTitle = mContext.getString(
mConnectableProfiles.get(i).getNameResource(mDevice));
assertThat(switches.get(i).getTitle()).isEqualTo(expectedTitle);
}
}
@Test
public void oneProfile() {
addFakeProfile(R.string.bluetooth_profile_a2dp, true);
showScreen(mController);
verifyProfileSwitchTitles(getProfileSwitches(true));
}
@Test
public void multipleProfiles() {
addFakeProfile(R.string.bluetooth_profile_a2dp, true);
addFakeProfile(R.string.bluetooth_profile_headset, false);
showScreen(mController);
List<SwitchPreference> switches = getProfileSwitches(true);
verifyProfileSwitchTitles(switches);
assertThat(switches.get(0).isChecked()).isTrue();
assertThat(switches.get(1).isChecked()).isFalse();
// Both switches should be enabled.
assertThat(switches.get(0).isEnabled()).isTrue();
assertThat(switches.get(1).isEnabled()).isTrue();
// Make device busy.
when(mCachedDevice.isBusy()).thenReturn(true);
mController.onDeviceAttributesChanged();
// There should have been no new switches added.
assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
// Make sure both switches got disabled.
assertThat(switches.get(0).isEnabled()).isFalse();
assertThat(switches.get(1).isEnabled()).isFalse();
}
@Test
public void disableThenReenableOneProfile() {
addFakeProfile(R.string.bluetooth_profile_a2dp, true);
addFakeProfile(R.string.bluetooth_profile_headset, true);
showScreen(mController);
List<SwitchPreference> switches = getProfileSwitches(true);
SwitchPreference pref = switches.get(0);
// Clicking the pref should cause the profile to become not-preferred.
assertThat(pref.isChecked()).isTrue();
pref.performClick();
assertThat(pref.isChecked()).isFalse();
assertThat(mConnectableProfiles.get(0).isPreferred(mDevice)).isFalse();
// Make sure no new preferences were added.
assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
// Clicking the pref again should make the profile once again preferred.
pref.performClick();
assertThat(pref.isChecked()).isTrue();
assertThat(mConnectableProfiles.get(0).isPreferred(mDevice)).isTrue();
// Make sure we still haven't gotten any new preferences added.
assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
}
@Test
public void disconnectedDeviceOneProfile() {
setupDevice(makeDefaultDeviceConfig().setConnected(false).setConnectionSummary(0));
addFakeProfile(R.string.bluetooth_profile_a2dp, true);
showScreen(mController);
verifyProfileSwitchTitles(getProfileSwitches(true));
}
@Test
public void pbapProfileStartsEnabled() {
setupDevice(makeDefaultDeviceConfig());
when(mCachedDevice.getPhonebookPermissionChoice()).thenReturn(
CachedBluetoothDevice.ACCESS_ALLOWED);
PbapServerProfile psp = mock(PbapServerProfile.class);
when(psp.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_pbap);
when(psp.toString()).thenReturn(PbapServerProfile.NAME);
when(mProfileManager.getPbapProfile()).thenReturn(psp);
showScreen(mController);
List<SwitchPreference> switches = getProfileSwitches(false);
assertThat(switches.size()).isEqualTo(1);
SwitchPreference pref = switches.get(0);
assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_pbap));
assertThat(pref.isChecked()).isTrue();
pref.performClick();
assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
verify(mCachedDevice).setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
}
@Test
public void pbapProfileStartsDisabled() {
setupDevice(makeDefaultDeviceConfig());
when(mCachedDevice.getPhonebookPermissionChoice()).thenReturn(
CachedBluetoothDevice.ACCESS_REJECTED);
PbapServerProfile psp = mock(PbapServerProfile.class);
when(psp.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_pbap);
when(psp.toString()).thenReturn(PbapServerProfile.NAME);
when(mProfileManager.getPbapProfile()).thenReturn(psp);
showScreen(mController);
List<SwitchPreference> switches = getProfileSwitches(false);
assertThat(switches.size()).isEqualTo(1);
SwitchPreference pref = switches.get(0);
assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_pbap));
assertThat(pref.isChecked()).isFalse();
pref.performClick();
assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
verify(mCachedDevice).setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
}
@Test
public void mapProfile() {
setupDevice(makeDefaultDeviceConfig());
MapProfile mapProfile = mock(MapProfile.class);
when(mapProfile.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_map);
when(mProfileManager.getMapProfile()).thenReturn(mapProfile);
when(mProfileManager.getProfileByName(eq(mapProfile.toString()))).thenReturn(mapProfile);
when(mCachedDevice.getMessagePermissionChoice()).thenReturn(
CachedBluetoothDevice.ACCESS_REJECTED);
showScreen(mController);
List<SwitchPreference> switches = getProfileSwitches(false);
assertThat(switches.size()).isEqualTo(1);
SwitchPreference pref = switches.get(0);
assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_map));
assertThat(pref.isChecked()).isFalse();
pref.performClick();
assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
verify(mCachedDevice).setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
}
private A2dpProfile addMockA2dpProfile(boolean preferred, boolean supportsHighQualityAudio,
boolean highQualityAudioEnabled) {
A2dpProfile profile = mock(A2dpProfile.class);
when(mProfileManager.getProfileByName(eq(profile.toString()))).thenReturn(profile);
when(profile.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_a2dp);
when(profile.getHighQualityAudioOptionLabel(mDevice)).thenReturn(mContext.getString(
R.string.bluetooth_profile_a2dp_high_quality_unknown_codec));
when(profile.supportsHighQualityAudio(mDevice)).thenReturn(supportsHighQualityAudio);
when(profile.isHighQualityAudioEnabled(mDevice)).thenReturn(highQualityAudioEnabled);
when(profile.isPreferred(mDevice)).thenReturn(preferred);
mConnectableProfiles.add(profile);
return profile;
}
private SwitchPreference getHighQualityAudioPref() {
return (SwitchPreference) mScreen.findPreference(
BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
}
@Test
public void highQualityAudio_prefIsPresentWhenSupported() {
setupDevice(makeDefaultDeviceConfig());
addMockA2dpProfile(true, true, true);
showScreen(mController);
SwitchPreference pref = getHighQualityAudioPref();
assertThat(pref.getKey()).isEqualTo(
BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
// Make sure the preference works when clicked on.
pref.performClick();
A2dpProfile profile = (A2dpProfile) mConnectableProfiles.get(0);
verify(profile).setHighQualityAudioEnabled(mDevice, false);
pref.performClick();
verify(profile).setHighQualityAudioEnabled(mDevice, true);
}
@Test
public void highQualityAudio_prefIsAbsentWhenNotSupported() {
setupDevice(makeDefaultDeviceConfig());
addMockA2dpProfile(true, false, false);
showScreen(mController);
assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
SwitchPreference pref = (SwitchPreference) mProfiles.getPreference(0);
assertThat(pref.getKey()).isNotEqualTo(
BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_a2dp));
}
@Test
public void highQualityAudio_busyDeviceDisablesSwitch() {
setupDevice(makeDefaultDeviceConfig());
addMockA2dpProfile(true, true, true);
when(mCachedDevice.isBusy()).thenReturn(true);
showScreen(mController);
SwitchPreference pref = getHighQualityAudioPref();
assertThat(pref.isEnabled()).isFalse();
}
@Test
public void highQualityAudio_mediaAudioDisabledAndReEnabled() {
setupDevice(makeDefaultDeviceConfig());
A2dpProfile audioProfile = addMockA2dpProfile(true, true, true);
showScreen(mController);
assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
// Disabling media audio should cause the high quality audio switch to disappear, but not
// the regular audio one.
SwitchPreference audioPref = (SwitchPreference) mScreen.findPreference(
audioProfile.toString());
audioPref.performClick();
verify(audioProfile).setPreferred(mDevice, false);
when(audioProfile.isPreferred(mDevice)).thenReturn(false);
mController.onDeviceAttributesChanged();
assertThat(audioPref.isVisible()).isTrue();
SwitchPreference highQualityAudioPref = getHighQualityAudioPref();
assertThat(highQualityAudioPref.isVisible()).isFalse();
// And re-enabling media audio should make high quality switch to reappear.
audioPref.performClick();
verify(audioProfile).setPreferred(mDevice, true);
when(audioProfile.isPreferred(mDevice)).thenReturn(true);
mController.onDeviceAttributesChanged();
highQualityAudioPref = getHighQualityAudioPref();
assertThat(highQualityAudioPref.isVisible()).isTrue();
}
@Test
public void highQualityAudio_mediaAudioStartsDisabled() {
setupDevice(makeDefaultDeviceConfig());
A2dpProfile audioProfile = addMockA2dpProfile(false, true, true);
showScreen(mController);
SwitchPreference audioPref = (SwitchPreference) mScreen.findPreference(
audioProfile.toString());
SwitchPreference highQualityAudioPref = getHighQualityAudioPref();
assertThat(audioPref).isNotNull();
assertThat(audioPref.isChecked()).isFalse();
assertThat(highQualityAudioPref).isNotNull();
assertThat(highQualityAudioPref.isVisible()).isFalse();
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.testutils.shadow;
import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.Parcelable;
import org.robolectric.shadows.ShadowBluetoothDevice;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@Implements(BluetoothDevice.class)
public class SettingsShadowBluetoothDevice {
private String mAddress;
public void __constructor__(String address) {
mAddress = address;
}
@Implementation
public String getAddress() {
return mAddress;
}
@Implementation
public int hashCode() {
return mAddress.hashCode();
}
}