fix: 引入Settings的Module

This commit is contained in:
2024-12-10 14:57:24 +08:00
parent ad8fc8731d
commit df105485bd
6934 changed files with 896168 additions and 2 deletions

View File

@@ -0,0 +1,557 @@
/*
* Copyright (C) 2019 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.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.provider.DeviceConfig;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.fuelgauge.BatteryMeterView;
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.LayoutPreference;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.HashSet;
import java.util.Set;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowEntityHeaderController.class, ShadowDeviceConfig.class})
public class AdvancedBluetoothDetailsHeaderControllerTest {
private static final int BATTERY_LEVEL_MAIN = 30;
private static final int BATTERY_LEVEL_LEFT = 25;
private static final int BATTERY_LEVEL_RIGHT = 45;
private static final int LOW_BATTERY_LEVEL = 15;
private static final int CASE_LOW_BATTERY_LEVEL = 19;
private static final int LOW_BATTERY_LEVEL_THRESHOLD = 15;
private static final int BATTERY_LEVEL_5 = 5;
private static final int BATTERY_LEVEL_50 = 50;
private static final String ICON_URI = "content://test.provider/icon.png";
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
private static final String DEVICE_SUMMARY = "test summary";
private Context mContext;
@Mock
private BluetoothDevice mBluetoothDevice;
@Mock
private Bitmap mBitmap;
@Mock
private ImageView mImageView;
@Mock
private CachedBluetoothDevice mCachedDevice;
@Mock
private BluetoothAdapter mBluetoothAdapter;
private AdvancedBluetoothDetailsHeaderController mController;
private LayoutPreference mLayoutPreference;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new AdvancedBluetoothDetailsHeaderController(mContext, "pref_Key");
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
mController.init(mCachedDevice);
mLayoutPreference = new LayoutPreference(mContext,
LayoutInflater.from(mContext).inflate(R.layout.advanced_bt_entity_header, null));
mController.mLayoutPreference = mLayoutPreference;
mController.mBluetoothAdapter = mBluetoothAdapter;
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCachedDevice.getIdentityAddress()).thenReturn(MAC_ADDRESS);
}
@Test
public void createBatteryIcon_hasCorrectInfo() {
final Drawable drawable = mController.createBtBatteryIcon(mContext, BATTERY_LEVEL_MAIN,
true /* charging */);
assertThat(drawable).isInstanceOf(BatteryMeterView.BatteryMeterDrawable.class);
final BatteryMeterView.BatteryMeterDrawable iconDrawable =
(BatteryMeterView.BatteryMeterDrawable) drawable;
assertThat(iconDrawable.getBatteryLevel()).isEqualTo(BATTERY_LEVEL_MAIN);
assertThat(iconDrawable.getCharging()).isTrue();
}
@Test
public void refresh_connectedWatch_behaveAsExpected() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
String.valueOf(false).getBytes());
when(mCachedDevice.isConnected()).thenReturn(true);
mController.refresh();
assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
View.VISIBLE);
// TODO (b/188954766) : clarify settings design
}
@Test
public void refresh_connectedWatch_unknownBatteryLevel_shouldNotShowBatteryLevel() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
String.valueOf(false).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
String.valueOf(BluetoothUtils.META_INT_ERROR).getBytes());
when(mBluetoothDevice.getBatteryLevel()).thenReturn(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
when(mCachedDevice.isConnected()).thenReturn(true);
mController.refresh();
assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
View.VISIBLE);
assertThat(mLayoutPreference.findViewById(R.id.layout_middle)
.requireViewById(R.id.bt_battery_summary).getVisibility()).isEqualTo(View.GONE);
}
@Test
public void refresh_connectedStylus_behaveAsExpected() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
String.valueOf(false).getBytes());
when(mCachedDevice.isConnected()).thenReturn(true);
mController.refresh();
assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
View.VISIBLE);
}
@Test
public void refresh_connectedUnknownType_behaveAsExpected() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
"UNKNOWN_TYPE".getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
String.valueOf(false).getBytes());
when(mCachedDevice.isConnected()).thenReturn(true);
mController.refresh();
assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
View.VISIBLE);
}
@Test
public void refresh_connectedUntetheredHeadset_behaveAsExpected() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
String.valueOf(false).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
String.valueOf(BATTERY_LEVEL_LEFT).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
String.valueOf(BATTERY_LEVEL_RIGHT).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
String.valueOf(BATTERY_LEVEL_MAIN).getBytes());
when(mCachedDevice.isConnected()).thenReturn(true);
mController.refresh();
assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_left), BATTERY_LEVEL_LEFT);
assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_right), BATTERY_LEVEL_RIGHT);
assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_middle), BATTERY_LEVEL_MAIN);
}
@Test
public void refresh_connected_updateCorrectInfo() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
String.valueOf(true).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
String.valueOf(BATTERY_LEVEL_LEFT).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
String.valueOf(BATTERY_LEVEL_RIGHT).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
String.valueOf(BATTERY_LEVEL_MAIN).getBytes());
when(mCachedDevice.isConnected()).thenReturn(true);
mController.refresh();
assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_left), BATTERY_LEVEL_LEFT);
assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_right), BATTERY_LEVEL_RIGHT);
assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_middle), BATTERY_LEVEL_MAIN);
}
@Test
public void refresh_disconnected_updateCorrectInfo() {
when(mCachedDevice.isConnected()).thenReturn(false);
mController.refresh();
final LinearLayout layout = mLayoutPreference.findViewById(R.id.layout_middle);
assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
View.GONE);
assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
View.GONE);
assertThat(layout.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(layout.findViewById(R.id.header_title).getVisibility()).isEqualTo(View.GONE);
assertThat(layout.findViewById(R.id.bt_battery_summary).getVisibility()).isEqualTo(
View.GONE);
assertThat(layout.findViewById(R.id.bt_battery_icon).getVisibility()).isEqualTo(View.GONE);
assertThat(layout.findViewById(R.id.header_icon).getVisibility()).isEqualTo(View.VISIBLE);
}
@Ignore
@Test
public void refresh_connectedWatch_checkSummary() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
when(mCachedDevice.isConnected()).thenReturn(true);
when(mCachedDevice.getConnectionSummary(/* shortSummary= */ true))
.thenReturn(DEVICE_SUMMARY);
mController.refresh();
assertThat(((TextView) (mLayoutPreference.findViewById(R.id.entity_header_summary)))
.getText()).isEqualTo(DEVICE_SUMMARY);
}
@Test
public void refresh_withLowBatteryAndUncharged_showAlertIcon() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
String.valueOf(true).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
String.valueOf(LOW_BATTERY_LEVEL).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
String.valueOf(LOW_BATTERY_LEVEL).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
String.valueOf(CASE_LOW_BATTERY_LEVEL).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING)).thenReturn(
String.valueOf(false).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING)).thenReturn(
String.valueOf(true).getBytes());
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING)).thenReturn(
String.valueOf(false).getBytes());
when(mCachedDevice.isConnected()).thenReturn(true);
mController.refresh();
assertBatteryIcon(mLayoutPreference.findViewById(R.id.layout_left),
R.drawable.ic_battery_alert_24dp);
assertBatteryIcon(mLayoutPreference.findViewById(R.id.layout_right), /* resId= */-1);
assertBatteryIcon(mLayoutPreference.findViewById(R.id.layout_middle),
R.drawable.ic_battery_alert_24dp);
}
@Test
public void getAvailabilityStatus_untetheredHeadsetWithConfigOn_returnAvailable() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("true".getBytes());
assertThat(mController.getAvailabilityStatus()).isEqualTo(
BasePreferenceController.AVAILABLE);
}
@Test
public void getAvailabilityStatus_untetheredHeadsetWithConfigOff_returnUnavailable() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "false", true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("true".getBytes());
assertThat(mController.getAvailabilityStatus()).isEqualTo(
BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
}
@Test
public void getAvailabilityStatus_notUntetheredHeadsetWithConfigOn_returnUnavailable() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("false".getBytes());
assertThat(mController.getAvailabilityStatus()).isEqualTo(
BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
}
@Test
public void getAvailabilityStatus_notUntetheredHeadsetWithConfigOff_returnUnavailable() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "false", true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("false".getBytes());
assertThat(mController.getAvailabilityStatus()).isEqualTo(
BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
}
@Test
public void updateIcon_existInCache_setImageBitmap() {
mController.mIconCache.put(ICON_URI, mBitmap);
mController.updateIcon(mImageView, ICON_URI);
verify(mImageView).setImageBitmap(mBitmap);
}
@Test
public void onStart_isAvailable_registerCallback() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("true".getBytes());
Set<CachedBluetoothDevice> cacheBluetoothDevices = new HashSet<>();
when(mCachedDevice.getMemberDevice()).thenReturn(cacheBluetoothDevices);
when(mBluetoothAdapter.addOnMetadataChangedListener(
mBluetoothDevice, mContext.getMainExecutor(), mController.mMetadataListener))
.thenReturn(true);
mController.onStart();
verify(mCachedDevice).registerCallback(mController);
verify(mBluetoothAdapter).addOnMetadataChangedListener(mBluetoothDevice,
mContext.getMainExecutor(), mController.mMetadataListener);
}
@Test
public void onStart_notAvailable_notNeedToRegisterCallback() {
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("false".getBytes());
mController.onStart();
verify(mCachedDevice, never()).registerCallback(mController);
verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(mBluetoothDevice,
mContext.getMainExecutor(), mController.mMetadataListener);
}
@Test
public void onStart_isAvailableButNoBluetoothDevice_notNeedToRegisterCallback() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("true".getBytes());
when(mCachedDevice.getDevice()).thenReturn(null);
Set<CachedBluetoothDevice> cacheBluetoothDevices = new HashSet<>();
when(mCachedDevice.getMemberDevice()).thenReturn(cacheBluetoothDevices);
mController.onStart();
verify(mCachedDevice, never()).registerCallback(mController);
verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(mBluetoothDevice,
mContext.getMainExecutor(), mController.mMetadataListener);
}
@Test
public void onStop_availableAndHasBluetoothDevice_unregisterCallback() {
onStart_isAvailable_registerCallback();
mController.onStop();
verify(mCachedDevice).unregisterCallback(mController);
verify(mBluetoothAdapter).removeOnMetadataChangedListener(mBluetoothDevice,
mController.mMetadataListener);
}
@Test
public void onStop_noBluetoothDevice_noNeedToUnregisterCallback() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("true".getBytes());
when(mCachedDevice.getDevice()).thenReturn(null);
mController.onStart();
mController.onStop();
verify(mCachedDevice, never()).unregisterCallback(mController);
verify(mBluetoothAdapter, never()).removeOnMetadataChangedListener(mBluetoothDevice,
mController.mMetadataListener);
}
@Test
public void onDestroy_recycleBitmap() {
mController.mIconCache.put(ICON_URI, mBitmap);
mController.onDestroy();
assertThat(mController.mIconCache).isEmpty();
verify(mBitmap).recycle();
}
@Test
public void estimateReadyIsBothAvailable_showsView() {
mController.mIsLeftDeviceEstimateReady = true;
mController.mIsRightDeviceEstimateReady = true;
mController.showBothDevicesBatteryPredictionIfNecessary();
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
View.VISIBLE);
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
View.VISIBLE);
}
@Test
public void leftDeviceEstimateIsReadyRightDeviceIsNotReady_notShowView() {
mController.mIsLeftDeviceEstimateReady = true;
mController.mIsRightDeviceEstimateReady = false;
mController.showBothDevicesBatteryPredictionIfNecessary();
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
View.GONE);
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
View.GONE);
}
@Test
public void leftDeviceEstimateIsNotReadyRightDeviceIsReady_notShowView() {
mController.mIsLeftDeviceEstimateReady = false;
mController.mIsRightDeviceEstimateReady = true;
mController.showBothDevicesBatteryPredictionIfNecessary();
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
View.GONE);
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
View.GONE);
}
@Test
public void bothDevicesEstimateIsNotReady_notShowView() {
mController.mIsLeftDeviceEstimateReady = false;
mController.mIsRightDeviceEstimateReady = false;
mController.showBothDevicesBatteryPredictionIfNecessary();
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
View.GONE);
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
View.GONE);
}
@Test
public void showBatteryPredictionIfNecessary_estimateReadyIsAvailable_showCorrectValue() {
final String leftBatteryPrediction =
StringUtil.formatElapsedTime(mContext, 12000000, false, false).toString();
final String rightBatteryPrediction =
StringUtil.formatElapsedTime(mContext, 1200000, false, false).toString();
mController.showBatteryPredictionIfNecessary(1, 12000000,
mLayoutPreference.findViewById(R.id.layout_left));
mController.showBatteryPredictionIfNecessary(1, 1200000,
mLayoutPreference.findViewById(R.id.layout_right));
assertBatteryPrediction(mLayoutPreference.findViewById(R.id.layout_left),
leftBatteryPrediction);
assertBatteryPrediction(mLayoutPreference.findViewById(R.id.layout_right),
rightBatteryPrediction);
}
private void assertBatteryPredictionVisible(LinearLayout linearLayout, int visible) {
final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
assertThat(textView.getVisibility()).isEqualTo(visible);
}
private void assertBatteryPrediction(LinearLayout linearLayout, String prediction) {
final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
assertThat(textView.getText().toString()).isEqualTo(prediction);
}
private void assertBatteryLevel(LinearLayout linearLayout, int batteryLevel) {
final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
assertThat(textView.getText().toString()).isEqualTo(
com.android.settings.Utils.formatPercentage(batteryLevel));
}
private void assertBatteryIcon(LinearLayout linearLayout, int resId) {
final ImageView imageView = linearLayout.findViewById(R.id.bt_battery_icon);
assertThat(shadowOf(imageView.getDrawable()).getCreatedFromResId())
.isEqualTo(resId);
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class AlwaysDiscoverableTest {
@Mock
private Context mContext;
private AlwaysDiscoverable mAlwaysDiscoverable;
private BluetoothAdapter mBluetoothAdapter;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mAlwaysDiscoverable = new AlwaysDiscoverable(mContext);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Test
public void isStartedWithoutStart() {
assertThat(mAlwaysDiscoverable.mStarted).isFalse();
}
@Test
public void isStartedWithStart() {
mAlwaysDiscoverable.start();
assertThat(mAlwaysDiscoverable.mStarted).isTrue();
}
@Test
public void isStartedWithStartStop() {
mAlwaysDiscoverable.start();
mAlwaysDiscoverable.stop();
assertThat(mAlwaysDiscoverable.mStarted).isFalse();
}
@Test
public void stopWithoutStart() {
mAlwaysDiscoverable.stop();
// expect no crash
mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
}
@Test
public void startSetsModeAndRegistersReceiver() {
mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE);
mAlwaysDiscoverable.start();
assertThat(mBluetoothAdapter.getScanMode())
.isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
verify(mContext).registerReceiver(eq(mAlwaysDiscoverable), any(),
eq(Context.RECEIVER_EXPORTED_UNAUDITED));
}
@Test
public void stopUnregistersReceiver() {
mAlwaysDiscoverable.start();
mAlwaysDiscoverable.stop();
verify(mContext).unregisterReceiver(mAlwaysDiscoverable);
}
@Test
public void resetsToDiscoverableModeWhenScanModeChanges() {
mAlwaysDiscoverable.start();
assertThat(mBluetoothAdapter.getScanMode())
.isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
sendScanModeChangedIntent(BluetoothAdapter.SCAN_MODE_CONNECTABLE,
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
assertThat(mBluetoothAdapter.getScanMode())
.isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
private void sendScanModeChangedIntent(int newMode, int previousMode) {
mBluetoothAdapter.setScanMode(newMode);
Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, newMode);
intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_SCAN_MODE, previousMode);
mAlwaysDiscoverable.onReceive(mContext, intent);
}
}

View File

@@ -0,0 +1,377 @@
/*
* Copyright 2018 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.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.util.Pair;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAudioManager;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.google.common.collect.ImmutableSet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.Collection;
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
ShadowAudioManager.class,
ShadowBluetoothAdapter.class,
ShadowBluetoothUtils.class
})
public class AvailableMediaBluetoothDeviceUpdaterTest {
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
@Mock private DashboardFragment mDashboardFragment;
@Mock private DevicePreferenceCallback mDevicePreferenceCallback;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock private BluetoothDevice mBluetoothDevice;
@Mock private Drawable mDrawable;
@Mock private LocalBluetoothManager mLocalBtManager;
@Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
private Context mContext;
private AvailableMediaBluetoothDeviceUpdater mBluetoothDeviceUpdater;
private Collection<CachedBluetoothDevice> mCachedDevices;
private AudioManager mAudioManager;
private BluetoothDevicePreference mPreference;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private AudioSharingFeatureProvider mFeatureProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mFeatureProvider = FakeFeatureFactory.setupForTest().getAudioSharingFeatureProvider();
mAudioManager = mContext.getSystemService(AudioManager.class);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
mLocalBtManager = Utils.getLocalBtManager(mContext);
when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mCachedDevices = new ArrayList<>();
mCachedDevices.add(mCachedBluetoothDevice);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
doReturn(mContext).when(mDashboardFragment).getContext();
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(ImmutableSet.of());
mBluetoothDeviceUpdater =
spy(
new AvailableMediaBluetoothDeviceUpdater(
mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0));
mBluetoothDeviceUpdater.setPrefContext(mContext);
mPreference =
new BluetoothDevicePreference(
mContext,
mCachedBluetoothDevice,
false,
BluetoothDevicePreference.SortType.TYPE_DEFAULT);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
}
@Test
public void onAudioModeChanged_hfpDeviceConnected_inCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onAudioModeChanged();
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onAudioModeChanged_hfpDeviceConnected_notInCall_removePreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onAudioModeChanged();
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onAudioModeChanged_a2dpDeviceConnected_inCall_removePreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onAudioModeChanged();
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onAudioModeChanged_a2dpDeviceConnected_notInCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onAudioModeChanged();
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_a2dpDeviceConnected_notInCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_a2dpDeviceConnected_inCall_removePreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_hfpDeviceConnected_notInCall_removePreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_hfpDeviceConnected_inCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_ashaHearingAidConnected_notInCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.HEARING_AID);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_ashaHearingAidConnected_inCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.HEARING_AID);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void
onProfileConnectionStateChanged_leaDeviceConnected_notInCallNoSharing_addsPreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mFeatureProvider.isAudioSharingFilterMatched(
any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class)))
.thenReturn(false);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void
onProfileConnectionStateChanged_leaDeviceConnected_inCallNoSharing_addsPreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mFeatureProvider.isAudioSharingFilterMatched(
any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class)))
.thenReturn(false);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void
onProfileConnectionStateChanged_leaDeviceConnected_notInCallInSharing_removesPref() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
when(mFeatureProvider.isAudioSharingFilterMatched(
any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class)))
.thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_leaDeviceConnected_inCallInSharing_removesPref() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
when(mFeatureProvider.isAudioSharingFilterMatched(
any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class)))
.thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void
onProfileConnectionStateChanged_deviceIsNotInList_notInCall_invokesRemovePreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
mCachedDevices.clear();
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovePreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
mCachedDevices.clear();
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() {
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onClick_Preference_setActive() {
mBluetoothDeviceUpdater.onPreferenceClick(mPreference);
verify(mCachedBluetoothDevice).setActive();
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2024 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.android.settings.bluetooth.BluetoothAutoOnPreferenceController.DISABLED;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.ENABLED;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.PREF_KEY;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.SETTING_NAME;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.UNSET;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
import static com.android.settingslib.flags.Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import android.content.ContentResolver;
import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class BluetoothAutoOnPreferenceControllerTest {
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
private ContentResolver mContentResolver;
private BluetoothAutoOnPreferenceController mController;
@Before
public void setUp() {
mSetFlagsRule.enableFlags(FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE);
mContext = spy(ApplicationProvider.getApplicationContext());
mContentResolver = mContext.getContentResolver();
mController = new BluetoothAutoOnPreferenceController(mContext, PREF_KEY);
}
@Test
public void getAvailability_valueUnset_returnUnsupported() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, UNSET);
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
public void getAvailability_valueSet_returnAvailable() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, DISABLED);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void isChecked_valueEnabled_returnTrue() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, ENABLED);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
assertThat(mController.isChecked()).isEqualTo(true);
}
@Test
public void setChecked_returnTrue() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, DISABLED);
mController.setChecked(true);
assertThat(mController.isChecked()).isEqualTo(true);
}
@Test
public void setChecked_returnFalse() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, ENABLED);
mController.setChecked(false);
assertThat(mController.isChecked()).isEqualTo(false);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2022 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 android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class BluetoothBroadcastSourcePreferenceTest {
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Spy
Context mContext = ApplicationProvider.getApplicationContext();
@Mock
BluetoothLeBroadcastReceiveState mBroadcastReceiveState;
@Mock
BluetoothLeBroadcastMetadata mBroadcastMetadata;
BluetoothBroadcastSourcePreference mPreference;
@Before
public void setUp() {
mPreference = new BluetoothBroadcastSourcePreference(mContext);
}
@Test
public void isCreatedByReceiveState_updateUiFromReceviceState_returnsTrue() {
mPreference.updateReceiveStateAndRefreshUi(mBroadcastReceiveState);
assertThat(mPreference.isCreatedByReceiveState()).isTrue();
}
@Test
public void isCreatedByReceiveState_updateUiFromMetadata_returnsFalse() {
mPreference.updateMetadataAndRefreshUi(mBroadcastMetadata, true);
assertThat(mPreference.isCreatedByReceiveState()).isFalse();
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2022 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.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER;
import static android.media.audio.Flags.automaticBtDeviceType;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.media.AudioManager;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceCategory;
import com.android.settingslib.bluetooth.LeAudioProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsAudioDeviceTypeControllerTest extends
BluetoothDetailsControllerTestBase {
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
private static final String KEY_BT_AUDIO_DEVICE_TYPE = "bluetooth_audio_device_type";
@Mock
private AudioManager mAudioManager;
@Mock
private Lifecycle mAudioDeviceTypeLifecycle;
@Mock
private PreferenceCategory mProfilesContainer;
@Mock
private BluetoothDevice mBluetoothDevice;
@Mock
private LocalBluetoothManager mManager;
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Mock
private LeAudioProfile mLeAudioProfile;
private BluetoothDetailsAudioDeviceTypeController mController;
private ListPreference mAudioDeviceTypePref;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(MAC_ADDRESS);
when(mBluetoothDevice.getType()).thenReturn(DEVICE_TYPE_LE);
when(mManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
when(mLeAudioProfile.isEnabled(mCachedDevice.getDevice())).thenReturn(true);
mController = new BluetoothDetailsAudioDeviceTypeController(mContext, mFragment, mManager,
mCachedDevice, mAudioDeviceTypeLifecycle);
mController.mProfilesContainer = mProfilesContainer;
mController.createAudioDeviceTypePreference(mContext);
mAudioDeviceTypePref = mController.getAudioDeviceTypePreference();
when(mProfilesContainer.findPreference(KEY_BT_AUDIO_DEVICE_TYPE)).thenReturn(
mAudioDeviceTypePref);
}
@Test
public void createAudioDeviceTypePreference_btDeviceIsCategorized_checkSelection() {
int deviceType = AUDIO_DEVICE_CATEGORY_SPEAKER;
if (automaticBtDeviceType()) {
when(mAudioManager.getBluetoothAudioDeviceCategory(MAC_ADDRESS)).thenReturn(deviceType);
} else {
when(mAudioManager.getBluetoothAudioDeviceCategory_legacy(MAC_ADDRESS, /*isBle=*/
true)).thenReturn(deviceType);
}
mController.createAudioDeviceTypePreference(mContext);
mAudioDeviceTypePref = mController.getAudioDeviceTypePreference();
assertThat(mAudioDeviceTypePref.getValue()).isEqualTo(Integer.toString(deviceType));
}
@Test
public void selectDeviceTypeSpeaker_invokeSetBluetoothAudioDeviceType() {
int deviceType = AUDIO_DEVICE_CATEGORY_SPEAKER;
mAudioDeviceTypePref.setValue(Integer.toString(deviceType));
mController.onPreferenceChange(mAudioDeviceTypePref, Integer.toString(deviceType));
if (automaticBtDeviceType()) {
verify(mAudioManager).setBluetoothAudioDeviceCategory(eq(MAC_ADDRESS),
eq(AUDIO_DEVICE_CATEGORY_SPEAKER));
} else {
verify(mAudioManager).setBluetoothAudioDeviceCategory_legacy(eq(MAC_ADDRESS), eq(true),
eq(AUDIO_DEVICE_CATEGORY_SPEAKER));
}
}
}

View File

@@ -0,0 +1,207 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.view.View;
import android.widget.Button;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.android.settings.R;
import com.android.settingslib.widget.ActionButtonsPreference;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
@Ignore
public class BluetoothDetailsButtonsControllerTest extends BluetoothDetailsControllerTestBase {
private BluetoothDetailsButtonsController mController;
private ActionButtonsPreference mButtonsPref;
private Button mConnectButton;
private Button mForgetButton;
@Override
public void setUp() {
super.setUp();
final View buttons = View.inflate(
RuntimeEnvironment.application,
com.android.settingslib.widget.preference.actionbuttons.R.layout.settingslib_action_buttons,
null /* parent */);
mConnectButton = buttons.findViewById(com.android.settingslib.widget.preference.actionbuttons.R.id.button2);
mForgetButton = buttons.findViewById(com.android.settingslib.widget.preference.actionbuttons.R.id.button1);
mController =
new BluetoothDetailsButtonsController(mContext, mFragment, mCachedDevice,
mLifecycle);
mButtonsPref = createMock();
when(mButtonsPref.getKey()).thenReturn(mController.getPreferenceKey());
when(mButtonsPref.setButton2OnClickListener(any(View.OnClickListener.class)))
.thenAnswer(invocation -> {
final Object[] args = invocation.getArguments();
mConnectButton.setOnClickListener((View.OnClickListener) args[0]);
return mButtonsPref;
});
when(mButtonsPref.setButton1OnClickListener(any(View.OnClickListener.class)))
.thenAnswer(invocation -> {
final Object[] args = invocation.getArguments();
mForgetButton.setOnClickListener((View.OnClickListener) args[0]);
return mButtonsPref;
});
mScreen.addPreference(mButtonsPref);
setupDevice(mDeviceConfig);
when(mCachedDevice.isBusy()).thenReturn(false);
}
@Test
public void connected() {
showScreen(mController);
verify(mButtonsPref).setButton2Text(R.string.bluetooth_device_context_disconnect);
verify(mButtonsPref).setButton1Text(R.string.forget);
}
@Test
public void clickOnDisconnect() {
showScreen(mController);
mConnectButton.callOnClick();
verify(mCachedDevice).disconnect();
}
@Test
public void clickOnConnect() {
when(mCachedDevice.isConnected()).thenReturn(false);
showScreen(mController);
verify(mButtonsPref).setButton2Text(R.string.bluetooth_device_context_connect);
mConnectButton.callOnClick();
verify(mCachedDevice).connect();
}
@Test
public void becomeDisconnected() {
showScreen(mController);
// By default we start out with the device connected.
verify(mButtonsPref).setButton2Text(R.string.bluetooth_device_context_disconnect);
// Now make the device appear to have changed to disconnected.
when(mCachedDevice.isConnected()).thenReturn(false);
mController.onDeviceAttributesChanged();
verify(mButtonsPref).setButton2Text(R.string.bluetooth_device_context_connect);
// Click the button and make sure that connect (not disconnect) gets called.
mConnectButton.callOnClick();
verify(mCachedDevice).connect();
}
@Test
public void becomeConnected() {
// Start out with the device disconnected.
when(mCachedDevice.isConnected()).thenReturn(false);
showScreen(mController);
verify(mButtonsPref).setButton2Text(R.string.bluetooth_device_context_connect);
// Now make the device appear to have changed to connected.
when(mCachedDevice.isConnected()).thenReturn(true);
mController.onDeviceAttributesChanged();
verify(mButtonsPref).setButton2Text(R.string.bluetooth_device_context_disconnect);
// Click the button and make sure that disconnect (not connect) gets called.
mConnectButton.callOnClick();
verify(mCachedDevice).disconnect();
}
@Test
public void forgetDialog() {
showScreen(mController);
FragmentManager fragmentManager = mock(FragmentManager.class);
when(mFragment.getFragmentManager()).thenReturn(fragmentManager);
FragmentTransaction ft = mock(FragmentTransaction.class);
when(fragmentManager.beginTransaction()).thenReturn(ft);
mForgetButton.callOnClick();
ArgumentCaptor<ForgetDeviceDialogFragment> dialogCaptor =
ArgumentCaptor.forClass(ForgetDeviceDialogFragment.class);
verify(ft).add(dialogCaptor.capture(), anyString());
ForgetDeviceDialogFragment dialogFragment = dialogCaptor.getValue();
assertThat(dialogFragment).isNotNull();
}
@Test
public void startsOutBusy() {
when(mCachedDevice.isBusy()).thenReturn(true);
showScreen(mController);
verify(mButtonsPref).setButton2Text(R.string.bluetooth_device_context_disconnect);
verify(mButtonsPref).setButton2Enabled(false);
verify(mButtonsPref).setButton1Text(R.string.forget);
// Now pretend the device became non-busy.
when(mCachedDevice.isBusy()).thenReturn(false);
mController.onDeviceAttributesChanged();
verify(mButtonsPref).setButton2Enabled(true);
}
@Test
public void becomesBusy() {
showScreen(mController);
verify(mButtonsPref).setButton2Enabled(true);
// Now pretend the device became busy.
when(mCachedDevice.isBusy()).thenReturn(true);
mController.onDeviceAttributesChanged();
verify(mButtonsPref).setButton2Enabled(false);
}
private ActionButtonsPreference createMock() {
final ActionButtonsPreference pref = mock(ActionButtonsPreference.class);
when(pref.setButton1Text(anyInt())).thenReturn(pref);
when(pref.setButton1Icon(anyInt())).thenReturn(pref);
when(pref.setButton1Enabled(anyBoolean())).thenReturn(pref);
when(pref.setButton1Visible(anyBoolean())).thenReturn(pref);
when(pref.setButton1OnClickListener(any(View.OnClickListener.class))).thenReturn(pref);
when(pref.setButton2Text(anyInt())).thenReturn(pref);
when(pref.setButton2Icon(anyInt())).thenReturn(pref);
when(pref.setButton2Enabled(anyBoolean())).thenReturn(pref);
when(pref.setButton2Visible(anyBoolean())).thenReturn(pref);
when(pref.setButton2OnClickListener(any(View.OnClickListener.class))).thenReturn(pref);
return pref;
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (C) 2021 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.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.MacAddress;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Ignore("b/191992001")
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsCompanionAppsControllerTest extends
BluetoothDetailsControllerTestBase {
private static final String PACKAGE_NAME_ONE = "com.google.android.deskclock";
private static final String PACKAGE_NAME_TWO = "com.google.android.calculator";
private static final String PACKAGE_NAME_THREE = "com.google.android.GoogleCamera";
private static final CharSequence APP_NAME_ONE = "deskclock";
private static final CharSequence APP_NAME_TWO = "calculator";
private static final CharSequence APP_NAME_THREE = "GoogleCamera";
@Mock
private CompanionDeviceManager mCompanionDeviceManager;
@Mock
private PackageManager mPackageManager;
private BluetoothDetailsCompanionAppsController mController;
private PreferenceCategory mProfiles;
private List<String> mPackages;
private List<CharSequence> mAppNames;
private List<AssociationInfo> mAssociations;
@Override
public void setUp() {
super.setUp();
mPackages = Arrays.asList(PACKAGE_NAME_ONE, PACKAGE_NAME_TWO, PACKAGE_NAME_THREE);
mAppNames = Arrays.asList(APP_NAME_ONE, APP_NAME_TWO, APP_NAME_THREE);
mProfiles = spy(new PreferenceCategory(mContext));
mAssociations = new ArrayList<>();
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(mAssociations);
when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager);
setupDevice(mDeviceConfig);
mController =
new BluetoothDetailsCompanionAppsController(mContext, mFragment, mCachedDevice,
mLifecycle);
mController.mCompanionDeviceManager = mCompanionDeviceManager;
mController.mPackageManager = mPackageManager;
mController.mProfilesContainer = mProfiles;
mProfiles.setKey(mController.getPreferenceKey());
mScreen.addPreference(mProfiles);
}
private void setupFakeLabelAndInfo(String packageName, CharSequence appName) {
ApplicationInfo appInfo = mock(ApplicationInfo.class);
try {
when(mPackageManager.getApplicationInfo(packageName, 0)).thenReturn(appInfo);
when(mPackageManager.getApplicationLabel(appInfo)).thenReturn(appName);
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
private void addFakeAssociation(String packageName, CharSequence appName) {
setupFakeLabelAndInfo(packageName, appName);
final int associationId = mAssociations.size() + 1;
final AssociationInfo association = new AssociationInfo(
associationId,
/* userId */ 0,
packageName,
/* tag */ null,
MacAddress.fromString(mCachedDevice.getAddress()),
/* displayName */ null,
/* deviceProfile */ "",
/* associatedDevice */ null,
/* selfManaged */ false,
/* notifyOnDeviceNearby */ true,
/* revoked */ false,
/* pending */ false,
/* timeApprovedMs */ System.currentTimeMillis(),
/* lastTimeConnected */ Long.MAX_VALUE,
/* systemDataSyncFlags */ -1);
mAssociations.add(association);
showScreen(mController);
}
private Preference getPreference(int index) {
PreferenceCategory preferenceCategory = mProfiles.findPreference(
mController.getPreferenceKey());
return Objects.requireNonNull(preferenceCategory).getPreference(index);
}
private void removeAssociation(String packageName) {
mAssociations = mAssociations.stream()
.filter(a -> !a.getPackageName().equals(packageName))
.collect(Collectors.toList());
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(mAssociations);
showScreen(mController);
}
@Test
public void addOneAssociation_preferenceShouldBeAdded() {
addFakeAssociation(PACKAGE_NAME_ONE, APP_NAME_ONE);
Preference preferenceOne = getPreference(0);
assertThat(preferenceOne.getClass()).isEqualTo(CompanionAppWidgetPreference.class);
assertThat(preferenceOne.getKey()).isEqualTo(PACKAGE_NAME_ONE);
assertThat(preferenceOne.getTitle()).isEqualTo(APP_NAME_ONE.toString());
assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
removeAssociation(PACKAGE_NAME_ONE);
assertThat(mProfiles.getPreferenceCount()).isEqualTo(0);
}
@Test
public void removeOneAssociation_preferenceShouldBeRemoved() {
addFakeAssociation(PACKAGE_NAME_ONE, APP_NAME_ONE);
removeAssociation(PACKAGE_NAME_ONE);
assertThat(mProfiles.getPreferenceCount()).isEqualTo(0);
}
@Test
public void addMultipleAssociations_preferencesShouldBeAdded() {
for (int i = 0; i < mPackages.size(); i++) {
addFakeAssociation(mPackages.get(i), mAppNames.get(i));
Preference preference = getPreference(i);
assertThat(preference.getClass()).isEqualTo(CompanionAppWidgetPreference.class);
assertThat(preference.getKey()).isEqualTo(mPackages.get(i));
assertThat(preference.getTitle()).isEqualTo(mAppNames.get(i).toString());
assertThat(mProfiles.getPreferenceCount()).isEqualTo(i + 1);
}
}
@Test
public void removeMultipleAssociations_preferencesShouldBeRemoved() {
for (int i = 0; i < mPackages.size(); i++) {
addFakeAssociation(mPackages.get(i), mAppNames.get(i).toString());
}
for (int i = 0; i < mPackages.size(); i++) {
removeAssociation(mPackages.get(i));
assertThat(mProfiles.getPreferenceCount()).isEqualTo(mPackages.size() - i - 1);
if (i == mPackages.size() - 1) {
break;
}
assertThat(getPreference(0).getKey()).isEqualTo(mPackages.get(i + 1));
assertThat(getPreference(0).getTitle()).isEqualTo(mAppNames.get(i + 1).toString());
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
@Ignore
public class BluetoothDetailsControllerEventsTest extends BluetoothDetailsControllerTestBase {
@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);
}
private static class TestController extends BluetoothDetailsController {
private TestController(Context context, PreferenceFragmentCompat 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() {}
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
com.android.settings.testutils.shadow.ShadowFragment.class,
com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal.class,
})
public abstract class BluetoothDetailsControllerTestBase {
protected Context mContext;
private LifecycleOwner mLifecycleOwner;
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 FragmentActivity mActivity;
@Mock
protected BluetoothClass mBluetoothDeviceClass;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
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);
mLifecycleOwner = () -> mLifecycle;
mLifecycle = spy(new Lifecycle(mLifecycleOwner));
mBluetoothManager = mContext.getSystemService(BluetoothManager.class);
mBluetoothAdapter = mBluetoothManager.getAdapter();
}
protected static class DeviceConfig {
private String name;
private String address;
private int majorDeviceClass;
private boolean connected;
private String connectionSummary;
DeviceConfig setName(String newValue) {
this.name = newValue;
return this;
}
DeviceConfig setAddress(String newValue) {
this.address = newValue;
return this;
}
DeviceConfig setMajorDeviceClass(int newValue) {
this.majorDeviceClass = newValue;
return this;
}
DeviceConfig setConnected(boolean newValue) {
this.connected = newValue;
return this;
}
DeviceConfig setConnectionSummary(String connectionSummary) {
this.connectionSummary = connectionSummary;
return this;
}
String getName() {
return name;
}
String getAddress() {
return address;
}
int getMajorDeviceClass() {
return majorDeviceClass;
}
boolean isConnected() {
return connected;
}
String getConnectionSummary() {
return connectionSummary;
}
}
DeviceConfig makeDefaultDeviceConfig() {
return new DeviceConfig()
.setName("Mock Device")
.setAddress("B4:B0:34:B5:3B:1B")
.setMajorDeviceClass(BluetoothClass.Device.Major.AUDIO_VIDEO)
.setConnected(true)
.setConnectionSummary(
mContext.getString(com.android.settingslib.R.string.bluetooth_connected));
}
/**
* Sets up the device mock to return various state based on a test config.
*/
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(config.getAddress());
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getAddress()).thenReturn(config.getAddress());
when(mCachedDevice.getIdentityAddress()).thenReturn(config.getAddress());
}
/**
* Convenience method to call displayPreference and onResume.
*/
void showScreen(BluetoothDetailsController controller) {
controller.displayPreference(mScreen);
controller.onResume();
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.Mockito.spy;
import static org.mockito.Mockito.when;
import android.companion.CompanionDeviceManager;
import android.companion.datatransfer.PermissionSyncRequest;
import androidx.preference.PreferenceCategory;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.Collections;
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsDataSyncControllerTest extends BluetoothDetailsControllerTestBase {
private static final String MAC_ADDRESS = "AA:BB:CC:DD:EE:FF";
private static final int DUMMY_ASSOCIATION_ID = -1;
private static final int ASSOCIATION_ID = 1;
private static final String KEY_PERM_SYNC = "perm_sync";
private BluetoothDetailsDataSyncController mController;
@Mock
private Lifecycle mLifecycle;
@Mock
private PreferenceCategory mPreferenceCategory;
@Mock
private CompanionDeviceManager mCompanionDeviceManager;
private PermissionSyncRequest mPermissionSyncRequest;
private TwoStatePreference mPermSyncPreference;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getSystemService(CompanionDeviceManager.class)).thenReturn(
mCompanionDeviceManager);
when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(Collections.emptyList());
mPermissionSyncRequest = new PermissionSyncRequest(ASSOCIATION_ID);
when(mCompanionDeviceManager.getPermissionSyncRequest(ASSOCIATION_ID)).thenReturn(
mPermissionSyncRequest);
mController = new BluetoothDetailsDataSyncController(mContext, mFragment,
mCachedDevice, mLifecycle);
mController.mAssociationId = ASSOCIATION_ID;
mController.mPreferenceCategory = mPreferenceCategory;
mPermSyncPreference = mController.createPermSyncPreference(mContext);
when(mPreferenceCategory.findPreference(KEY_PERM_SYNC)).thenReturn(mPermSyncPreference);
}
@Test
public void isAvailable_noAssociations_returnsFalse() {
mController.mAssociationId = DUMMY_ASSOCIATION_ID;
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_hasAssociations_returnTrue() {
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void refresh_noAssociations_checkPreferenceInvisible() {
mController.mAssociationId = DUMMY_ASSOCIATION_ID;
mController.refresh();
assertThat(mPermSyncPreference.isVisible()).isFalse();
}
@Test
public void refresh_permSyncNull_checkPreferenceInvisible() {
mPermissionSyncRequest = null;
when(mCompanionDeviceManager.getPermissionSyncRequest(ASSOCIATION_ID)).thenReturn(
mPermissionSyncRequest);
mController.refresh();
assertThat(mPermSyncPreference.isVisible()).isFalse();
}
@Test
public void refresh_permSyncEnabled_checkPreferenceOn() {
mPermissionSyncRequest.setUserConsented(true);
mController.refresh();
assertThat(mPermSyncPreference.isVisible()).isTrue();
assertThat(mPermSyncPreference.isChecked()).isTrue();
}
@Test
public void refresh_permSyncDisabled_checkPreferenceOff() {
mPermissionSyncRequest.setUserConsented(false);
mController.refresh();
assertThat(mPermSyncPreference.isVisible()).isTrue();
assertThat(mPermSyncPreference.isChecked()).isFalse();
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2024 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.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowLooper;
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsExtraOptionsControllerTest extends BluetoothDetailsControllerTestBase {
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
@Mock private BluetoothDevice mBluetoothDevice;
@Mock private Lifecycle mExtraOptionsLifecycle;
@Mock private PreferenceCategory mOptionsContainer;
@Mock private PreferenceScreen mPreferenceScreen;
private BluetoothDetailsExtraOptionsController mController;
private BluetoothFeatureProvider mFeatureProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(MAC_ADDRESS);
final FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest();
mFeatureProvider = fakeFeatureFactory.getBluetoothFeatureProvider();
mController =
new BluetoothDetailsExtraOptionsController(
mContext, mFragment, mCachedDevice, mExtraOptionsLifecycle);
}
@Test
public void displayPreference_removeAndAddPreferences() {
Preference preference1 = new SwitchPreferenceCompat(mContext);
Preference preference2 = new SwitchPreferenceCompat(mContext);
when(mFeatureProvider.getBluetoothExtraOptions(mContext, mCachedDevice))
.thenReturn(ImmutableList.of(preference1, preference2));
when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
.thenReturn(mOptionsContainer);
mController.displayPreference(mPreferenceScreen);
ShadowLooper.idleMainLooper();
verify(mOptionsContainer).removeAll();
verify(mOptionsContainer).addPreference(preference1);
verify(mOptionsContainer).addPreference(preference2);
}
}

View File

@@ -0,0 +1,150 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.graphics.drawable.Drawable;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.widget.LayoutPreference;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Ignore
@Config(shadows = {ShadowEntityHeaderController.class, ShadowDeviceConfig.class})
public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsControllerTestBase {
private BluetoothDetailsHeaderController mController;
private LayoutPreference mPreference;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private EntityHeaderController mHeaderController;
@Mock
private LocalBluetoothManager mBluetoothManager;
@Mock
private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock
private BluetoothDevice mBluetoothDevice;
@Override
public void setUp() {
super.setUp();
FakeFeatureFactory.setupForTest();
ShadowEntityHeaderController.setUseMock(mHeaderController);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.getSubDeviceSummary(mCachedDevice)).thenReturn("abc");
mController =
new BluetoothDetailsHeaderController(mContext, mFragment, mCachedDevice, mLifecycle);
mPreference = new LayoutPreference(
mContext, com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header);
mPreference.setKey(mController.getPreferenceKey());
mScreen.addPreference(mPreference);
setupDevice(mDeviceConfig);
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
}
@After
public void tearDown() {
ShadowEntityHeaderController.reset();
}
/**
* Test to verify the current test context object works so that we are not checking null
* against null
*/
@Test
public void testContextMock() {
assertThat(mContext.getString(com.android.settingslib.R.string.bluetooth_connected))
.isNotNull();
}
@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).setSecondSummary(any(String.class));
verify(mHeaderController).done(true);
}
@Test
public void connectionStatusChangesWhileScreenOpen() {
InOrder inOrder = inOrder(mHeaderController);
when(mCachedDevice.getConnectionSummary())
.thenReturn(mContext.getString(com.android.settingslib.R.string.bluetooth_connected));
showScreen(mController);
inOrder.verify(mHeaderController)
.setSummary(mContext.getString(com.android.settingslib.R.string.bluetooth_connected));
when(mCachedDevice.getConnectionSummary()).thenReturn(null);
mController.onDeviceAttributesChanged();
inOrder.verify(mHeaderController).setSummary((CharSequence) null);
when(mCachedDevice.getConnectionSummary())
.thenReturn(mContext.getString(com.android.settingslib.R.string.bluetooth_connecting));
mController.onDeviceAttributesChanged();
inOrder.verify(mHeaderController)
.setSummary(mContext.getString(com.android.settingslib.R.string.bluetooth_connecting));
}
@Test
public void isAvailable_untetheredHeadsetWithConfigOn_returnFalse() {
android.provider.DeviceConfig.setProperty(
android.provider.DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("true".getBytes());
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_untetheredHeadsetWithConfigOff_returnTrue() {
android.provider.DeviceConfig.setProperty(
android.provider.DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "false", true);
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("true".getBytes());
assertThat(mController.isAvailable()).isTrue();
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2022 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.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import androidx.preference.Preference;
import com.android.settings.SettingsActivity;
import com.android.settings.accessibility.AccessibilityHearingAidsFragment;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link BluetoothDetailsHearingDeviceControlsController}. */
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsHearingDeviceControlsControllerTest extends
BluetoothDetailsControllerTestBase {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@Captor
private ArgumentCaptor<Intent> mIntentArgumentCaptor;
private BluetoothDetailsHearingDeviceControlsController mController;
@Override
public void setUp() {
super.setUp();
FakeFeatureFactory.setupForTest();
mController = new BluetoothDetailsHearingDeviceControlsController(mActivity, mFragment,
mCachedDevice, mLifecycle);
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
}
@Test
public void isAvailable_isHearingAidDevice_available() {
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void isAvailable_isNotHearingAidDevice_notAvailable() {
when(mCachedDevice.isHearingAidDevice()).thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void onPreferenceClick_hearingDeviceControlsKey_LaunchExpectedFragment() {
final Preference hearingControlsKeyPreference = new Preference(mContext);
hearingControlsKeyPreference.setKey(
BluetoothDetailsHearingDeviceControlsController.KEY_HEARING_DEVICE_CONTROLS);
mController.onPreferenceClick(hearingControlsKeyPreference);
assertStartActivityWithExpectedFragment(mActivity,
AccessibilityHearingAidsFragment.class.getName());
}
private void assertStartActivityWithExpectedFragment(Context mockContext, String fragmentName) {
verify(mockContext).startActivity(mIntentArgumentCaptor.capture());
assertThat(mIntentArgumentCaptor.getValue()
.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(fragmentName);
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.android.settings.bluetooth.BluetoothDetailsMacAddressController.KEY_DEVICE_DETAILS_FOOTER;
import static com.google.common.truth.Truth.assertThat;
import com.android.settingslib.widget.FooterPreference;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
@Ignore
public class BluetoothDetailsMacAddressControllerTest extends BluetoothDetailsControllerTestBase {
private BluetoothDetailsMacAddressController mController;
@Override
public void setUp() {
super.setUp();
mController =
new BluetoothDetailsMacAddressController(mContext, mFragment, mCachedDevice,
mLifecycle);
setupDevice(mDeviceConfig);
mScreen.addPreference(new FooterPreference.Builder(mContext).setKey(
KEY_DEVICE_DETAILS_FOOTER).setTitle(KEY_DEVICE_DETAILS_FOOTER).build());
}
@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,250 @@
/*
* Copyright (C) 2022 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.Mockito.when;
import android.bluetooth.BluetoothDevice;
import com.android.settings.R;
import com.android.settings.applications.SpacePreference;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidInfo;
import com.android.settingslib.widget.ButtonPreference;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import java.util.HashSet;
import java.util.Set;
/** Tests for {@link BluetoothDetailsPairOtherController}. */
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsPairOtherControllerTest extends BluetoothDetailsControllerTestBase {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@Mock
private CachedBluetoothDevice mSubCachedDevice;
private BluetoothDetailsPairOtherController mController;
private ButtonPreference mPreference;
private SpacePreference mSpacePreference;
@Override
public void setUp() {
super.setUp();
mController = new BluetoothDetailsPairOtherController(mContext, mFragment, mCachedDevice,
mLifecycle);
mPreference = new ButtonPreference(mContext);
mSpacePreference = new SpacePreference(mContext, null);
mPreference.setKey(mController.getPreferenceKey());
mSpacePreference.setKey(BluetoothDetailsPairOtherController.KEY_SPACE);
mScreen.addPreference(mPreference);
mScreen.addPreference(mSpacePreference);
}
/** Test the pair other side button title during initialization. */
@Test
public void init_deviceIsLeftSide_showPairRightSideTitle() {
when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidInfo.DeviceSide.SIDE_LEFT);
mController.init(mScreen);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.bluetooth_pair_right_ear_button));
}
/** Test the pair other side button title during initialization. */
@Test
public void init_deviceIsRightSide_showPairLeftSideTitle() {
when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidInfo.DeviceSide.SIDE_RIGHT);
mController.init(mScreen);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.bluetooth_pair_left_ear_button));
}
/** Test the pair other side button visibility during initialization. */
@Test
public void init_deviceIsNotConnectedHearingAid_preferenceIsNotVisible() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(false);
mController.init(mScreen);
assertThat(mPreference.isVisible()).isFalse();
assertThat(mSpacePreference.isVisible()).isFalse();
}
/**
* Test if the controller is available.
* Conditions:
* 1. The device is not a connected hearing aid
* Expected result:
* The controller is not available. No need to show pair other side hint for
* non-hearing aid device or not connected device.
*/
@Test
public void isAvailable_deviceIsNotConnectedHearingAid_notAvailable() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
}
/**
* Test if the controller is available.
* Conditions:
* 1. Monaural hearing aid
* Expected result:
* The controller is not available. No need to show pair other side hint for
* monaural device.
*/
@Test
public void isAvailable_deviceIsConnectedHearingAid_isMonaural_notAvailable() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_MONAURAL);
assertThat(mController.isAvailable()).isFalse();
}
/**
* Test if the controller is available.
* Conditions:
* 1. Binaural hearing aids
* 2. Sub device is added
* 3. Sub device is bonded
* Expected result:
* The controller is not available. Both sides are already paired.
*/
@Test
public void isAvailable_deviceIsConnectedHearingAid_subDeviceIsBonded_notAvailable() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mSubCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mCachedDevice.getSubDevice()).thenReturn(mSubCachedDevice);
assertThat(mController.isAvailable()).isFalse();
}
/**
* Test if the controller is available.
* Conditions:
* 1. Binaural hearing aids
* 2. Sub device is added
* 3. Sub device is not bonded
* Expected result:
* The controller is available. Need to show the hint to pair the other side.
*/
@Test
public void isAvailable_deviceIsConnectedHearingAid_subDeviceIsNotBonded_available() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mSubCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
when(mCachedDevice.getSubDevice()).thenReturn(mSubCachedDevice);
assertThat(mController.isAvailable()).isTrue();
}
/**
* Test if the controller is available.
* Conditions:
* 1. Binaural hearing aids
* 2. Member device is added
* 3. Member device is bonded
* Expected result:
* The controller is not available. Both sides are already paired.
*/
@Test
public void isAvailable_deviceIsConnectedHearingAid_memberDeviceIsBonded_notAvailable() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mSubCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mSubCachedDevice));
assertThat(mController.isAvailable()).isFalse();
}
/**
* Test if the controller is available.
* Conditions:
* 1. Binaural hearing aids
* 2. Member device is added
* 3. Member device is not bonded
* Expected result:
* The controller is available. Need to show the hint to pair the other side.
*/
@Test
public void isAvailable_deviceIsConnectedHearingAid_memberDeviceIsNotBonded_available() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mSubCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mSubCachedDevice));
assertThat(mController.isAvailable()).isTrue();
}
/**
* Test if the controller is available.
* Conditions:
* 1. Binaural hearing aids
* 2. No sub device is added
* 2. No member device is added
* Expected result:
* The controller is available. Need to show the hint to pair the other side.
*/
@Test
public void isAvailable_deviceIsConnectedHearingAid_otherDeviceIsNotExist_available() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mCachedDevice.getSubDevice()).thenReturn(null);
when(mCachedDevice.getMemberDevice()).thenReturn(new HashSet<>());
assertThat(mController.isAvailable()).isTrue();
}
/** Test the pair other side button title after refreshing. */
@Test
public void refresh_deviceIsRightSide_showPairLeftSideTitle() {
when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidInfo.DeviceSide.SIDE_RIGHT);
mController.init(mScreen);
mController.refresh();
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.bluetooth_pair_left_ear_button));
}
/** Test the pair other side button visibility after refreshing. */
@Test
public void refresh_deviceIsNotConnectedHearingAid_preferenceIsNotVisible() {
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(false);
mController.init(mScreen);
mController.refresh();
assertThat(mPreference.isVisible()).isFalse();
assertThat(mSpacePreference.isVisible()).isFalse();
}
}

View File

@@ -0,0 +1,561 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.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.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.sysprop.BluetoothProperties;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreferenceCompat;
import com.android.settings.flags.Flags;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothDevice;
import com.android.settingslib.R;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LeAudioProfile;
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 com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowBluetoothDevice.class)
public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsControllerTestBase {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String LE_DEVICE_MODEL = "le_audio_headset";
private static final String NON_LE_DEVICE_MODEL = "non_le_audio_headset";
private BluetoothDetailsProfilesController mController;
private List<LocalBluetoothProfile> mConnectableProfiles;
private PreferenceCategory mProfiles;
private BluetoothFeatureProvider mFeatureProvider;
@Mock
private LocalBluetoothManager mLocalManager;
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Mock
private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager;
@Override
public void setUp() {
super.setUp();
FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest();
mFeatureProvider = fakeFeatureFactory.getBluetoothFeatureProvider();
mProfiles = spy(new PreferenceCategory(mContext));
when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager);
mConnectableProfiles = new ArrayList<>();
when(mLocalManager.getProfileManager()).thenReturn(mProfileManager);
when(mLocalManager.getCachedDeviceManager()).thenReturn(mCachedBluetoothDeviceManager);
when(mCachedBluetoothDeviceManager.getCachedDevicesCopy())
.thenReturn(ImmutableList.of(mCachedDevice));
when(mCachedDevice.getConnectableProfiles()).thenAnswer(invocation ->
new ArrayList<>(mConnectableProfiles)
);
setupDevice(mDeviceConfig);
mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
mCachedDevice, mLifecycle);
mProfiles.setKey(mController.getPreferenceKey());
mController.mProfilesContainer = mProfiles;
mScreen.addPreference(mProfiles);
BluetoothProperties.le_audio_allow_list(Lists.newArrayList(LE_DEVICE_MODEL));
}
static class FakeBluetoothProfile implements LocalBluetoothProfile {
private Set<BluetoothDevice> mConnectedDevices = new HashSet<>();
private Map<BluetoothDevice, Boolean> mPreferred = new HashMap<>();
private Context mContext;
private int mNameResourceId;
private FakeBluetoothProfile(Context context, int nameResourceId) {
mContext = context;
mNameResourceId = nameResourceId;
}
@Override
public String toString() {
return mContext.getString(mNameResourceId);
}
@Override
public boolean accessProfileEnabled() {
return true;
}
@Override
public boolean isAutoConnectable() {
return true;
}
@Override
public int getConnectionStatus(BluetoothDevice device) {
if (mConnectedDevices.contains(device)) {
return BluetoothProfile.STATE_CONNECTED;
} else {
return BluetoothProfile.STATE_DISCONNECTED;
}
}
@Override
public boolean isEnabled(BluetoothDevice device) {
return mPreferred.getOrDefault(device, false);
}
@Override
public int getConnectionPolicy(BluetoothDevice device) {
return isEnabled(device)
? BluetoothProfile.CONNECTION_POLICY_ALLOWED
: BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
mPreferred.put(device, enabled);
return true;
}
@Override
public boolean isProfileReady() {
return true;
}
@Override
public int getProfileId() {
return 0;
}
@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.setEnabled(mDevice, deviceIsPreferred);
mConnectableProfiles.add(profile);
when(mProfileManager.getProfileByName(eq(profile.toString()))).thenReturn(profile);
return profile;
}
/**
* Returns the list of SwitchPreferenceCompat objects added to the screen - there should be one
* per Bluetooth profile.
*/
private List<SwitchPreferenceCompat> getProfileSwitches(boolean expectOnlyMConnectable) {
if (expectOnlyMConnectable) {
assertThat(mConnectableProfiles).isNotEmpty();
assertThat(mProfiles.getPreferenceCount() - 1).isEqualTo(mConnectableProfiles.size());
}
List<SwitchPreferenceCompat> result = new ArrayList<>();
for (int i = 0; i < mProfiles.getPreferenceCount(); i++) {
final Preference preference = mProfiles.getPreference(i);
if (preference instanceof SwitchPreferenceCompat) {
result.add((SwitchPreferenceCompat) preference);
}
}
return result;
}
private void verifyProfileSwitchTitles(List<SwitchPreferenceCompat> 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(com.android.settingslib.R.string.bluetooth_profile_a2dp, true);
showScreen(mController);
verifyProfileSwitchTitles(getProfileSwitches(true));
}
@Test
public void multipleProfiles() {
addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_a2dp, true);
addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_headset, false);
showScreen(mController);
List<SwitchPreferenceCompat> 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(3);
// Make sure both switches got disabled.
assertThat(switches.get(0).isEnabled()).isFalse();
assertThat(switches.get(1).isEnabled()).isFalse();
}
@Test
public void disableThenReenableOneProfile() {
addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_a2dp, true);
addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_headset, true);
showScreen(mController);
List<SwitchPreferenceCompat> switches = getProfileSwitches(true);
SwitchPreferenceCompat 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).isEnabled(mDevice)).isFalse();
// Make sure no new preferences were added.
assertThat(mProfiles.getPreferenceCount()).isEqualTo(3);
// Clicking the pref again should make the profile once again preferred.
pref.performClick();
assertThat(pref.isChecked()).isTrue();
assertThat(mConnectableProfiles.get(0).isEnabled(mDevice)).isTrue();
// Make sure we still haven't gotten any new preferences added.
assertThat(mProfiles.getPreferenceCount()).isEqualTo(3);
}
@Test
public void disconnectedDeviceOneProfile() {
setupDevice(makeDefaultDeviceConfig().setConnected(false).setConnectionSummary(null));
addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_a2dp, true);
showScreen(mController);
verifyProfileSwitchTitles(getProfileSwitches(true));
}
@Test
public void pbapProfileStartsEnabled() {
setupDevice(makeDefaultDeviceConfig());
mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
PbapServerProfile psp = mock(PbapServerProfile.class);
when(psp.getNameResource(mDevice))
.thenReturn(com.android.settingslib.R.string.bluetooth_profile_pbap);
when(psp.getSummaryResourceForDevice(mDevice))
.thenReturn(R.string.bluetooth_profile_pbap_summary);
when(psp.toString()).thenReturn(PbapServerProfile.NAME);
when(psp.isProfileReady()).thenReturn(true);
when(mProfileManager.getPbapProfile()).thenReturn(psp);
showScreen(mController);
List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
assertThat(switches.size()).isEqualTo(1);
SwitchPreferenceCompat pref = switches.get(0);
assertThat(pref.getTitle()).isEqualTo(
mContext.getString(com.android.settingslib.R.string.bluetooth_profile_pbap));
assertThat(pref.isChecked()).isTrue();
pref.performClick();
assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
assertThat(mDevice.getPhonebookAccessPermission())
.isEqualTo(BluetoothDevice.ACCESS_REJECTED);
}
@Test
public void pbapProfileStartsDisabled() {
setupDevice(makeDefaultDeviceConfig());
mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
PbapServerProfile psp = mock(PbapServerProfile.class);
when(psp.getNameResource(mDevice))
.thenReturn(com.android.settingslib.R.string.bluetooth_profile_pbap);
when(psp.getSummaryResourceForDevice(mDevice))
.thenReturn(R.string.bluetooth_profile_pbap_summary);
when(psp.toString()).thenReturn(PbapServerProfile.NAME);
when(psp.isProfileReady()).thenReturn(true);
when(mProfileManager.getPbapProfile()).thenReturn(psp);
showScreen(mController);
List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
assertThat(switches.size()).isEqualTo(1);
SwitchPreferenceCompat pref = switches.get(0);
assertThat(pref.getTitle()).isEqualTo(
mContext.getString(com.android.settingslib.R.string.bluetooth_profile_pbap));
assertThat(pref.isChecked()).isFalse();
pref.performClick();
assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
assertThat(mDevice.getPhonebookAccessPermission())
.isEqualTo(BluetoothDevice.ACCESS_ALLOWED);
}
@Test
public void mapProfile() {
setupDevice(makeDefaultDeviceConfig());
MapProfile mapProfile = mock(MapProfile.class);
when(mapProfile.getNameResource(mDevice))
.thenReturn(com.android.settingslib.R.string.bluetooth_profile_map);
when(mapProfile.isProfileReady()).thenReturn(true);
when(mProfileManager.getMapProfile()).thenReturn(mapProfile);
when(mProfileManager.getProfileByName(eq(mapProfile.toString()))).thenReturn(mapProfile);
mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
showScreen(mController);
List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
assertThat(switches.size()).isEqualTo(1);
SwitchPreferenceCompat pref = switches.get(0);
assertThat(pref.getTitle()).isEqualTo(
mContext.getString(com.android.settingslib.R.string.bluetooth_profile_map));
assertThat(pref.isChecked()).isFalse();
pref.performClick();
assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
assertThat(mDevice.getMessageAccessPermission()).isEqualTo(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(com.android.settingslib.R.string.bluetooth_profile_a2dp);
when(profile.getHighQualityAudioOptionLabel(mDevice)).thenReturn(
mContext.getString(com.android.settingslib.R
.string.bluetooth_profile_a2dp_high_quality_unknown_codec));
when(profile.supportsHighQualityAudio(mDevice)).thenReturn(supportsHighQualityAudio);
when(profile.isHighQualityAudioEnabled(mDevice)).thenReturn(highQualityAudioEnabled);
when(profile.isEnabled(mDevice)).thenReturn(preferred);
when(profile.isProfileReady()).thenReturn(true);
mConnectableProfiles.add(profile);
return profile;
}
private SwitchPreferenceCompat getHighQualityAudioPref() {
return (SwitchPreferenceCompat) mScreen.findPreference(
BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
}
@Test
public void highQualityAudio_prefIsPresentWhenSupported() {
setupDevice(makeDefaultDeviceConfig());
addMockA2dpProfile(true, true, true);
showScreen(mController);
SwitchPreferenceCompat 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(2);
SwitchPreferenceCompat pref = (SwitchPreferenceCompat) mProfiles.getPreference(0);
assertThat(pref.getKey())
.isNotEqualTo(BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
assertThat(pref.getTitle()).isEqualTo(
mContext.getString(com.android.settingslib.R.string.bluetooth_profile_a2dp));
}
@Test
public void highQualityAudio_busyDeviceDisablesSwitch() {
setupDevice(makeDefaultDeviceConfig());
addMockA2dpProfile(true, true, true);
when(mCachedDevice.isBusy()).thenReturn(true);
showScreen(mController);
SwitchPreferenceCompat 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(3);
// Disabling media audio should cause the high quality audio switch to disappear, but not
// the regular audio one.
SwitchPreferenceCompat audioPref =
(SwitchPreferenceCompat) mScreen.findPreference(audioProfile.toString());
audioPref.performClick();
verify(audioProfile).setEnabled(mDevice, false);
when(audioProfile.isEnabled(mDevice)).thenReturn(false);
mController.onDeviceAttributesChanged();
assertThat(audioPref.isVisible()).isTrue();
SwitchPreferenceCompat highQualityAudioPref = getHighQualityAudioPref();
assertThat(highQualityAudioPref.isVisible()).isFalse();
// And re-enabling media audio should make high quality switch to reappear.
audioPref.performClick();
verify(audioProfile).setEnabled(mDevice, true);
when(audioProfile.isEnabled(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);
SwitchPreferenceCompat audioPref = mScreen.findPreference(audioProfile.toString());
SwitchPreferenceCompat highQualityAudioPref = getHighQualityAudioPref();
assertThat(audioPref).isNotNull();
assertThat(audioPref.isChecked()).isFalse();
assertThat(highQualityAudioPref).isNotNull();
assertThat(highQualityAudioPref.isVisible()).isFalse();
}
@Test
public void onResume_addServiceListener() {
mController.onResume();
verify(mProfileManager).addServiceListener(mController);
}
@Test
public void onPause_removeServiceListener() {
mController.onPause();
verify(mProfileManager).removeServiceListener(mController);
}
@Test
public void isDeviceInAllowList_returnTrue() {
assertThat(mController.isModelNameInAllowList(LE_DEVICE_MODEL)).isTrue();
}
@Test
public void isDeviceInAllowList_returnFalse() {
assertThat(mController.isModelNameInAllowList(null)).isFalse();
assertThat(mController.isModelNameInAllowList(NON_LE_DEVICE_MODEL)).isFalse();
}
@Test
public void prefKeyInBlockingList_hideToggle() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_BLUETOOTH_PROFILE_TOGGLE_VISIBILITY_CHECKER);
setupDevice(makeDefaultDeviceConfig());
LeAudioProfile leAudioProfile = mock(LeAudioProfile.class);
when(leAudioProfile.getNameResource(mDevice))
.thenReturn(com.android.settingslib.R.string.bluetooth_profile_le_audio);
when(leAudioProfile.isProfileReady()).thenReturn(true);
when(leAudioProfile.toString()).thenReturn("LE_AUDIO");
when(mProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile);
when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any()))
.thenReturn(ImmutableSet.of("LE_AUDIO"));
mConnectableProfiles.add(leAudioProfile);
showScreen(mController);
List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
assertThat(switches.get(0).isVisible()).isFalse();
}
@Test
public void prefKeyNotInBlockingList_showToggle() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_BLUETOOTH_PROFILE_TOGGLE_VISIBILITY_CHECKER);
setupDevice(makeDefaultDeviceConfig());
LeAudioProfile leAudioProfile = mock(LeAudioProfile.class);
when(leAudioProfile.getNameResource(mDevice))
.thenReturn(com.android.settingslib.R.string.bluetooth_profile_le_audio);
when(leAudioProfile.isProfileReady()).thenReturn(true);
when(leAudioProfile.toString()).thenReturn("LE_AUDIO");
when(mProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile);
when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any()))
.thenReturn(ImmutableSet.of("A2DP"));
mConnectableProfiles.add(leAudioProfile);
showScreen(mController);
List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
assertThat(switches.get(0).isVisible()).isTrue();
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2022 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.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.view.accessibility.AccessibilityManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowAccessibilityManager;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.List;
/** Tests for {@link BluetoothDetailsRelatedToolsController}. */
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsRelatedToolsControllerTest extends BluetoothDetailsControllerTestBase {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
private static final String PACKAGE_NAME = "com.android.test";
private static final String PACKAGE_NAME2 = "com.android.test2";
private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service";
private static final String KEY_RELATED_TOOLS_GROUP = "bluetooth_related_tools";
private static final String KEY_LIVE_CAPTION = "live_caption";
private BluetoothDetailsRelatedToolsController mController;
private BluetoothFeatureProvider mFeatureProvider;
private ShadowAccessibilityManager mShadowAccessibilityManager;
private PreferenceCategory mPreferenceCategory;
@Override
public void setUp() {
super.setUp();
final FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest();
mFeatureProvider = fakeFeatureFactory.getBluetoothFeatureProvider();
mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext));
final Preference preference = new Preference(mContext);
preference.setKey(KEY_LIVE_CAPTION);
mPreferenceCategory = new PreferenceCategory(mContext);
mPreferenceCategory.setKey(KEY_RELATED_TOOLS_GROUP);
mScreen.addPreference(mPreferenceCategory);
mScreen.addPreference(preference);
mController = new BluetoothDetailsRelatedToolsController(mContext, mFragment, mCachedDevice,
mLifecycle);
mController.init(mScreen);
}
@Test
public void isAvailable_isHearingAidDevice_available() {
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void isAvailable_isNotHearingAidDevice_notAvailable() {
when(mCachedDevice.isHearingAidDevice()).thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void displayPreference_oneRelatedToolsMatchA11yService_showOnePreference() {
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
List.of(getMockAccessibilityServiceInfo(PACKAGE_NAME, CLASS_NAME)));
when(mFeatureProvider.getRelatedTools()).thenReturn(
List.of(new ComponentName(PACKAGE_NAME, CLASS_NAME)));
mController.displayPreference(mScreen);
assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1);
}
@Test
public void displayPreference_oneRelatedToolsNotMatchA11yService_showNoPreference() {
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
List.of(getMockAccessibilityServiceInfo(PACKAGE_NAME, CLASS_NAME)));
when(mFeatureProvider.getRelatedTools()).thenReturn(
List.of(new ComponentName(PACKAGE_NAME2, CLASS_NAME)));
mController.displayPreference(mScreen);
assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0);
}
@Test
public void displayPreference_noRelatedTools_showNoPreference() {
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
List.of(getMockAccessibilityServiceInfo(PACKAGE_NAME, CLASS_NAME)));
when(mFeatureProvider.getRelatedTools()).thenReturn(null);
mController.displayPreference(mScreen);
assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0);
}
private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName,
String className) {
final ApplicationInfo applicationInfo = new ApplicationInfo();
final ServiceInfo serviceInfo = new ServiceInfo();
applicationInfo.packageName = packageName;
serviceInfo.packageName = packageName;
serviceInfo.name = className;
serviceInfo.applicationInfo = applicationInfo;
final ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.serviceInfo = serviceInfo;
try {
final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo,
mContext);
ComponentName componentName = ComponentName.unflattenFromString(
packageName + "/" + className);
info.setComponentName(componentName);
return info;
} catch (XmlPullParserException | IOException e) {
// Do nothing
}
return null;
}
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright (C) 2022 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.media.Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
import static android.media.Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.Spatializer;
import androidx.preference.PreferenceCategory;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowLooper;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetailsControllerTestBase {
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
private static final String KEY_SPATIAL_AUDIO = "spatial_audio";
private static final String KEY_HEAD_TRACKING = "head_tracking";
@Mock
private AudioManager mAudioManager;
@Mock
private Spatializer mSpatializer;
@Mock
private Lifecycle mSpatialAudioLifecycle;
@Mock
private PreferenceCategory mProfilesContainer;
@Mock
private BluetoothDevice mBluetoothDevice;
private AudioDeviceAttributes mAvailableDevice;
private BluetoothDetailsSpatialAudioController mController;
private TwoStatePreference mSpatialAudioPref;
private TwoStatePreference mHeadTrackingPref;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
when(mAudioManager.getSpatializer()).thenReturn(mSpatializer);
when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(MAC_ADDRESS);
mController = new BluetoothDetailsSpatialAudioController(mContext, mFragment,
mCachedDevice, mSpatialAudioLifecycle);
mController.mProfilesContainer = mProfilesContainer;
mSpatialAudioPref = mController.createSpatialAudioPreference(mContext);
mHeadTrackingPref = mController.createHeadTrackingPreference(mContext);
when(mProfilesContainer.findPreference(KEY_SPATIAL_AUDIO)).thenReturn(mSpatialAudioPref);
when(mProfilesContainer.findPreference(KEY_HEAD_TRACKING)).thenReturn(mHeadTrackingPref);
mAvailableDevice = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
MAC_ADDRESS);
}
@Test
public void isAvailable_forSpatializerWithLevelNone_returnsFalse() {
when(mSpatializer.getImmersiveAudioLevel()).thenReturn(SPATIALIZER_IMMERSIVE_LEVEL_NONE);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_forSpatializerWithLevelNotNone_returnsTrue() {
when(mSpatializer.getImmersiveAudioLevel()).thenReturn(
SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL);
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void refresh_spatialAudioIsTurnedOn_checksSpatialAudioPreference() {
List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
mController.setAvailableDevice(mAvailableDevice);
compatibleAudioDevices.add(mController.mAudioDevice);
when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(true);
when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
mController.refresh();
ShadowLooper.idleMainLooper();
assertThat(mSpatialAudioPref.isChecked()).isTrue();
}
@Test
public void refresh_spatialAudioIsTurnedOff_unchecksSpatialAudioPreference() {
List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
mController.refresh();
ShadowLooper.idleMainLooper();
assertThat(mSpatialAudioPref.isChecked()).isFalse();
}
@Test
public void refresh_spatialAudioOnAndHeadTrackingIsAvailable_showsHeadTrackingPreference() {
List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
compatibleAudioDevices.add(mController.mAudioDevice);
when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true);
mController.refresh();
ShadowLooper.idleMainLooper();
assertThat(mHeadTrackingPref.isVisible()).isTrue();
}
@Test
public void
refresh_spatialAudioOnAndHeadTrackingIsNotAvailable_hidesHeadTrackingPreference() {
List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
mController.setAvailableDevice(mAvailableDevice);
compatibleAudioDevices.add(mController.mAudioDevice);
when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(false);
mController.refresh();
ShadowLooper.idleMainLooper();
verify(mProfilesContainer).removePreference(mHeadTrackingPref);
}
@Test
public void refresh_spatialAudioOff_hidesHeadTrackingPreference() {
List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
mController.refresh();
ShadowLooper.idleMainLooper();
verify(mProfilesContainer).removePreference(mHeadTrackingPref);
}
@Test
public void refresh_headTrackingIsTurnedOn_checksHeadTrackingPreference() {
List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
mController.setAvailableDevice(mAvailableDevice);
compatibleAudioDevices.add(mController.mAudioDevice);
when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(true);
when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true);
when(mSpatializer.isHeadTrackerEnabled(mController.mAudioDevice)).thenReturn(true);
mController.refresh();
ShadowLooper.idleMainLooper();
assertThat(mHeadTrackingPref.isChecked()).isTrue();
}
@Test
public void refresh_headTrackingIsTurnedOff_unchecksHeadTrackingPreference() {
List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
mController.setAvailableDevice(mAvailableDevice);
compatibleAudioDevices.add(mController.mAudioDevice);
when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(true);
when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true);
when(mSpatializer.isHeadTrackerEnabled(mController.mAudioDevice)).thenReturn(false);
mController.refresh();
ShadowLooper.idleMainLooper();
assertThat(mHeadTrackingPref.isChecked()).isFalse();
}
@Test
public void turnedOnSpatialAudio_invokesAddCompatibleAudioDevice() {
mController.setAvailableDevice(mAvailableDevice);
mSpatialAudioPref.setChecked(true);
mController.onPreferenceClick(mSpatialAudioPref);
verify(mSpatializer).addCompatibleAudioDevice(mController.mAudioDevice);
}
@Test
public void turnedOffSpatialAudio_invokesRemoveCompatibleAudioDevice() {
mController.setAvailableDevice(mAvailableDevice);
mSpatialAudioPref.setChecked(false);
mController.onPreferenceClick(mSpatialAudioPref);
verify(mSpatializer).removeCompatibleAudioDevice(mController.mAudioDevice);
}
@Test
public void turnedOnHeadTracking_invokesSetHeadTrackerEnabled_setsTrue() {
mController.setAvailableDevice(mAvailableDevice);
mHeadTrackingPref.setChecked(true);
mController.onPreferenceClick(mHeadTrackingPref);
verify(mSpatializer).setHeadTrackerEnabled(true, mController.mAudioDevice);
}
@Test
public void turnedOffHeadTracking_invokesSetHeadTrackerEnabled_setsFalse() {
mController.setAvailableDevice(mAvailableDevice);
mHeadTrackingPref.setChecked(false);
mController.onPreferenceClick(mHeadTrackingPref);
verify(mSpatializer).setHeadTrackerEnabled(false, mController.mAudioDevice);
}
}

View File

@@ -0,0 +1,298 @@
/*
* 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 android.bluetooth.BluetoothDevice.BOND_NONE;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceControlsController.KEY_DEVICE_CONTROLS_GENERAL_GROUP;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
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.app.settings.SettingsEnums;
import android.companion.CompanionDeviceManager;
import android.content.Context;
import android.content.Intent;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.UserManager;
import android.util.FeatureFlagUtils;
import android.view.InputDevice;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.fakes.RoboMenu;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
com.android.settings.testutils.shadow.ShadowUserManager.class,
com.android.settings.testutils.shadow.ShadowFragment.class,
})
public class BluetoothDeviceDetailsFragmentTest {
private static final String TEST_ADDRESS = "55:66:77:88:99:AA";
private BluetoothDeviceDetailsFragment mFragment;
private Context mContext;
private RoboMenu mMenu;
private MenuInflater mInflater;
private FragmentTransaction mFragmentTransaction;
private FragmentActivity mActivity;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CachedBluetoothDevice mCachedDevice;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private LocalBluetoothManager mLocalManager;
@Mock
private PreferenceScreen mPreferenceScreen;
@Mock
private UserManager mUserManager;
@Mock
private InputManager mInputManager;
@Mock
private CompanionDeviceManager mCompanionDeviceManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
doReturn(mInputManager).when(mContext).getSystemService(InputManager.class);
doReturn(mCompanionDeviceManager).when(mContext)
.getSystemService(CompanionDeviceManager.class);
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(ImmutableList.of());
removeInputDeviceWithMatchingBluetoothAddress();
FakeFeatureFactory.setupForTest();
mFragment = setupFragment();
mFragment.onAttach(mContext);
mMenu = new RoboMenu(mContext);
mInflater = new MenuInflater(mContext);
}
@Test
public void verifyOnAttachResult() {
assertThat(mFragment.mDeviceAddress).isEqualTo(TEST_ADDRESS);
assertThat(mFragment.mManager).isEqualTo(mLocalManager);
assertThat(mFragment.mCachedDevice).isEqualTo(mCachedDevice);
assertThat(mFragment.mInputDevice).isEqualTo(null);
}
@Test
public void verifyOnAttachResult_flagEnabledAndInputDeviceSet_returnsInputDevice() {
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
true);
InputDevice inputDevice = createInputDeviceWithMatchingBluetoothAddress();
BluetoothDeviceDetailsFragment fragment = setupFragment();
FragmentActivity activity = mock(FragmentActivity.class);
doReturn(inputDevice).when(fragment).getInputDevice(any());
doReturn(activity).when(fragment).getActivity();
fragment.onAttach(mContext);
assertThat(fragment.mDeviceAddress).isEqualTo(TEST_ADDRESS);
assertThat(fragment.mManager).isEqualTo(mLocalManager);
assertThat(fragment.mCachedDevice).isEqualTo(mCachedDevice);
assertThat(fragment.mInputDevice).isEqualTo(inputDevice);
}
@Test
public void verifyOnAttachResult_flagDisabled_returnsNullInputDevice() {
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
false);
InputDevice inputDevice = createInputDeviceWithMatchingBluetoothAddress();
BluetoothDeviceDetailsFragment fragment = setupFragment();
FragmentActivity activity = mock(FragmentActivity.class);
doReturn(inputDevice).when(fragment).getInputDevice(any());
doReturn(activity).when(fragment).getActivity();
fragment.onAttach(mContext);
assertThat(fragment.mDeviceAddress).isEqualTo(TEST_ADDRESS);
assertThat(fragment.mManager).isEqualTo(mLocalManager);
assertThat(fragment.mCachedDevice).isEqualTo(mCachedDevice);
assertThat(fragment.mInputDevice).isEqualTo(null);
}
@Test
public void getTitle_displayEditTitle() {
mFragment.onCreateOptionsMenu(mMenu, mInflater);
final MenuItem item = mMenu.getItem(0);
assertThat(item.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_rename_button));
}
@Test
public void getTitle_inputDeviceTitle() {
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
true);
InputDevice inputDevice = mock(InputDevice.class);
doReturn(true).when(inputDevice).supportsSource(InputDevice.SOURCE_STYLUS);
doReturn(inputDevice).when(mFragment).getInputDevice(mContext);
mFragment.onAttach(mContext);
mFragment.setTitleForInputDevice();
assertThat(mActivity.getTitle().toString()).isEqualTo(
mContext.getString(R.string.stylus_device_details_title));
}
@Test
public void getTitle_inputDeviceNull_doesNotSetTitle() {
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
true);
doReturn(null).when(mFragment).getInputDevice(mContext);
mFragment.onAttach(mContext);
mFragment.setTitleForInputDevice();
verify(mActivity, times(0)).setTitle(any());
}
@Test
public void editMenu_clicked_showDialog() {
mFragment.onCreateOptionsMenu(mMenu, mInflater);
final MenuItem item = mMenu.getItem(0);
ArgumentCaptor<Fragment> captor = ArgumentCaptor.forClass(Fragment.class);
mFragment.onOptionsItemSelected(item);
assertThat(item.getItemId())
.isEqualTo(BluetoothDeviceDetailsFragment.EDIT_DEVICE_NAME_ITEM_ID);
verify(mFragmentTransaction).add(captor.capture(), eq(RemoteDeviceNameDialogFragment.TAG));
RemoteDeviceNameDialogFragment dialog = (RemoteDeviceNameDialogFragment) captor.getValue();
assertThat(dialog).isNotNull();
}
@Test
public void finishFragmentIfNecessary_deviceIsBondNone_finishFragment() {
when(mCachedDevice.getBondState()).thenReturn(BOND_NONE);
mFragment.finishFragmentIfNecessary();
verify(mFragment).finish();
}
@Test
public void createPreferenceControllers_launchFromHAPage_deviceControllerNotExist() {
BluetoothDeviceDetailsFragment fragment = setupFragment();
Intent intent = fragment.getActivity().getIntent();
intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
SettingsEnums.ACCESSIBILITY_HEARING_AID_SETTINGS);
fragment.onAttach(mContext);
List<AbstractPreferenceController> controllerList = fragment.createPreferenceControllers(
mContext);
assertThat(controllerList.stream()
.anyMatch(controller -> controller.getPreferenceKey().equals(
KEY_DEVICE_CONTROLS_GENERAL_GROUP))).isFalse();
}
@Test
public void createPreferenceControllers_notLaunchFromHAPage_deviceControllerExist() {
BluetoothDeviceDetailsFragment fragment = setupFragment();
Intent intent = fragment.getActivity().getIntent();
intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
SettingsEnums.PAGE_UNKNOWN);
fragment.onAttach(mContext);
List<AbstractPreferenceController> controllerList = fragment.createPreferenceControllers(
mContext);
assertThat(controllerList.stream()
.anyMatch(controller -> controller.getPreferenceKey().equals(
KEY_DEVICE_CONTROLS_GENERAL_GROUP))).isTrue();
}
private InputDevice createInputDeviceWithMatchingBluetoothAddress() {
doReturn(new int[]{0}).when(mInputManager).getInputDeviceIds();
InputDevice device = mock(InputDevice.class);
doReturn(TEST_ADDRESS).when(mInputManager).getInputDeviceBluetoothAddress(0);
doReturn(device).when(mInputManager).getInputDevice(0);
return device;
}
private InputDevice removeInputDeviceWithMatchingBluetoothAddress() {
doReturn(new int[]{0}).when(mInputManager).getInputDeviceIds();
doReturn(null).when(mInputManager).getInputDeviceBluetoothAddress(0);
return null;
}
private BluetoothDeviceDetailsFragment setupFragment() {
BluetoothDeviceDetailsFragment fragment = spy(
BluetoothDeviceDetailsFragment.newInstance(TEST_ADDRESS));
doReturn(mLocalManager).when(fragment).getLocalBluetoothManager(any());
doReturn(mCachedDevice).when(fragment).getCachedDevice(any());
doReturn(mPreferenceScreen).when(fragment).getPreferenceScreen();
doReturn(mUserManager).when(fragment).getUserManager();
mActivity = spy(Robolectric.setupActivity(FragmentActivity.class));
doReturn(mActivity).when(fragment).getActivity();
doReturn(mContext).when(fragment).getContext();
FragmentManager fragmentManager = mock(FragmentManager.class);
doReturn(fragmentManager).when(fragment).getFragmentManager();
mFragmentTransaction = mock(FragmentTransaction.class);
doReturn(mFragmentTransaction).when(fragmentManager).beginTransaction();
doReturn(TEST_ADDRESS).when(mCachedDevice).getAddress();
doReturn(TEST_ADDRESS).when(mCachedDevice).getIdentityAddress();
Bundle args = new Bundle();
args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, TEST_ADDRESS);
fragment.setArguments(args);
return fragment;
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class BluetoothDeviceNamePreferenceControllerTest {
private static final String DEVICE_NAME = "Nightshade";
private static final int ORDER = 1;
private static final String KEY_DEVICE_NAME = "test_key_name";
private Context mContext;
@Mock
private PreferenceScreen mPreferenceScreen;
private Preference mPreference;
private BluetoothDeviceNamePreferenceController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mPreferenceScreen.getContext()).thenReturn(mContext);
mPreference = new Preference(mContext);
mPreference.setKey(KEY_DEVICE_NAME);
mController = spy(new BluetoothDeviceNamePreferenceController(mContext, KEY_DEVICE_NAME));
doReturn(DEVICE_NAME).when(mController).getDeviceName();
}
@Test
public void testUpdateDeviceName_showSummaryWithDeviceName() {
mController.updatePreferenceState(mPreference);
final CharSequence summary = mPreference.getSummary();
assertThat(summary.toString())
.isEqualTo("Visible as \u201CNightshade\u201D to other devices");
assertThat(mPreference.isSelectable()).isFalse();
}
@Test
public void testCreateBluetoothDeviceNamePreference() {
Preference preference =
mController.createBluetoothDeviceNamePreference(mPreferenceScreen, ORDER);
assertThat(preference.getKey()).isEqualTo(mController.getPreferenceKey());
assertThat(preference.getOrder()).isEqualTo(ORDER);
verify(mPreferenceScreen).addPreference(preference);
}
@Test
public void testOnStart_receiverRegistered() {
mController.onStart();
verify(mContext).registerReceiver(eq(mController.mReceiver), any());
}
@Test
public void testOnStop_receiverUnregistered() {
// register it first
mContext.registerReceiver(mController.mReceiver, null,
Context.RECEIVER_EXPORTED/*UNAUDITED*/);
mController.onStop();
verify(mContext).unregisterReceiver(mController.mReceiver);
}
}

View File

@@ -0,0 +1,269 @@
/*
* 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,
com.android.settings.testutils.shadow.ShadowFragment.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.getDevicePreferenceMap().put(mCachedBluetoothDevice, preference);
when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
assertThat(mFragment.getDevicePreferenceMap().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.getDevicePreferenceMap().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

@@ -0,0 +1,319 @@
/*
* 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.ArgumentMatchers.any;
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.content.Context;
import android.graphics.drawable.Drawable;
import android.os.UserManager;
import android.util.Pair;
import android.view.ContextThemeWrapper;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAlertDialogCompat.class})
public class BluetoothDevicePreferenceTest {
private static final boolean SHOW_DEVICES_WITHOUT_NAMES = true;
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
private static final String MAC_ADDRESS_2 = "05:52:C7:0B:D8:3C";
private static final String MAC_ADDRESS_3 = "06:52:C7:0B:D8:3C";
private static final String MAC_ADDRESS_4 = "07:52:C7:0B:D8:3C";
private static final Comparator<BluetoothDevicePreference> COMPARATOR =
Comparator.naturalOrder();
private static final String FAKE_DESCRIPTION = "fake_description";
private Context mContext;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private CachedBluetoothDevice mCachedDevice1;
@Mock
private CachedBluetoothDevice mCachedDevice2;
@Mock
private CachedBluetoothDevice mCachedDevice3;
@Mock
private BluetoothDevice mBluetoothDevice;
@Mock
private BluetoothDevice mBluetoothDevice1;
@Mock
private BluetoothDevice mBluetoothDevice2;
@Mock
private BluetoothDevice mBluetoothDevice3;
@Mock
private Drawable mDrawable;
@Mock
private BluetoothAdapter mBluetoothAdapter;
private FakeFeatureFactory mFakeFeatureFactory;
private MetricsFeatureProvider mMetricsFeatureProvider;
private BluetoothDevicePreference mPreference;
private List<BluetoothDevicePreference> mPreferenceList = new ArrayList<>();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Context context = spy(RuntimeEnvironment.application.getApplicationContext());
mContext = new ContextThemeWrapper(context, R.style.Theme_Settings);
mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
mMetricsFeatureProvider = mFakeFeatureFactory.getMetricsFeatureProvider();
when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCachedBluetoothDevice.getDrawableWithDescription())
.thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION));
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedDevice1.getAddress()).thenReturn(MAC_ADDRESS_2);
when(mCachedDevice1.getDrawableWithDescription())
.thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION));
when(mCachedDevice1.getDevice()).thenReturn(mBluetoothDevice1);
when(mCachedDevice2.getAddress()).thenReturn(MAC_ADDRESS_3);
when(mCachedDevice2.getDrawableWithDescription())
.thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION));
when(mCachedDevice2.getDevice()).thenReturn(mBluetoothDevice2);
when(mCachedDevice3.getAddress()).thenReturn(MAC_ADDRESS_4);
when(mCachedDevice3.getDrawableWithDescription())
.thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION));
when(mCachedDevice3.getDevice()).thenReturn(mBluetoothDevice3);
mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
SHOW_DEVICES_WITHOUT_NAMES, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
mPreference.mBluetoothAdapter = mBluetoothAdapter;
}
@Test
public void onClicked_deviceConnected_shouldLogBluetoothDisconnectEvent() {
when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
mPreference.onClicked();
verify(mMetricsFeatureProvider)
.action(mContext, MetricsEvent.ACTION_SETTINGS_BLUETOOTH_DISCONNECT);
}
@Test
public void onClicked_deviceBonded_shouldLogBluetoothConnectEvent() {
when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
mPreference.onClicked();
verify(mMetricsFeatureProvider)
.action(mContext, MetricsEvent.ACTION_SETTINGS_BLUETOOTH_CONNECT);
}
@Test
public void onClicked_deviceNotBonded_shouldLogBluetoothPairEvent() {
when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
when(mCachedBluetoothDevice.startPairing()).thenReturn(true);
when(mCachedBluetoothDevice.hasHumanReadableName()).thenReturn(true);
mPreference.onClicked();
verify(mMetricsFeatureProvider)
.action(mContext, MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR);
verify(mMetricsFeatureProvider, never())
.action(mContext,
MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR_DEVICES_WITHOUT_NAMES);
}
@Test
public void onClicked_deviceNotBonded_shouldLogBluetoothPairEventAndPairWithoutNameEvent() {
when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
when(mCachedBluetoothDevice.startPairing()).thenReturn(true);
when(mCachedBluetoothDevice.hasHumanReadableName()).thenReturn(false);
mPreference.onClicked();
verify(mMetricsFeatureProvider)
.action(mContext, MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR);
verify(mMetricsFeatureProvider)
.action(mContext,
MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR_DEVICES_WITHOUT_NAMES);
}
@Test
public void getSecondTargetResource_shouldBeGearIconLayout() {
assertThat(mPreference.getSecondTargetResId()).isEqualTo(R.layout.preference_widget_gear);
}
@Test
public void shouldHideSecondTarget_noDevice_shouldReturnTrue() {
ReflectionHelpers.setField(mPreference, "mCachedDevice", null);
assertThat(mPreference.shouldHideSecondTarget()).isTrue();
}
@Test
public void shouldHideSecondTarget_notBond_shouldReturnTrue() {
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
assertThat(mPreference.shouldHideSecondTarget()).isTrue();
}
@Test
public void shouldHideSecondTarget_hasUserRestriction_shouldReturnTrue() {
final UserManager um = mock(UserManager.class);
ReflectionHelpers.setField(mPreference, "mUserManager", um);
when(um.hasUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(true);
assertThat(mPreference.shouldHideSecondTarget()).isTrue();
}
@Test
public void shouldHideSecondTarget_hasBoundDeviceAndNoRestriction_shouldReturnFalse() {
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
final UserManager um = mock(UserManager.class);
ReflectionHelpers.setField(mPreference, "mUserManager", um);
when(um.hasUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(false);
assertThat(mPreference.shouldHideSecondTarget()).isFalse();
}
@Test
public void isVisible_showDeviceWithoutNames_visible() {
doReturn(false).when(mCachedBluetoothDevice).hasHumanReadableName();
BluetoothDevicePreference preference =
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
SHOW_DEVICES_WITHOUT_NAMES,
BluetoothDevicePreference.SortType.TYPE_DEFAULT);
assertThat(preference.isVisible()).isTrue();
}
@Test
public void isVisible_hideDeviceWithoutNames_invisible() {
doReturn(false).when(mCachedBluetoothDevice).hasHumanReadableName();
BluetoothDevicePreference preference =
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
false, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
assertThat(preference.isVisible()).isFalse();
}
@Test
public void setNeedNotifyHierarchyChanged_updateValue() {
mPreference.setNeedNotifyHierarchyChanged(true);
assertThat(mPreference.mNeedNotifyHierarchyChanged).isTrue();
}
@Test
public void compareTo_sortTypeFIFO() {
final BluetoothDevicePreference preference3 = new BluetoothDevicePreference(mContext,
mCachedDevice3, SHOW_DEVICES_WITHOUT_NAMES,
BluetoothDevicePreference.SortType.TYPE_FIFO);
final BluetoothDevicePreference preference2 = new BluetoothDevicePreference(mContext,
mCachedDevice2, SHOW_DEVICES_WITHOUT_NAMES,
BluetoothDevicePreference.SortType.TYPE_FIFO);
final BluetoothDevicePreference preference1 = new BluetoothDevicePreference(mContext,
mCachedDevice1, SHOW_DEVICES_WITHOUT_NAMES,
BluetoothDevicePreference.SortType.TYPE_FIFO);
mPreferenceList.add(preference1);
mPreferenceList.add(preference2);
mPreferenceList.add(preference3);
Collections.sort(mPreferenceList, COMPARATOR);
assertThat(mPreferenceList.get(0).getCachedDevice().getAddress())
.isEqualTo(preference3.getCachedDevice().getAddress());
assertThat(mPreferenceList.get(1).getCachedDevice().getAddress())
.isEqualTo(preference2.getCachedDevice().getAddress());
assertThat(mPreferenceList.get(2).getCachedDevice().getAddress())
.isEqualTo(preference1.getCachedDevice().getAddress());
}
@Test
public void compareTo_sortTypeDefault() {
final BluetoothDevicePreference preference3 = new BluetoothDevicePreference(mContext,
mCachedDevice3, SHOW_DEVICES_WITHOUT_NAMES,
BluetoothDevicePreference.SortType.TYPE_DEFAULT);
final BluetoothDevicePreference preference2 = new BluetoothDevicePreference(mContext,
mCachedDevice2, SHOW_DEVICES_WITHOUT_NAMES,
BluetoothDevicePreference.SortType.TYPE_DEFAULT);
final BluetoothDevicePreference preference1 = new BluetoothDevicePreference(mContext,
mCachedDevice1, SHOW_DEVICES_WITHOUT_NAMES,
BluetoothDevicePreference.SortType.TYPE_DEFAULT);
mPreferenceList.add(preference1);
mPreferenceList.add(preference2);
mPreferenceList.add(preference3);
Collections.sort(mPreferenceList, COMPARATOR);
assertThat(mPreferenceList.get(0).getCachedDevice().getAddress())
.isEqualTo(preference1.getCachedDevice().getAddress());
assertThat(mPreferenceList.get(1).getCachedDevice().getAddress())
.isEqualTo(preference2.getCachedDevice().getAddress());
assertThat(mPreferenceList.get(2).getCachedDevice().getAddress())
.isEqualTo(preference3.getCachedDevice().getAddress());
}
@Test
public void onAttached_callbackNotRemoved_doNotRegisterCallback() {
mPreference.onAttached();
// After the onAttached(), the callback is registered.
// If it goes to the onAttached() again, then it do not register again, since the
// callback is not removed.
mPreference.onAttached();
verify(mCachedBluetoothDevice, times(1)).registerCallback(any());
verify(mBluetoothAdapter, times(1)).addOnMetadataChangedListener(any(), any(), any());
}
@Test
public void onAttached_callbackRemoved_registerCallback() {
mPreference.onAttached();
mPreference.onPrepareForRemoval();
mPreference.onAttached();
verify(mCachedBluetoothDevice, times(1)).unregisterCallback(any());
verify(mCachedBluetoothDevice, times(2)).registerCallback(any());
verify(mBluetoothAdapter, times(2)).addOnMetadataChangedListener(any(), any(), any());
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowBluetoothAdapter.class,
com.android.settings.testutils.shadow.ShadowFragment.class,
})
public class BluetoothDeviceRenamePreferenceControllerTest {
private static final String DEVICE_NAME = "Nightshade";
private static final String PREF_KEY = "bt_rename_devices";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Fragment mFragment;
@Mock
private FragmentTransaction mFragmentTransaction;
@Mock
private PreferenceScreen mScreen;
private Context mContext;
private Preference mPreference;
private BluetoothDeviceRenamePreferenceController mController;
private BluetoothAdapter mBluetoothAdapter;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
mPreference = new Preference(mContext);
mPreference.setKey(PREF_KEY);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter);
mController = spy(new BluetoothDeviceRenamePreferenceController(mContext, PREF_KEY));
mController.setFragment(mFragment);
doReturn(DEVICE_NAME).when(mController).getDeviceName();
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
mController.displayPreference(mScreen);
}
@Test
public void testUpdateDeviceName_showSummaryWithDeviceName() {
mController.updatePreferenceState(mPreference);
final CharSequence summary = mPreference.getSummary();
assertThat(summary.toString()).isEqualTo(DEVICE_NAME);
}
@Test
public void testHandlePreferenceTreeClick_startDialogFragment() {
when(mFragment.getFragmentManager().beginTransaction()).thenReturn(mFragmentTransaction);
mController.handlePreferenceTreeClick(mPreference);
verify(mFragmentTransaction).add(any(), anyString());
verify(mFragmentTransaction).commit();
}
@Test
public void displayPreference_shouldFindPreferenceWithMatchingPrefKey() {
assertThat(mController.mPreference.getKey()).isEqualTo(mController.getPreferenceKey());
}
@Test
public void updatePreferenceState_whenBTisOnPreferenceShouldBeVisible() {
mShadowBluetoothAdapter.setEnabled(true);
mController.updatePreferenceState(mPreference);
assertThat(mPreference.isVisible()).isTrue();
}
@Test
public void updatePreferenceState_whenBTisOffPreferenceShouldBeHide() {
mShadowBluetoothAdapter.setEnabled(false);
mController.updatePreferenceState(mPreference);
assertThat(mPreference.isVisible()).isFalse();
}
}

View File

@@ -0,0 +1,282 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.Pair;
import androidx.preference.Preference;
import com.android.settings.SettingsActivity;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class BluetoothDeviceUpdaterTest {
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
private static final String SUB_MAC_ADDRESS = "05:52:C7:0B:D8:3C";
private static final String TEST_NAME = "test_name";
@Mock
private DevicePreferenceCallback mDevicePreferenceCallback;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private CachedBluetoothDevice mSubCachedBluetoothDevice;
@Mock
private BluetoothDevice mBluetoothDevice;
@Mock
private BluetoothDevice mSubBluetoothDevice;
@Mock
private SettingsActivity mSettingsActivity;
@Mock
private LocalBluetoothManager mLocalManager;
@Mock
private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock
private Drawable mDrawable;
private Context mContext;
private TestBluetoothDeviceUpdater mBluetoothDeviceUpdater;
private BluetoothDevicePreference mPreference;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private List<CachedBluetoothDevice> mCachedDevices = new ArrayList<>();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
mContext = RuntimeEnvironment.application;
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mCachedDevices.add(mCachedBluetoothDevice);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice);
when(mLocalManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mSubBluetoothDevice.getAddress()).thenReturn(SUB_MAC_ADDRESS);
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
/* showDeviceWithoutNames= */ false,
BluetoothDevicePreference.SortType.TYPE_DEFAULT);
mBluetoothDeviceUpdater = new TestBluetoothDeviceUpdater(mContext,
mDevicePreferenceCallback, mLocalManager, /* metricsCategory= */ 0);
mBluetoothDeviceUpdater.setPrefContext(mContext);
}
@Test
public void testAddPreference_deviceExist_doNothing() {
mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference);
mBluetoothDeviceUpdater.addPreference(mCachedBluetoothDevice);
verify(mDevicePreferenceCallback, never()).onDeviceAdded(any(Preference.class));
}
@Test
public void testAddPreference_deviceNotExist_addPreference() {
mBluetoothDeviceUpdater.addPreference(mCachedBluetoothDevice);
final Preference preference = mBluetoothDeviceUpdater.mPreferenceMap.get(mBluetoothDevice);
assertThat(preference).isNotNull();
verify(mDevicePreferenceCallback).onDeviceAdded(preference);
}
@Test
public void testRemovePreference_deviceExist_removePreference() {
mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference);
mBluetoothDeviceUpdater.removePreference(mCachedBluetoothDevice);
verify(mDevicePreferenceCallback).onDeviceRemoved(mPreference);
assertThat(mBluetoothDeviceUpdater.mPreferenceMap.containsKey(mBluetoothDevice)).isFalse();
}
@Test
public void testOnDeviceDeleted_deviceExists_removePreference() {
mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference);
mBluetoothDeviceUpdater.onDeviceDeleted(mCachedBluetoothDevice);
verify(mDevicePreferenceCallback).onDeviceRemoved(mPreference);
assertThat(mBluetoothDeviceUpdater.mPreferenceMap.containsKey(mBluetoothDevice)).isFalse();
}
@Test
public void testRemovePreference_deviceNotExist_doNothing() {
mBluetoothDeviceUpdater.removePreference(mCachedBluetoothDevice);
verify(mDevicePreferenceCallback, never()).onDeviceRemoved(any(Preference.class));
}
@Test
public void testRemovePreference_subDeviceExist_removePreference() {
when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
mBluetoothDeviceUpdater.mPreferenceMap.put(mSubBluetoothDevice, mPreference);
assertThat(mBluetoothDeviceUpdater.mPreferenceMap.
containsKey(mSubBluetoothDevice)).isTrue();
mBluetoothDeviceUpdater.removePreference(mCachedBluetoothDevice);
verify(mDevicePreferenceCallback).onDeviceRemoved(mPreference);
assertThat(mBluetoothDeviceUpdater.mPreferenceMap.
containsKey(mSubBluetoothDevice)).isFalse();
}
@Test
public void testDeviceProfilesListener_click_startBluetoothDeviceDetailPage() {
mBluetoothDeviceUpdater = new TestBluetoothDeviceUpdater(mSettingsActivity,
mDevicePreferenceCallback, mLocalManager, /* metricsCategory= */ 0);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
mBluetoothDeviceUpdater.mDeviceProfilesListener.onGearClick(mPreference);
verify(mSettingsActivity).startActivity(intentCaptor.capture());
assertThat(intentCaptor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(BluetoothDeviceDetailsFragment.class.getName());
}
@Test
public void isDeviceConnected_deviceConnected() {
doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState();
doReturn(true).when(mBluetoothDevice).isConnected();
assertThat(mBluetoothDeviceUpdater.isDeviceConnected(mCachedBluetoothDevice)).isTrue();
}
@Test
public void isDeviceConnected_deviceNotConnected() {
doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState();
doReturn(false).when(mBluetoothDevice).isConnected();
assertThat(mBluetoothDeviceUpdater.isDeviceConnected(mCachedBluetoothDevice)).isFalse();
}
@Test
public void registerCallback_localBluetoothManagerNull_shouldNotCrash() {
mBluetoothDeviceUpdater.mLocalManager = null;
// Shouldn't crash
mBluetoothDeviceUpdater.registerCallback();
}
@Test
public void unregisterCallback_localBluetoothManagerNull_shouldNotCrash() {
mBluetoothDeviceUpdater.mLocalManager = null;
// Shouldn't crash
mBluetoothDeviceUpdater.unregisterCallback();
}
@Test
public void forceUpdate_bluetoothDisabled_removeAllDevicesFromPreference() {
mShadowBluetoothAdapter.setEnabled(false);
mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference);
mBluetoothDeviceUpdater.forceUpdate();
verify(mDevicePreferenceCallback).onDeviceRemoved(mPreference);
assertThat(mBluetoothDeviceUpdater.mPreferenceMap).isEmpty();
}
@Test
public void forceUpdate_bluetoothEnabled_addPreference() {
mShadowBluetoothAdapter.setEnabled(true);
mBluetoothDeviceUpdater.forceUpdate();
verify(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class));
}
@Test
public void onBluetoothStateChanged_bluetoothStateIsOn_forceUpdate() {
mShadowBluetoothAdapter.setEnabled(true);
mBluetoothDeviceUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
verify(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class));
}
@Test
public void onBluetoothStateChanged_bluetoothStateIsOff_removeAllDevicesFromPreference() {
mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference);
mBluetoothDeviceUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
verify(mDevicePreferenceCallback).onDeviceRemoved(mPreference);
assertThat(mBluetoothDeviceUpdater.mPreferenceMap.containsKey(mBluetoothDevice)).isFalse();
}
@Test
public void havePreference_refreshPreference() {
mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference);
mPreference.setTitle("fake_name");
when(mCachedBluetoothDevice.getName()).thenReturn(TEST_NAME);
mBluetoothDeviceUpdater.refreshPreference();
assertThat(mPreference.getTitle()).isEqualTo(TEST_NAME);
}
public static class TestBluetoothDeviceUpdater extends BluetoothDeviceUpdater {
public TestBluetoothDeviceUpdater(Context context,
DevicePreferenceCallback devicePreferenceCallback,
LocalBluetoothManager localManager, int metricsCategory) {
super(context, devicePreferenceCallback, localManager, metricsCategory);
}
@Override
public boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice) {
return true;
}
@Override
protected String getPreferenceKey() {
return "test_bt";
}
}
}

View File

@@ -0,0 +1,254 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
import android.os.UserManager;
import android.view.View;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.widget.SwitchWidgetController;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
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 BluetoothEnablerTest {
private static EnforcedAdmin sFakeEnforcedAdmin;
private PreferenceViewHolder mHolder;
private RestrictedSwitchPreference mRestrictedSwitchPreference;
@BeforeClass
public static void beforeClass() {
sFakeEnforcedAdmin = new EnforcedAdmin(new ComponentName("test.package", "test.Class"),
UserHandle.of(10));
}
@Mock
private MetricsFeatureProvider mMetricsFeatureProvider;
@Mock
private RestrictionUtils mRestrictionUtils;
@Mock
private SwitchWidgetController.OnSwitchChangeListener mCallback;
private Context mContext;
@Mock
private SwitchWidgetController mSwitchController;
private BluetoothEnabler mBluetoothEnabler;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
mRestrictedSwitchPreference = new RestrictedSwitchPreference(mContext);
mBluetoothEnabler = new BluetoothEnabler(
mContext,
mSwitchController,
mMetricsFeatureProvider,
123,
mRestrictionUtils);
mHolder = PreferenceViewHolder.createInstanceForTests(mock(View.class));
mRestrictedSwitchPreference.onBindViewHolder(mHolder);
mBluetoothEnabler.setToggleCallback(mCallback);
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
}
@Test
public void onSwitchToggled_shouldLogActionWithSuppliedEvent() {
// WHEN the switch is toggled...
mBluetoothEnabler.onSwitchToggled(false);
// THEN the corresponding metrics action is logged.
verify(mMetricsFeatureProvider).action(mContext, 123, false);
}
@Test
public void onSwitchToggled_shouldTriggerCallback() {
// WHEN the switch is toggled...
mBluetoothEnabler.onSwitchToggled(false);
// THEN the callback is triggered
verify(mCallback).onSwitchToggled(false);
}
@Test
public void maybeEnforceRestrictions_noRestrictions() {
// GIVEN there are no restrictions set...
when(mRestrictionUtils.checkIfRestrictionEnforced(any(Context.class), any(String.class)))
.thenReturn(null);
// WHEN the maybeEnforceRestrictions is called...
// THEN false is returned to indicate there was no restriction to enforce
assertThat(mBluetoothEnabler.maybeEnforceRestrictions()).isFalse();
// THEN a null EnfoceAdmin is set.
verify(mSwitchController).setDisabledByAdmin(null);
// THEN the state of the switch isn't changed.
verify(mSwitchController, never()).setChecked(anyBoolean());
}
@Test
public void maybeEnforceRestrictions_disallowBluetoothRestrictionSet() {
// GIVEN Bluetooth has been disallowed...
when(mRestrictionUtils.checkIfRestrictionEnforced(
mContext, UserManager.DISALLOW_BLUETOOTH)).thenReturn(sFakeEnforcedAdmin);
when(mRestrictionUtils.checkIfRestrictionEnforced(
mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(null);
// WHEN the maybeEnforceRestrictions is called...
// THEN true is returned to indicate there was a restriction to enforce.
assertThat(mBluetoothEnabler.maybeEnforceRestrictions()).isTrue();
// THEN the expected EnfoceAdmin is set.
verify(mSwitchController).setDisabledByAdmin(sFakeEnforcedAdmin);
// THEN the switch is unchecked.
verify(mSwitchController).setChecked(false);
}
@Test
public void maybeEnforceRestrictions_disallowConfigBluetoothRestrictionSet() {
// GIVEN configuring Bluetooth has been disallowed...
when(mRestrictionUtils.checkIfRestrictionEnforced(
mContext, UserManager.DISALLOW_BLUETOOTH)).thenReturn(null);
when(mRestrictionUtils.checkIfRestrictionEnforced(
mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(sFakeEnforcedAdmin);
// WHEN the maybeEnforceRestrictions is called...
// THEN true is returned to indicate there was a restriction to enforce.
assertThat(mBluetoothEnabler.maybeEnforceRestrictions()).isTrue();
// THEN the expected EnfoceAdmin is set.
verify(mSwitchController).setDisabledByAdmin(sFakeEnforcedAdmin);
// THEN the switch is unchecked.
verify(mSwitchController).setChecked(false);
}
@Test
public void maybeEnforceRestrictions_disallowBluetoothNotOverriden() {
// GIVEN Bluetooth has been disallowed...
when(mRestrictionUtils.checkIfRestrictionEnforced(
mContext, UserManager.DISALLOW_BLUETOOTH)).thenReturn(sFakeEnforcedAdmin);
when(mRestrictionUtils.checkIfRestrictionEnforced(
mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(null);
mBluetoothEnabler.resume(mContext);
verify(mSwitchController, never()).setEnabled(true);
}
@Test
public void startWithBluetoothOff_switchIsOff() {
mShadowBluetoothAdapter.setState(BluetoothAdapter.STATE_OFF);
verify(mSwitchController, never()).setChecked(anyBoolean());
mBluetoothEnabler.resume(mContext);
verify(mSwitchController, never()).setChecked(true);
}
@Test
public void startWithBluetoothOn_switchIsOn() {
mShadowBluetoothAdapter.setState(BluetoothAdapter.STATE_ON);
verify(mSwitchController, never()).setChecked(anyBoolean());
mBluetoothEnabler.resume(mContext);
verify(mSwitchController, never()).setChecked(false);
verify(mSwitchController).setChecked(true);
}
@Test
public void bluetoothTurnsOff_switchTurnsOff() {
// Start up with bluetooth turned on. The switch should get turned on.
ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
when(mContext.registerReceiver(captor.capture(), any(IntentFilter.class),
eq(Context.RECEIVER_EXPORTED_UNAUDITED))).thenReturn(null);
mShadowBluetoothAdapter.setState(BluetoothAdapter.STATE_ON);
verify(mSwitchController, never()).setChecked(anyBoolean());
mBluetoothEnabler.resume(mContext);
verify(mSwitchController, never()).setChecked(false);
when(mSwitchController.isChecked()).thenReturn(true);
// Now simulate bluetooth being turned off via an event.
BroadcastReceiver receiver = captor.getValue();
Intent turningOff = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
turningOff.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_TURNING_OFF);
receiver.onReceive(mContext, turningOff);
Intent off = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
off.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
receiver.onReceive(mContext, off);
// Make sure the switch was turned off.
verify(mSwitchController).setChecked(false);
}
@Test
public void bluetoothTurnsOn_switchTurnsOn() {
// Start up with bluetooth turned on. The switch should be left off.
ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
when(mContext.registerReceiver(captor.capture(), any(IntentFilter.class),
eq(Context.RECEIVER_EXPORTED_UNAUDITED))).thenReturn(null);
mShadowBluetoothAdapter.setState(BluetoothAdapter.STATE_OFF);
verify(mSwitchController, never()).setChecked(anyBoolean());
mBluetoothEnabler.resume(mContext);
verify(mSwitchController, never()).setChecked(anyBoolean());
// Now simulate bluetooth being turned on via an event.
BroadcastReceiver receiver = captor.getValue();
Intent turningOn = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
turningOn.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_TURNING_ON);
receiver.onReceive(mContext, turningOn);
Intent on = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
on.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
receiver.onReceive(mContext, on);
// Make sure the switch was turned on.
verify(mSwitchController).setChecked(true);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2018 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.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.net.Uri;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class BluetoothFeatureProviderImplTest {
private static final String SETTINGS_URI = "content://test.provider/settings_uri";
private static final String CONTROL_METADATA =
"<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + SETTINGS_URI
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private BluetoothFeatureProvider mBluetoothFeatureProvider;
@Mock
private BluetoothDevice mBluetoothDevice;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mBluetoothFeatureProvider = new BluetoothFeatureProviderImpl();
}
@Test
public void getBluetoothDeviceSettingsUri_containCorrectMacAddress() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI)).thenReturn(
SETTINGS_URI.getBytes());
final Uri uri = mBluetoothFeatureProvider.getBluetoothDeviceSettingsUri(mBluetoothDevice);
assertThat(uri.toString()).isEqualTo(SETTINGS_URI);
}
@Test
public void getBluetoothDeviceControlUri_returnsCorrectUri() {
when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)).thenReturn(
CONTROL_METADATA.getBytes());
assertThat(
mBluetoothFeatureProvider.getBluetoothDeviceControlUri(mBluetoothDevice)).isEqualTo(
SETTINGS_URI);
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2022 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.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
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.BluetoothLeBroadcastMetadata;
import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceCategory;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
com.android.settings.testutils.shadow.ShadowFragment.class,
})
public class BluetoothFindBroadcastsFragmentTest {
private static final String TEST_ADDRESS = "55:66:77:88:99:AA";
private BluetoothFindBroadcastsFragment mFragment;
private FragmentActivity mActivity;
private Context mContext;
private FragmentTransaction mFragmentTransaction;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CachedBluetoothDevice mCachedDevice;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private LocalBluetoothManager mLocalManager;
@Mock
private PreferenceCategory mPreferenceCategroy;
@Mock
private LocalBluetoothLeBroadcastAssistant mBroadcastAssistant;
@Mock
private BluetoothLeBroadcastMetadata mBroadcastMetadata;
@Mock
private BluetoothBroadcastSourcePreference mBroadcastSourcePreference;
@Mock
private BluetoothBroadcastSourcePreference mBroadcastSourcePreferenceUserClick;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
FakeFeatureFactory.setupForTest();
mFragment = spy(new BluetoothFindBroadcastsFragment());
doReturn(mLocalManager).when(mFragment).getLocalBluetoothManager(any());
doReturn(mCachedDevice).when(mFragment).getCachedDevice(any());
doReturn(mBroadcastAssistant).when(mFragment).getLeBroadcastAssistant();
doReturn(mPreferenceCategroy).when(mFragment).findPreference(any());
mActivity = Robolectric.setupActivity(FragmentActivity.class);
when(mFragment.getActivity()).thenReturn(mActivity);
FragmentManager fragmentManager = mock(FragmentManager.class);
when(mFragment.getFragmentManager()).thenReturn(fragmentManager);
mFragmentTransaction = mock(FragmentTransaction.class);
when(fragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
when(mCachedDevice.getIdentityAddress()).thenReturn(TEST_ADDRESS);
Bundle args = new Bundle();
args.putString(BluetoothFindBroadcastsFragment.KEY_DEVICE_ADDRESS, TEST_ADDRESS);
mFragment.setArguments(args);
mFragment.onAttach(mContext);
}
@Test
public void verifyOnAttachResult() {
assertThat(mFragment.mDeviceAddress).isEqualTo(TEST_ADDRESS);
assertThat(mFragment.mManager).isEqualTo(mLocalManager);
assertThat(mFragment.mCachedDevice).isEqualTo(mCachedDevice);
}
@Test
public void addSource_selectedPrefIsNull_returnsNewPref() {
mFragment.mSelectedPreference = null;
mFragment.addSource(mBroadcastSourcePreference);
assertThat(mFragment.mSelectedPreference).isEqualTo(mBroadcastSourcePreference);
}
@Test
public void addSource_sourcePrefIsCreatedByReceiveState_removesOldPref() {
mFragment.mSelectedPreference = mBroadcastSourcePreference;
mFragment.mBroadcastSourceListCategory = mPreferenceCategroy;
doReturn(true).when(mFragment.mSelectedPreference).isCreatedByReceiveState();
mFragment.addSource(mBroadcastSourcePreferenceUserClick);
verify(mFragment.mBroadcastSourceListCategory).removePreference(mBroadcastSourcePreference);
assertThat(mFragment.mSelectedPreference).isEqualTo(mBroadcastSourcePreferenceUserClick);
}
@Test
public void addSource_sourcePrefIsCreatedByMetadata_updatesOldPref() {
when(mBroadcastSourcePreference.isCreatedByReceiveState()).thenReturn(false);
when(mBroadcastSourcePreference.getBluetoothLeBroadcastMetadata())
.thenReturn(mBroadcastMetadata);
mFragment.mSelectedPreference = mBroadcastSourcePreference;
mFragment.mBroadcastSourceListCategory = mPreferenceCategroy;
mFragment.addSource(mBroadcastSourcePreferenceUserClick);
verify(mBroadcastSourcePreference).updateMetadataAndRefreshUi(
any(BluetoothLeBroadcastMetadata.class), anyBoolean());
assertThat(mFragment.mSelectedPreference).isEqualTo(mBroadcastSourcePreferenceUserClick);
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2018 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 android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class BluetoothNameDialogFragmentTest {
private TestBluetoothNameDialogFragment mBluetoothNameDialogFragment;
private TextView mTextView;
private static final String NAME_FOR_TEST = "test_device_name";
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mBluetoothNameDialogFragment = new TestBluetoothNameDialogFragment();
mTextView = new TextView(RuntimeEnvironment.application);
}
@Test
public void onEditorAction_dialogNull_shouldNotCrash() {
mBluetoothNameDialogFragment.mAlertDialog = null;
// Should not crash
assertThat(
mBluetoothNameDialogFragment.onEditorAction(mTextView, EditorInfo.IME_ACTION_DONE,
null)).isTrue();
}
@Test
public void onEditorAction_ImeNull_setsDeviceName() {
mTextView.setText(NAME_FOR_TEST);
assertThat(
mBluetoothNameDialogFragment.onEditorAction(mTextView, EditorInfo.IME_NULL,
null)).isTrue();
assertThat(mBluetoothNameDialogFragment.getDeviceName()).isEqualTo(NAME_FOR_TEST);
}
/**
* Test fragment for {@link BluetoothNameDialogFragment} to test common methods
*/
public static class TestBluetoothNameDialogFragment extends BluetoothNameDialogFragment {
private String mName;
@Override
protected int getDialogTitle() {
return 0;
}
@Override
protected String getDeviceName() {
return mName;
}
@Override
protected void setDeviceName(String deviceName) {
mName = deviceName;
}
@Override
public int getMetricsCategory() {
return 0;
}
}
}

View File

@@ -0,0 +1,281 @@
/*
* Copyright (C) 2018 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.bluetooth.BluetoothDevice.PAIRING_VARIANT_CONSENT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Parcel;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class,
ShadowDeviceConfig.class})
public class BluetoothPairingControllerTest {
private final BluetoothClass mBluetoothClass =
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
@Mock
private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
@Mock
private BluetoothDevice mBluetoothDevice;
@Mock
private CachedBluetoothDevice mCachedDevice;
@Mock
private LocalBluetoothProfile mLocalBluetoothProfile;
@Mock
private LocalBluetoothProfile mPbapLocalBluetoothProfile;
private Context mContext;
private BluetoothPairingController mBluetoothPairingController;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private BluetoothClass createBtClass(int deviceClass) {
Parcel p = Parcel.obtain();
p.writeInt(deviceClass);
p.setDataPosition(0); // reset position of parcel before passing to constructor
BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
p.recycle();
return bluetoothClass;
}
private BluetoothPairingController createBluetoothPairingController() {
final Intent intent = new Intent();
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
return new BluetoothPairingController(intent, mContext);
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice);
List<LocalBluetoothProfile> localBluetoothProfiles = new ArrayList<>();
mockIsLeAudio(false);
localBluetoothProfiles.add(mLocalBluetoothProfile);
when(mCachedDevice.getProfiles()).thenReturn(localBluetoothProfiles);
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
mBluetoothPairingController = createBluetoothPairingController();
mBluetoothPairingController.mockPbapClientProfile(mPbapLocalBluetoothProfile);
}
@Test
public void onDialogPositiveClick_confirmationDialog_setPBAP() {
mBluetoothPairingController.mType = PAIRING_VARIANT_CONSENT;
mBluetoothPairingController.onCheckedChanged(null, true);
mBluetoothPairingController.onDialogPositiveClick(null);
verify(mBluetoothDevice).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
}
@Test
public void onSetContactSharingState_permissionAllowed_setPBAPAllowed() {
when(mBluetoothDevice.getPhonebookAccessPermission()).thenReturn(
BluetoothDevice.ACCESS_ALLOWED);
mBluetoothPairingController.setContactSharingState();
mBluetoothPairingController.onDialogPositiveClick(null);
verify(mBluetoothDevice).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
}
@Test
public void onSetContactSharingState_permissionUnknown_audioVideoHandsfree_setPBAPAllowed() {
when(mBluetoothDevice.getPhonebookAccessPermission()).thenReturn(
BluetoothDevice.ACCESS_UNKNOWN);
when(mBluetoothDevice.getBluetoothClass()).thenReturn(mBluetoothClass);
mBluetoothPairingController.setContactSharingState();
mBluetoothPairingController.onDialogPositiveClick(null);
verify(mBluetoothDevice).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
}
@Test
public void onSetContactSharingState_permissionRejected_setPBAPRejected() {
when(mBluetoothDevice.getPhonebookAccessPermission()).thenReturn(
BluetoothDevice.ACCESS_REJECTED);
when(mBluetoothDevice.getBluetoothClass()).thenReturn(mBluetoothClass);
mBluetoothPairingController.setContactSharingState();
mBluetoothPairingController.onDialogPositiveClick(null);
verify(mBluetoothDevice).setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
}
@Test
public void isLeAudio_noLeProfile_returnsFalse() {
mockIsLeAudio(false);
mBluetoothPairingController = createBluetoothPairingController();
assertThat(mBluetoothPairingController.isLeAudio()).isFalse();
}
@Test
public void isLeAudio_isLeProfile_returnsTrue() {
mockIsLeAudio(true);
mBluetoothPairingController = createBluetoothPairingController();
assertThat(mBluetoothPairingController.isLeAudio()).isTrue();
}
@Test
public void isLeContactSharingEnabled_configIsFalse_returnsFalse() {
mockIsLeContactSharingEnabled(false);
mBluetoothPairingController = createBluetoothPairingController();
assertThat(mBluetoothPairingController.isLeContactSharingEnabled()).isFalse();
}
@Test
public void isLeContactSharingEnabled_configIsTrue_returnsTrue() {
mockIsLeContactSharingEnabled(true);
mBluetoothPairingController = createBluetoothPairingController();
assertThat(mBluetoothPairingController.isLeContactSharingEnabled()).isTrue();
}
@Test
public void isContactSharingVisible_profileIsNotReady_returnsTrue() {
// isProfileReady=false, isLeAudio=false, isLeContactSharingEnabled=true
mockIsProfileReady(false);
mockIsLeAudio(false);
mockIsLeContactSharingEnabled(true);
mBluetoothPairingController = createBluetoothPairingController();
mBluetoothPairingController.mockPbapClientProfile(mPbapLocalBluetoothProfile);
assertThat(mBluetoothPairingController.isContactSharingVisible()).isTrue();
}
@Test
public void isContactSharingVisible_profileIsReady_returnsFalse() {
// isProfileReady=true, isLeAudio=false, isLeContactSharingEnabled=true
mockIsProfileReady(true);
mockIsLeAudio(false);
mockIsLeContactSharingEnabled(true);
mBluetoothPairingController = createBluetoothPairingController();
mBluetoothPairingController.mockPbapClientProfile(mPbapLocalBluetoothProfile);
assertThat(mBluetoothPairingController.isContactSharingVisible()).isFalse();
}
@Test
public void isContactSharingVisible_DeviceIsLeAudioAndProfileIsReady_returnsFalse() {
// isProfileReady=true, isLeAudio=true, isLeContactSharingEnabled=true
mockIsProfileReady(true);
mockIsLeAudio(true);
mockIsLeContactSharingEnabled(true);
mBluetoothPairingController = createBluetoothPairingController();
mBluetoothPairingController.mockPbapClientProfile(mPbapLocalBluetoothProfile);
assertThat(mBluetoothPairingController.isContactSharingVisible()).isFalse();
}
@Test
public void isContactSharingVisible_DeviceIsLeAudioAndProfileIsNotReady_returnsTrue() {
// isProfileReady=false, isLeAudio=true, isLeContactSharingEnabled=true
mockIsProfileReady(false);
mockIsLeAudio(true);
mockIsLeContactSharingEnabled(true);
mBluetoothPairingController = createBluetoothPairingController();
mBluetoothPairingController.mockPbapClientProfile(mPbapLocalBluetoothProfile);
assertThat(mBluetoothPairingController.isContactSharingVisible()).isTrue();
}
@Test
public void isContactSharingVisible_DeviceIsLeAndContactSharingIsNotEnabled_returnsFalse() {
// isProfileReady=false, isLeAudio=true, isLeContactSharingEnabled=false
mockIsProfileReady(false);
mockIsLeAudio(true);
mockIsLeContactSharingEnabled(false);
mBluetoothPairingController = createBluetoothPairingController();
mBluetoothPairingController.mockPbapClientProfile(mPbapLocalBluetoothProfile);
assertThat(mBluetoothPairingController.isContactSharingVisible()).isFalse();
}
private void mockIsProfileReady(boolean mockValue) {
when(mPbapLocalBluetoothProfile.isProfileReady()).thenReturn(mockValue);
}
private void mockIsLeAudio(boolean mockValue) {
int profileId = BluetoothProfile.HEADSET;
if (mockValue) {
profileId = BluetoothProfile.LE_AUDIO;
}
when(mLocalBluetoothProfile.getProfileId()).thenReturn(profileId);
}
private void mockIsLeContactSharingEnabled(boolean mockValue) {
android.provider.DeviceConfig.setProperty(
android.provider.DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_LE_AUDIO_CONTACT_SHARING_ENABLED,
/* value= */ mockValue ? "true" : "false", true);
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.Mockito.doReturn;
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 android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
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.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import java.util.Collections;
@RunWith(RobolectricTestRunner.class)
public class BluetoothPairingDetailTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private final Lifecycle mFakeLifecycle = new Lifecycle() {
@Override
public void addObserver(@NonNull LifecycleObserver observer) {}
@Override
public void removeObserver(@NonNull LifecycleObserver observer) {}
@NonNull
@Override
public State getCurrentState() {
return State.CREATED;
}
};
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private LocalBluetoothManager mLocalManager;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CachedBluetoothDeviceManager mDeviceManager;
private BluetoothPairingDetail mFragment;
private BluetoothProgressCategory mAvailableDevicesCategory;
private FooterPreference mFooterPreference;
private BluetoothAdapter mBluetoothAdapter;
@Before
public void setUp() {
mFragment = spy(new BluetoothPairingDetail());
doReturn(mContext).when(mFragment).getContext();
mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
mFooterPreference = new FooterPreference(mContext);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
doReturn(mAvailableDevicesCategory).when(mFragment)
.findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
doReturn(mFooterPreference).when(mFragment)
.findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
doReturn(new View(mContext)).when(mFragment).getView();
doReturn((LifecycleOwner) () -> mFakeLifecycle).when(mFragment).getViewLifecycleOwner();
doReturn(Collections.emptyList()).when(mDeviceManager).getCachedDevicesCopy();
mFragment.mBluetoothAdapter = mBluetoothAdapter;
mFragment.mLocalManager = mLocalManager;
mFragment.mCachedDeviceManager = mDeviceManager;
mFragment.mDeviceListGroup = mAvailableDevicesCategory;
mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY);
}
@Test
public void initPreferencesFromPreferenceScreen_findPreferences() {
mFragment.initPreferencesFromPreferenceScreen();
assertThat(mFragment.mAvailableDevicesCategory).isEqualTo(mAvailableDevicesCategory);
assertThat(mFragment.mFooterPreference).isEqualTo(mFooterPreference);
}
@Test
public void updateContent_stateOn_addDevices() {
mFragment.initPreferencesFromPreferenceScreen();
mFragment.updateContent(BluetoothAdapter.STATE_ON);
assertThat(mFragment.mAlwaysDiscoverable.mStarted).isEqualTo(true);
assertThat(mBluetoothAdapter.getScanMode())
.isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
@Test
public void onScanningStateChanged_restartScanAfterInitialScanning() {
mFragment.initPreferencesFromPreferenceScreen();
// Initial Bluetooth ON will trigger scan enable, list clear and scan start
mFragment.updateContent(BluetoothAdapter.STATE_ON);
verify(mFragment).enableScanning();
assertThat(mAvailableDevicesCategory.getPreferenceCount()).isEqualTo(0);
verify(mFragment).startScanning();
// Subsequent scan started event will not trigger start/stop nor list clear
mFragment.onScanningStateChanged(true);
verify(mFragment, times(1)).startScanning();
verify(mAvailableDevicesCategory, times(1)).setProgress(true);
// Subsequent scan finished event will trigger scan start without list clean
mFragment.onScanningStateChanged(false);
verify(mFragment, times(2)).startScanning();
verify(mAvailableDevicesCategory, times(2)).setProgress(true);
// Subsequent scan started event will not trigger any change
mFragment.onScanningStateChanged(true);
verify(mFragment, times(2)).startScanning();
verify(mAvailableDevicesCategory, times(3)).setProgress(true);
verify(mFragment, never()).stopScanning();
// Disable scanning will trigger scan stop
mFragment.disableScanning();
verify(mFragment, times(1)).stopScanning();
// Subsequent scan start event will not trigger any change besides progress circle
mFragment.onScanningStateChanged(true);
verify(mAvailableDevicesCategory, times(4)).setProgress(true);
// However, subsequent scan finished event won't trigger new scan start and will stop
// progress circle from spinning
mFragment.onScanningStateChanged(false);
verify(mAvailableDevicesCategory, times(1)).setProgress(false);
verify(mFragment, times(2)).startScanning();
verify(mFragment, times(1)).stopScanning();
// Verify that clean up only happen once at initialization
verify(mAvailableDevicesCategory, times(1)).removeAll();
}
}

View File

@@ -0,0 +1,488 @@
/*
* Copyright (C) 2016 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.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Dialog;
import android.content.Context;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.androidx.fragment.FragmentController;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowAlertDialogCompat.class)
public class BluetoothPairingDialogTest {
private static final String FILLER = "text that goes in a view";
private static final String FAKE_DEVICE_NAME = "Fake Bluetooth Device";
@Mock
private BluetoothPairingController controller;
@Mock
private BluetoothPairingDialog dialogActivity;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doNothing().when(dialogActivity).dismiss();
}
@Test
public void dialogUpdatesControllerWithUserInput() {
// set the correct dialog type
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
// we don't care about these for this test
when(controller.getDeviceVariantMessageId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
when(controller.getDeviceVariantMessageHintId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
// build fragment
BluetoothPairingDialogFragment frag = makeFragment();
// test that controller is updated on text change
frag.afterTextChanged(new SpannableStringBuilder(FILLER));
verify(controller, times(1)).updateUserInput(any());
}
@Test
public void dialogEnablesSubmitButtonOnValidationFromController() {
// set the correct dialog type
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
// we don't care about these for this test
when(controller.getDeviceVariantMessageId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
when(controller.getDeviceVariantMessageHintId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
// force the controller to say that any passkey is valid
when(controller.isPasskeyValid(any())).thenReturn(true);
// build fragment
BluetoothPairingDialogFragment frag = makeFragment();
// test that the positive button is enabled when passkey is valid
frag.afterTextChanged(new SpannableStringBuilder(FILLER));
View button = frag.getmDialog().getButton(AlertDialog.BUTTON_POSITIVE);
assertThat(button).isNotNull();
assertThat(button.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void dialogDoesNotAskForPairCodeOnConsentVariant() {
// set the dialog variant to confirmation/consent
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// check that the input field used by the entry dialog fragment does not exist
View view = frag.getmDialog().findViewById(R.id.text);
assertThat(view).isNull();
}
@Ignore
@Test
public void dialogAsksForPairCodeOnUserEntryVariant() {
// set the dialog variant to user entry
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
// we don't care about these for this test
when(controller.getDeviceVariantMessageId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
when(controller.getDeviceVariantMessageHintId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
Context context = spy(RuntimeEnvironment.application);
InputMethodManager imm = mock(InputMethodManager.class);
doReturn(imm).when(context).getSystemService(Context.INPUT_METHOD_SERVICE);
// build the fragment
BluetoothPairingDialogFragment frag = spy(new BluetoothPairingDialogFragment());
when(frag.getContext()).thenReturn(context);
setupFragment(frag);
AlertDialog alertDialog = frag.getmDialog();
// check that the pin/passkey input field is visible to the user
View view = alertDialog.findViewById(R.id.text);
assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
// check that showSoftInput was called to make input method appear when the dialog was shown
assertThat(view.isFocused()).isTrue();
// TODO(b/73892004): Figure out why this is failing.
// assertThat(imm.isActive()).isTrue();
verify(imm).showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
@Test
public void dialogDisplaysPairCodeOnDisplayPasskeyVariant() {
// set the dialog variant to display passkey
when(controller.getDialogType())
.thenReturn(BluetoothPairingController.DISPLAY_PASSKEY_DIALOG);
// ensure that the controller returns good values to indicate a passkey needs to be shown
when(controller.isDisplayPairingKeyVariant()).thenReturn(true);
when(controller.hasPairingContent()).thenReturn(true);
when(controller.getPairingContent()).thenReturn(FILLER);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// get the relevant views
View messagePairing = frag.getmDialog().findViewById(R.id.pairing_code_message);
TextView pairingViewContent = frag.getmDialog().findViewById(R.id.pairing_subhead);
View pairingViewCaption = frag.getmDialog().findViewById(R.id.pairing_caption);
// check that the relevant views are visible and that the passkey is shown
assertThat(messagePairing.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(pairingViewCaption.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(pairingViewContent.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(TextUtils.equals(FILLER, pairingViewContent.getText())).isTrue();
}
@Test(expected = IllegalStateException.class)
public void dialogThrowsExceptionIfNoControllerSet() {
// instantiate a fragment
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
// this should throw an error
FragmentController.setupFragment(frag, FragmentActivity.class, 0 /* containerViewId */,
null /* bundle */);
fail("Starting the fragment with no controller set should have thrown an exception.");
}
@Test
public void dialogCallsHookOnPositiveButtonPress() {
// set the dialog variant to confirmation/consent
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
// we don't care what this does, just that it is called
doNothing().when(controller).onDialogPositiveClick(any());
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// click the button and verify that the controller hook was called
frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE);
verify(controller, times(1)).onDialogPositiveClick(any());
}
@Test
public void dialogCallsHookOnNegativeButtonPress() {
// set the dialog variant to confirmation/consent
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
// we don't care what this does, just that it is called
doNothing().when(controller).onDialogNegativeClick(any());
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// click the button and verify that the controller hook was called
frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE);
verify(controller, times(1)).onDialogNegativeClick(any());
}
@Test(expected = IllegalStateException.class)
public void dialogDoesNotAllowSwappingController() {
// instantiate a fragment
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
frag.setPairingController(controller);
// this should throw an error
frag.setPairingController(controller);
fail("Setting the controller multiple times should throw an exception.");
}
@Test(expected = IllegalStateException.class)
public void dialogDoesNotAllowSwappingActivity() {
// instantiate a fragment
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
frag.setPairingDialogActivity(dialogActivity);
// this should throw an error
frag.setPairingDialogActivity(dialogActivity);
fail("Setting the dialog activity multiple times should throw an exception.");
}
@Test
public void dialogPositiveButtonDisabledWhenUserInputInvalid() {
// set the correct dialog type
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
// we don't care about these for this test
when(controller.getDeviceVariantMessageId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
when(controller.getDeviceVariantMessageHintId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
// force the controller to say that any passkey is valid
when(controller.isPasskeyValid(any())).thenReturn(false);
// build fragment
BluetoothPairingDialogFragment frag = makeFragment();
// test that the positive button is enabled when passkey is valid
frag.afterTextChanged(new SpannableStringBuilder(FILLER));
View button = frag.getmDialog().getButton(AlertDialog.BUTTON_POSITIVE);
assertThat(button).isNotNull();
assertThat(button.isEnabled()).isFalse();
}
@Test
public void contactSharingToggle_conditionIsReady_showsUi() {
// set the dialog variant to confirmation/consent
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
// set a fake device name and pretend the profile has not been set up for it
when(controller.getDeviceName()).thenReturn(FAKE_DEVICE_NAME);
when(controller.isContactSharingVisible()).thenReturn(true);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// verify that the toggle is visible
View sharingToggle =
frag.getmDialog().findViewById(R.id.phonebook_sharing);
assertThat(sharingToggle.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void contactSharingToggle_conditionIsNotReady_doesNotShowUi() {
// set the dialog variant to confirmation/consent
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
// set a fake device name and pretend the profile has been set up for it
when(controller.getDeviceName()).thenReturn(FAKE_DEVICE_NAME);
when(controller.isContactSharingVisible()).thenReturn(false);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// verify that the toggle is gone
View sharingToggle =
frag.getmDialog().findViewById(R.id.phonebook_sharing);
assertThat(sharingToggle.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void dialogShowsMessageOnPinEntryView() {
// set the correct dialog type
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
// Set the message id to something specific to verify later
when(controller.getDeviceVariantMessageId()).thenReturn(R.string.cancel);
when(controller.getDeviceVariantMessageHintId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// verify message is what we expect it to be and is visible
TextView message = frag.getmDialog().findViewById(R.id.message_below_pin);
assertThat(message.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(TextUtils.equals(frag.getString(R.string.cancel), message.getText())).isTrue();
}
@Test
public void dialogShowsMessageHintOnPinEntryView() {
// set the correct dialog type
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
// Set the message id hint to something specific to verify later
when(controller.getDeviceVariantMessageHintId()).thenReturn(R.string.cancel);
when(controller.getDeviceVariantMessageId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// verify message is what we expect it to be and is visible
TextView hint = frag.getmDialog().findViewById(R.id.pin_values_hint);
assertThat(hint.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(TextUtils.equals(frag.getString(R.string.cancel), hint.getText())).isTrue();
}
@Test
public void dialogHidesMessageAndHintWhenNotProvidedOnPinEntryView() {
// set the correct dialog type
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
// Set the id's to what is returned when it is not provided
when(controller.getDeviceVariantMessageHintId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
when(controller.getDeviceVariantMessageId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// verify message is what we expect it to be and is visible
TextView hint = frag.getmDialog().findViewById(R.id.pin_values_hint);
assertThat(hint.getVisibility()).isEqualTo(View.GONE);
TextView message = frag.getmDialog().findViewById(R.id.message_below_pin);
assertThat(message.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void pairingDialogDismissedOnPositiveClick() {
// set the dialog variant to confirmation/consent
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
// we don't care what this does, just that it is called
doNothing().when(controller).onDialogPositiveClick(any());
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// click the button and verify that the controller hook was called
frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE);
verify(controller, times(1)).onDialogPositiveClick(any());
verify(dialogActivity, times(1)).dismiss();
}
@Test
public void pairingDialogDismissedOnNegativeClick() {
// set the dialog variant to confirmation/consent
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
// we don't care what this does, just that it is called
doNothing().when(controller).onDialogNegativeClick(any());
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// click the button and verify that the controller hook was called
frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE);
verify(controller, times(1)).onDialogNegativeClick(any());
verify(dialogActivity, times(1)).dismiss();
}
@Ignore
@Test
public void rotateDialog_nullPinText_okButtonEnabled() {
userEntryDialogExistingTextTest(null);
}
@Ignore
@Test
public void rotateDialog_emptyPinText_okButtonEnabled() {
userEntryDialogExistingTextTest("");
}
@Ignore
@Test
public void rotateDialog_nonEmptyPinText_okButtonEnabled() {
userEntryDialogExistingTextTest("test");
}
@Test
public void groupPairing_setMemberDevice_showsMessageHint() {
// set the correct dialog type
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
when(controller.isCoordinatedSetMemberDevice()).thenReturn(true);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// verify message is what we expect it to be and is visible
TextView message = frag.getmDialog().findViewById(R.id.pairing_group_message);
assertThat(message.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void groupPairing_nonSetMemberDevice_hidesMessageHint() {
// set the correct dialog type
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
when(controller.isCoordinatedSetMemberDevice()).thenReturn(false);
// build the fragment
BluetoothPairingDialogFragment frag = makeFragment();
// verify message is what we expect it to be and is visible
TextView message = frag.getmDialog().findViewById(R.id.pairing_group_message);
assertThat(message.getVisibility()).isEqualTo(View.GONE);
}
// Runs a test simulating the user entry dialog type in a situation like device rotation, where
// the dialog fragment gets created and we already have some existing text entered into the
// pin field.
private void userEntryDialogExistingTextTest(CharSequence existingText) {
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
when(controller.getDeviceVariantMessageHintId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
when(controller.getDeviceVariantMessageId())
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
BluetoothPairingDialogFragment fragment = spy(new BluetoothPairingDialogFragment());
when(fragment.getPairingViewText()).thenReturn(existingText);
setupFragment(fragment);
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
boolean expected = !TextUtils.isEmpty(existingText);
assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).isEnabled()).isEqualTo(expected);
}
private void setupFragment(BluetoothPairingDialogFragment frag) {
assertThat(frag.isPairingControllerSet()).isFalse();
frag.setPairingController(controller);
assertThat(frag.isPairingDialogActivitySet()).isFalse();
frag.setPairingDialogActivity(dialogActivity);
FragmentController.setupFragment(frag, FragmentActivity.class, 0 /* containerViewId */,
null /* bundle */);
assertThat(frag.getmDialog()).isNotNull();
assertThat(frag.isPairingControllerSet()).isTrue();
assertThat(frag.isPairingDialogActivitySet()).isTrue();
}
private BluetoothPairingDialogFragment makeFragment() {
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
setupFragment(frag);
return frag;
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.testutils.DrawableTestHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class BluetoothPairingPreferenceControllerTest {
private static final int ORDER = 1;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private DashboardFragment mFragment;
private Context mContext;
private Preference mPreference;
private BluetoothPairingPreferenceController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mFragment.getPreferenceScreen().getContext()).thenReturn(mContext);
mPreference = new Preference(mContext);
mPreference.setKey(BluetoothPairingPreferenceController.KEY_PAIRING);
mController = new BluetoothPairingPreferenceController(mContext, mFragment);
}
@Test
public void testCreateBluetoothPairingPreference() {
Preference pref = mController.createBluetoothPairingPreference(ORDER);
assertThat(pref.getKey()).isEqualTo(BluetoothPairingPreferenceController.KEY_PAIRING);
DrawableTestHelper.assertDrawableResId(pref.getIcon(), R.drawable.ic_add_24dp);
assertThat(pref.getOrder()).isEqualTo(ORDER);
assertThat(pref.getTitle())
.isEqualTo(mContext.getString(R.string.bluetooth_pairing_pref_title));
}
@Test
public void testHandlePreferenceTreeClick_startFragment() {
doNothing().when(mContext).startActivity(any(Intent.class));
mController.handlePreferenceTreeClick(mPreference);
verify(mContext).startActivity(any(Intent.class));
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2021 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.android.settings.bluetooth.BluetoothPairingService.ACTION_DISMISS_PAIRING;
import static com.android.settings.bluetooth.BluetoothPairingService.ACTION_PAIRING_DIALOG;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Application;
import android.app.NotificationManager;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import com.android.settings.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public class BluetoothPairingServiceTest {
private final String mFakeTicker = "fake_ticker";
@Mock
private NotificationManager mNm;
@Mock
private BluetoothDevice mDevice;
@Mock
private Context mContext;
@Mock
private Resources mResources;
@Mock
private PackageManager mPackageManager;
@Mock
private DisplayMetrics mDisplayMetrics;
private BluetoothPairingService mBluetoothPairingService;
private Application mApplication;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mBluetoothPairingService = new BluetoothPairingService();
mBluetoothPairingService.mNm = mNm;
mApplication = RuntimeEnvironment.application;
ReflectionHelpers.setField(mBluetoothPairingService, "mBase", mContext);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getString(R.string.bluetooth_notif_ticker)).thenReturn(mFakeTicker);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
mDisplayMetrics.density = 1.5f;
}
@Test
public void receivePairingRequestAction_notificationShown() {
Intent intent = new Intent();
intent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name");
when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0);
verify(mNm).notify(eq(mBluetoothPairingService.NOTIFICATION_ID), any());
}
@Test
public void receiveDismissPairingAction_cancelPairing() {
Intent intent = new Intent();
intent.setAction(ACTION_DISMISS_PAIRING);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name");
when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0);
verify(mDevice).cancelBondProcess();
verify(mNm).cancel(mBluetoothPairingService.NOTIFICATION_ID);
}
@Test
public void receivePairingDialogAction_startActivity() {
Intent intent = new Intent();
intent.setAction(ACTION_PAIRING_DIALOG);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name");
when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0);
verify(mContext).startActivity(any());
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2021 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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Process;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.util.ReflectionHelpers;
@Ignore("b/313014781")
@RunWith(RobolectricTestRunner.class)
public class BluetoothPermissionActivityTest {
private BluetoothPermissionActivity mActivity;
private Context mContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = mock(ContextWrapper.class);
mActivity = new BluetoothPermissionActivity();
}
@Test
public void sendBroadcastWithPermission() throws Exception {
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
ReflectionHelpers.setField(mActivity, "mBase", mContext);
when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
final String btPkgName = "com.android.bluetooth";
ActivityInfo btOppLauncherActivityInfo = new ActivityInfo();
btOppLauncherActivityInfo.name = "com.android.bluetooth.opp.BluetoothOppLauncherActivity";
PackageInfo btPkgInfo = new PackageInfo();
btPkgInfo.activities = new ActivityInfo[] {btOppLauncherActivityInfo};
PackageManager pm = mock(PackageManager.class);
when(pm.getPackagesForUid(Process.BLUETOOTH_UID)).thenReturn(new String[] {btPkgName});
when(pm.getPackageInfo(eq(btPkgName), anyInt())).thenReturn(btPkgInfo);
when(mContext.getPackageManager()).thenReturn(pm);
mActivity.sendReplyIntentToReceiver(true, true);
verify(mContext).sendBroadcast(intentCaptor.capture(),
eq("android.permission.BLUETOOTH_CONNECT"));
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2018 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 android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.SliceMetadata;
import androidx.slice.SliceProvider;
import androidx.slice.core.SliceAction;
import androidx.slice.widget.SliceLiveData;
import com.android.settings.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class BluetoothSliceBuilderTest {
private Context mContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
// Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
}
@Test
public void getBluetoothSlice_correctSliceContent() {
final Slice BluetoothSlice = BluetoothSliceBuilder.getSlice(mContext);
final SliceMetadata metadata = SliceMetadata.from(mContext, BluetoothSlice);
assertThat(metadata.getTitle()).isEqualTo(
mContext.getString(R.string.bluetooth_settings_title));
final List<SliceAction> toggles = metadata.getToggles();
assertThat(toggles).hasSize(1);
final SliceAction primaryAction = metadata.getPrimaryAction();
final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext,
com.android.internal.R.drawable.ic_settings_bluetooth);
assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString());
}
@Test
public void handleUriChange_updatesBluetooth() {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
final Intent intent = new Intent();
intent.putExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, true);
adapter.disable()/* enabled */;
BluetoothSliceBuilder.handleUriChange(mContext, intent);
assertThat(adapter.isEnabled()).isTrue();
}
}

View File

@@ -0,0 +1,313 @@
/*
* 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.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import java.util.HashSet;
import java.util.Set;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class BluetoothSummaryUpdaterTest {
private static final String DEVICE_NAME = "Nightshade";
private static final String DEVICE_KEYBOARD_NAME = "Bluetooth Keyboard";
private Context mContext;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private LocalBluetoothManager mBluetoothManager;
@Mock
private BluetoothDevice mConnectedDevice;
@Mock
private BluetoothDevice mConnectedKeyBoardDevice;
@Mock
private SummaryListener mListener;
// Disabled by default
private final boolean[] mAdapterEnabled = {false};
// Not connected by default
private final int[] mAdapterConnectionState = {BluetoothAdapter.STATE_DISCONNECTED};
// Not connected by default
private final boolean[] mDeviceConnected = {false, false};
private final Set<BluetoothDevice> mBondedDevices = new HashSet<>();
private BluetoothSummaryUpdater mSummaryUpdater;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application.getApplicationContext();
doCallRealMethod().when(mListener).onSummaryChanged(anyString());
mSummaryUpdater = new BluetoothSummaryUpdater(mContext, mListener, mBluetoothManager);
// Setup first device
doReturn(DEVICE_NAME).when(mConnectedDevice).getName();
doAnswer(invocation -> mDeviceConnected[0]).when(mConnectedDevice).isConnected();
// Setup second device
doReturn(DEVICE_KEYBOARD_NAME).when(mConnectedKeyBoardDevice).getName();
doAnswer(invocation -> mDeviceConnected[1]).when(mConnectedKeyBoardDevice).isConnected();
// Setup BluetoothAdapter
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(mAdapterEnabled[0]);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mShadowBluetoothAdapter.setConnectionState(mAdapterConnectionState[0]);
}
@Test
public void register_true_shouldRegisterListener() {
mSummaryUpdater.register(true);
verify(mBluetoothManager.getEventManager()).registerCallback(mSummaryUpdater);
}
@Test
public void register_false_shouldUnregisterListener() {
mSummaryUpdater.register(false);
verify(mBluetoothManager.getEventManager()).unregisterCallback(mSummaryUpdater);
}
@Test
public void register_true_shouldSendSummaryChange() {
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mDeviceConnected[0] = true;
mSummaryUpdater.register(true);
verify(mListener).onSummaryChanged(
mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
}
@Test
public void onBluetoothStateChanged_btDisabled_shouldSendDisabledSummary() {
// These states should be ignored
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mDeviceConnected[0] = true;
mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
}
@Test
public void onBluetoothStateChanged_btEnabled_connected_shouldSendConnectedSummary() {
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mDeviceConnected[0] = true;
mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
verify(mListener).onSummaryChanged(
mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
}
@Test
public void onBluetoothStateChanged_btEnabled_connectedMisMatch_shouldSendNotConnected() {
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
// State mismatch
mDeviceConnected[0] = false;
mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
}
@Test
public void onBluetoothStateChanged_btEnabled_notConnected_shouldSendDisconnectedMessage() {
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_DISCONNECTED);
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
// This should be ignored
mDeviceConnected[0] = true;
mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
}
@Test
public void onBluetoothStateChanged_ConnectedDisabledEnabled_shouldSendDisconnectedSummary() {
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_DISCONNECTED);
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mDeviceConnected[0] = false;
mSummaryUpdater.register(true);
verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
mDeviceConnected[0] = true;
mSummaryUpdater.onConnectionStateChanged(null /* device */,
BluetoothAdapter.STATE_CONNECTED);
verify(mListener).onSummaryChanged(
mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
mShadowBluetoothAdapter.setEnabled(false);
mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
// Turning ON means not enabled
mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
// There should still be only one invocation of disabled message
verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
mShadowBluetoothAdapter.setEnabled(true);
mDeviceConnected[0] = false;
mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
verify(mListener, times(2)).onSummaryChanged(mContext.getString(R.string.disconnected));
verify(mListener, times(4)).onSummaryChanged(anyString());
}
@Test
public void onConnectionStateChanged_connected_shouldSendConnectedMessage() {
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mDeviceConnected[0] = true;
mSummaryUpdater.onConnectionStateChanged(null /* device */,
BluetoothAdapter.STATE_CONNECTED);
verify(mListener).onSummaryChanged(
mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
}
@Test
public void onConnectionStateChanged_inconsistentState_shouldSendDisconnectedMessage() {
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_DISCONNECTED);
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mDeviceConnected[0] = false;
mSummaryUpdater.onConnectionStateChanged(null /* device */,
BluetoothAdapter.STATE_CONNECTED);
verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
}
@Test
public void onConnectionStateChanged_noBondedDevice_shouldSendDisconnectedMessage() {
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
mSummaryUpdater.onConnectionStateChanged(null /* device */,
BluetoothAdapter.STATE_CONNECTED);
verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
}
@Test
public void onConnectionStateChanged_connecting_shouldSendConnectingMessage() {
// No need for bonded devices
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTING);
mSummaryUpdater.onConnectionStateChanged(null /* device */,
BluetoothAdapter.STATE_CONNECTING);
verify(mListener).onSummaryChanged(
mContext.getString(com.android.settingslib.R.string.bluetooth_connecting));
}
@Test
public void onConnectionStateChanged_disconnecting_shouldSendDisconnectingMessage() {
// No need for bonded devices
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_DISCONNECTING);
mSummaryUpdater.onConnectionStateChanged(null /* device */,
BluetoothAdapter.STATE_DISCONNECTING);
verify(mListener).onSummaryChanged(
mContext.getString(com.android.settingslib.R.string.bluetooth_disconnecting));
}
@Test
public void getConnectedDeviceSummary_hasConnectedDevice_returnOneDeviceSummary() {
mBondedDevices.add(mConnectedDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mDeviceConnected[0] = true;
final String expectedSummary =
mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME);
assertThat(mSummaryUpdater.getConnectedDeviceSummary()).isEqualTo(expectedSummary);
}
@Test
public void getConnectedDeviceSummary_multipleDevices_returnMultipleDevicesSummary() {
mBondedDevices.add(mConnectedDevice);
mBondedDevices.add(mConnectedKeyBoardDevice);
mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
mDeviceConnected[0] = true;
mDeviceConnected[1] = true;
final String expectedSummary =
mContext.getString(R.string.bluetooth_connected_multiple_devices_summary);
assertThat(mSummaryUpdater.getConnectedDeviceSummary()).isEqualTo(expectedSummary);
}
private class SummaryListener implements OnSummaryChangeListener {
String summary;
@Override
public void onSummaryChanged(String summary) {
this.summary = summary;
}
}
}

View File

@@ -0,0 +1,143 @@
/*
* 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.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.provider.Settings;
import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.utils.AnnotationSpan;
import com.android.settings.widget.SwitchWidgetController;
import com.android.settingslib.widget.FooterPreference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class BluetoothSwitchPreferenceControllerTest {
private static final String BLUETOOTH_INFO_STRING = "When Bluetooth is turned on, your device"
+ " can communicate with other nearby Bluetooth devices";
@Mock
private RestrictionUtils mRestrictionUtils;
@Mock
private SwitchWidgetController mSwitchController;
@Mock
private AlwaysDiscoverable mAlwaysDiscoverable;
private FooterPreference mFooterPreference;
private Context mContext;
private BluetoothSwitchPreferenceController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application.getApplicationContext());
mFooterPreference = new FooterPreference(mContext);
FakeFeatureFactory.setupForTest();
mController =
new BluetoothSwitchPreferenceController(mContext, mRestrictionUtils,
mSwitchController, mFooterPreference);
mController.mAlwaysDiscoverable = mAlwaysDiscoverable;
}
@Test
public void updateText_bluetoothOffScanningOn() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 1);
mController.updateText(false);
AnnotationSpan.LinkInfo info = new AnnotationSpan.LinkInfo(
AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, mController);
CharSequence text = AnnotationSpan.linkify(
mContext.getText(R.string.bluetooth_scanning_on_info_message), info);
assertThat(TextUtils.equals(mFooterPreference.getTitle(), text)).isTrue();
}
@Test
public void updateText_bluetoothOffScanningOff() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
mController.updateText(false);
assertThat(mFooterPreference.getTitle()).isEqualTo(BLUETOOTH_INFO_STRING);
}
@Test
public void updateText_bluetoothOnScanningOff() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
mController.updateText(true);
assertThat(mFooterPreference.getTitle()).isEqualTo(BLUETOOTH_INFO_STRING);
}
@Test
public void updateText_bluetoothOnScanningOn() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 1);
mController.updateText(true);
assertThat(mFooterPreference.getTitle()).isEqualTo(BLUETOOTH_INFO_STRING);
}
@Test
public void onStart_setAlwaysDiscoverableAsTrue_shouldStartAlwaysDiscoverable() {
mController.setAlwaysDiscoverable(true);
mController.onStart();
verify(mAlwaysDiscoverable).start();
}
@Test
public void onStart_setAlwaysDiscoverableAsFalse_shouldStartAlwaysDiscoverable() {
mController.setAlwaysDiscoverable(false);
mController.onStart();
verify(mAlwaysDiscoverable, never()).start();
}
@Test
public void onStop_setAlwaysDiscoverableAsTrue_shouldStopAlwaysDiscoverable() {
mController.setAlwaysDiscoverable(true);
mController.onStop();
verify(mAlwaysDiscoverable).stop();
}
@Test
public void onStop__setAlwaysDiscoverableAsFalse_shouldStopAlwaysDiscoverable() {
mController.setAlwaysDiscoverable(false);
mController.onStop();
verify(mAlwaysDiscoverable, never()).stop();
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2021 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.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.ContextThemeWrapper;
import android.view.View;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAlertDialogCompat.class})
public class CompanionAppWidgetPreferenceTest {
private static final String TITLE_ONE = "Test Title 1";
private static final String TITLE_TWO = "Test Title 1";
private static final String KEY_ONE = "Test Key 1";
private static final String KEY_TWO = "Test Key 1";
private Context mContext;
private Drawable mWidgetIconOne;
private Drawable mWidgetIconTwo;
private Drawable mAppIconOne;
private Drawable mAppIconTwo;
@Mock
private View.OnClickListener mWidgetListenerOne;
@Mock
private View.OnClickListener mWidgetListenerTwo;
private List<CompanionAppWidgetPreference> mPreferenceContainer;
@Before
public void setUp() {
mPreferenceContainer = new ArrayList<>();
Context context = spy(RuntimeEnvironment.application.getApplicationContext());
mContext = new ContextThemeWrapper(context, R.style.Theme_Settings);
mWidgetIconOne = mock(Drawable.class);
mAppIconOne = mock(Drawable.class);
mWidgetListenerOne = mock(View.OnClickListener.class);
mWidgetIconTwo = mock(Drawable.class);
mAppIconTwo = mock(Drawable.class);
mWidgetListenerTwo = mock(View.OnClickListener.class);
}
private void setUpPreferenceContainer(Drawable widgetIcon, Drawable appIcon,
View.OnClickListener listener, String title, String key) {
CompanionAppWidgetPreference preference = new CompanionAppWidgetPreference(
widgetIcon, listener, mContext);
preference.setIcon(appIcon);
preference.setTitle(title);
preference.setKey(key);
mPreferenceContainer.add(preference);
}
@Test
public void setUpPreferenceContainer_preferenceShouldBeAdded() {
setUpPreferenceContainer(
mWidgetIconOne, mAppIconOne, mWidgetListenerOne, TITLE_ONE, KEY_ONE);
assertThat(mPreferenceContainer.get(0).getIcon()).isEqualTo(mAppIconOne);
assertThat(mPreferenceContainer.get(0).getKey()).isEqualTo(KEY_ONE);
assertThat(mPreferenceContainer.get(0).getTitle()).isEqualTo(TITLE_ONE);
setUpPreferenceContainer(
mWidgetIconTwo, mAppIconTwo, mWidgetListenerTwo, TITLE_TWO, KEY_TWO);
assertThat(mPreferenceContainer.get(1).getIcon()).isEqualTo(mAppIconTwo);
assertThat(mPreferenceContainer.get(1).getKey()).isEqualTo(KEY_TWO);
assertThat(mPreferenceContainer.get(1).getTitle()).isEqualTo(TITLE_TWO);
}
}

View File

@@ -0,0 +1,433 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
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.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Pair;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.testutils.shadow.ShadowAudioManager;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowCachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.Collection;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAudioManager.class, ShadowBluetoothAdapter.class,
ShadowCachedBluetoothDeviceManager.class})
public class ConnectedBluetoothDeviceUpdaterTest {
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private DashboardFragment mDashboardFragment;
@Mock
private DevicePreferenceCallback mDevicePreferenceCallback;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private BluetoothDevice mBluetoothDevice;
@Mock
private Drawable mDrawable;
@Mock
private PackageManager mPackageManager;
private Context mContext;
private ConnectedBluetoothDeviceUpdater mBluetoothDeviceUpdater;
private Collection<CachedBluetoothDevice> mCachedDevices;
private AudioManager mAudioManager;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private ShadowCachedBluetoothDeviceManager mShadowCachedBluetoothDeviceManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
mContext = spy(RuntimeEnvironment.application);
mAudioManager = mContext.getSystemService(AudioManager.class);
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowCachedBluetoothDeviceManager = Shadow.extract(
Utils.getLocalBtManager(mContext).getCachedDeviceManager());
doReturn(mContext).when(mDashboardFragment).getContext();
mCachedDevices = new ArrayList<>();
mCachedDevices.add(mCachedBluetoothDevice);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
mShadowCachedBluetoothDeviceManager.setCachedDevicesCopy(mCachedDevices);
mBluetoothDeviceUpdater = spy(new ConnectedBluetoothDeviceUpdater(mContext,
mDevicePreferenceCallback, /* metricsCategory= */ 0));
mBluetoothDeviceUpdater.setPrefContext(mContext);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
}
@Test
public void onAudioModeChanged_hfpDeviceConnected_notInCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onAudioModeChanged();
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onAudioModeChanged_hfpDeviceConnected_inCall_removePreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onAudioModeChanged();
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onAudioModeChanged_a2dpDeviceConnected_notInCall_removePreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onAudioModeChanged();
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onAudioModeChanged_a2dpDeviceConnected_inCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onAudioModeChanged();
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_a2dpDeviceConnected_inCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovePreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mCachedDevices.clear();
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_a2dpDeviceConnected_notInCall_removePreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_hfpDeviceConnected_inCall_removePreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_hfpDeviceConnected_notInCall_addPreference() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_ashaHearingAidConnected_inCall_removePreference()
{
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_ashaHearingAidConnected_notInCall_removePreference()
{
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_leAudioDeviceConnected_inCall_removesPreference() {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_leAudioDeviceConnected_notInCall_removesPreference()
{
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovesPreference()
{
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
mCachedDevices.clear();
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_notInCall_invokesRemovesPreference
() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
mCachedDevices.clear();
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() {
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void addPreference_addPreference_shouldHideSecondTarget() {
BluetoothDevicePreference btPreference =
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
true, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, btPreference);
mBluetoothDeviceUpdater.addPreference(mCachedBluetoothDevice);
assertThat(btPreference.shouldHideSecondTarget()).isTrue();
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_notExclusiveManagedDevice_addDevice() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
null);
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_notAllowedExclusiveManagedDevice_addDevice() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_existingExclusivelyManagedDeviceWithPackageInstalled_removePreference()
throws Exception {
final String exclusiveManagerName =
BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
FAKE_EXCLUSIVE_MANAGER_NAME);
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
exclusiveManagerName.getBytes());
doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_newExclusivelyManagedDeviceWithPackageInstalled_doNotAddPreference()
throws Exception {
final String exclusiveManagerName =
BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
FAKE_EXCLUSIVE_MANAGER_NAME);
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
exclusiveManagerName.getBytes());
doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_exclusivelyManagedDeviceWithoutPackageInstalled_addDevice()
throws Exception {
final String exclusiveManagerName =
BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
FAKE_EXCLUSIVE_MANAGER_NAME);
mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
exclusiveManagerName.getBytes());
doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
exclusiveManagerName, 0);
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
}

View File

@@ -0,0 +1,263 @@
/*
* 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 android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothUuid
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.content.Context
import android.content.res.Resources
import androidx.preference.Preference
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.CachedBluetoothDeviceManager
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Spy
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.mockito.Mockito.`when` as whenever
@RunWith(RobolectricTestRunner::class)
@Config(shadows = [
ShadowBluetoothAdapter::class,
com.android.settings.testutils.shadow.ShadowFragment::class,
])
class DeviceListPreferenceFragmentTest {
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
private lateinit var resource: Resources
@Mock
private lateinit var context: Context
@Mock
private lateinit var bluetoothLeScanner: BluetoothLeScanner
@Mock
private lateinit var cachedDeviceManager: CachedBluetoothDeviceManager
@Mock
private lateinit var cachedDevice: CachedBluetoothDevice
@Spy
private var fragment = TestFragment()
private lateinit var myDevicePreference: Preference
private lateinit var bluetoothAdapter: BluetoothAdapter
@Before
fun setUp() {
doReturn(context).`when`(fragment).context
doReturn(resource).`when`(fragment).resources
doNothing().`when`(fragment).onDeviceAdded(cachedDevice)
bluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter())
fragment.mBluetoothAdapter = bluetoothAdapter
fragment.mCachedDeviceManager = cachedDeviceManager
myDevicePreference = Preference(RuntimeEnvironment.application)
}
@Test
fun setUpdateMyDevicePreference_setTitleCorrectly() {
doReturn(FOOTAGE_MAC_STRING).`when`(fragment)
.getString(eq(R.string.bluetooth_footer_mac_message), any())
fragment.updateFooterPreference(myDevicePreference)
assertThat(myDevicePreference.title).isEqualTo(FOOTAGE_MAC_STRING)
}
@Test
fun testEnableDisableScanning_testStateAfterEnableDisable() {
fragment.enableScanning()
verify(fragment).startScanning()
assertThat(fragment.mScanEnabled).isTrue()
fragment.disableScanning()
verify(fragment).stopScanning()
assertThat(fragment.mScanEnabled).isFalse()
}
@Test
fun testScanningStateChanged_testScanStarted() {
fragment.enableScanning()
assertThat(fragment.mScanEnabled).isTrue()
verify(fragment).startScanning()
fragment.onScanningStateChanged(true)
verify(fragment, times(1)).startScanning()
}
@Test
fun testScanningStateChanged_testScanFinished() {
// Could happen when last scanning not done while current scan gets enabled
fragment.enableScanning()
verify(fragment).startScanning()
assertThat(fragment.mScanEnabled).isTrue()
fragment.onScanningStateChanged(false)
verify(fragment, times(2)).startScanning()
}
@Test
fun testScanningStateChanged_testScanStateMultiple() {
// Could happen when last scanning not done while current scan gets enabled
fragment.enableScanning()
assertThat(fragment.mScanEnabled).isTrue()
verify(fragment).startScanning()
fragment.onScanningStateChanged(true)
verify(fragment, times(1)).startScanning()
fragment.onScanningStateChanged(false)
verify(fragment, times(2)).startScanning()
fragment.onScanningStateChanged(true)
verify(fragment, times(2)).startScanning()
fragment.disableScanning()
verify(fragment).stopScanning()
fragment.onScanningStateChanged(false)
verify(fragment, times(2)).startScanning()
fragment.onScanningStateChanged(true)
verify(fragment, times(2)).startScanning()
}
@Test
fun testScanningStateChanged_testScanFinishedAfterDisable() {
fragment.enableScanning()
verify(fragment).startScanning()
assertThat(fragment.mScanEnabled).isTrue()
fragment.disableScanning()
verify(fragment).stopScanning()
assertThat(fragment.mScanEnabled).isFalse()
fragment.onScanningStateChanged(false)
verify(fragment, times(1)).startScanning()
}
@Test
fun testScanningStateChanged_testScanStartedAfterDisable() {
fragment.enableScanning()
verify(fragment).startScanning()
assertThat(fragment.mScanEnabled).isTrue()
fragment.disableScanning()
verify(fragment).stopScanning()
assertThat(fragment.mScanEnabled).isFalse()
fragment.onScanningStateChanged(true)
verify(fragment, times(1)).startScanning()
}
@Test
fun startScanning_setLeScanFilter_shouldStartLeScan() {
val leScanFilter = ScanFilter.Builder()
.setServiceData(BluetoothUuid.HEARING_AID, byteArrayOf(0), byteArrayOf(0))
.build()
doReturn(bluetoothLeScanner).`when`(bluetoothAdapter).bluetoothLeScanner
fragment.setFilter(listOf(leScanFilter))
fragment.startScanning()
verify(bluetoothLeScanner).startScan(eq(listOf(leScanFilter)), any(), any<ScanCallback>())
}
@Test
fun addCachedDevices_whenFilterIsNull_onDeviceAddedIsCalled() = runBlocking {
val mockCachedDevice = mock(CachedBluetoothDevice::class.java)
whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(mockCachedDevice))
fragment.lifecycleScope = this
fragment.addCachedDevices(filterForCachedDevices = null)
delay(100)
verify(fragment).onDeviceAdded(mockCachedDevice)
}
@Test
fun addCachedDevices_whenFilterMatched_onDeviceAddedIsCalled() = runBlocking {
val mockBluetoothDevice = mock(BluetoothDevice::class.java)
whenever(mockBluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
whenever(cachedDevice.device).thenReturn(mockBluetoothDevice)
whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(cachedDevice))
fragment.lifecycleScope = this
fragment.addCachedDevices(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER)
delay(100)
verify(fragment).onDeviceAdded(cachedDevice)
}
@Test
fun addCachedDevices_whenFilterNoMatch_onDeviceAddedNotCalled() = runBlocking {
val mockBluetoothDevice = mock(BluetoothDevice::class.java)
whenever(mockBluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
whenever(cachedDevice.device).thenReturn(mockBluetoothDevice)
whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(cachedDevice))
fragment.lifecycleScope = this
fragment.addCachedDevices(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER)
delay(100)
verify(fragment, never()).onDeviceAdded(cachedDevice)
}
/**
* Fragment to test since `DeviceListPreferenceFragment` is abstract
*/
open class TestFragment : DeviceListPreferenceFragment(null) {
override fun getMetricsCategory() = 0
override fun initPreferencesFromPreferenceScreen() {}
override val deviceListKey = "device_list"
override fun getLogTag() = null
override fun getPreferenceScreenResId() = 0
}
private companion object {
const val FOOTAGE_MAC_STRING = "Bluetooth mac: xxxx"
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class DevicePickerFragmentTest {
@Mock
private BluetoothProgressCategory mAvailableDevicesCategory;
private DevicePickerFragment mFragment;
private Context mContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mFragment = new DevicePickerFragment();
mContext = spy(RuntimeEnvironment.application);
mFragment.mContext = mContext;
mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory;
}
@Test
public void testScanningStateChanged_started_setProgressStarted() {
mFragment.mScanEnabled = true;
mFragment.onScanningStateChanged(true);
verify(mAvailableDevicesCategory).setProgress(true);
}
@Test
public void callingPackageIsEqualToLaunchPackage_sendBroadcastToLaunchPackage() {
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
mFragment.mSelectedDevice = bluetoothDevice;
mFragment.mLaunchPackage = "com.android.settings";
mFragment.mLaunchClass = "com.android.settings.bluetooth.BluetoothPermissionActivity";
mFragment.mCallingAppPackageName = "com.android.settings";
mFragment.onDeviceBondStateChanged(cachedDevice, BluetoothDevice.BOND_BONDED);
verify(mContext).sendBroadcast(intentCaptor.capture(),
eq("android.permission.BLUETOOTH_CONNECT"));
assertThat(intentCaptor.getValue().getComponent().getPackageName())
.isEqualTo(mFragment.mLaunchPackage);
}
@Test
public void callingPackageIsNotEqualToLaunchPackage_broadcastNotSend() {
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
mFragment.mSelectedDevice = bluetoothDevice;
mFragment.mLaunchPackage = "com.fake.settings";
mFragment.mLaunchClass = "com.android.settings.bluetooth.BluetoothPermissionActivity";
mFragment.mCallingAppPackageName = "com.android.settings";
mFragment.onDeviceBondStateChanged(cachedDevice, BluetoothDevice.BOND_BONDED);
verify(mContext, never()).sendBroadcast(intentCaptor.capture());
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowDialog;
import org.robolectric.shadows.androidx.fragment.FragmentController;
@Ignore
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAlertDialogCompat.class})
public class ForgetDeviceDialogFragmentTest {
private static final String DEVICE_NAME = "Nightshade";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CachedBluetoothDevice mCachedDevice;
@Mock
private BluetoothDevice mBluetoothDevice;
private ForgetDeviceDialogFragment mFragment;
private FragmentActivity mActivity;
private AlertDialog mDialog;
private Context mContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
FakeFeatureFactory.setupForTest();
String deviceAddress = "55:66:77:88:99:AA";
when(mCachedDevice.getAddress()).thenReturn(deviceAddress);
when(mCachedDevice.getIdentityAddress()).thenReturn(deviceAddress);
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
mFragment = spy(ForgetDeviceDialogFragment.newInstance(deviceAddress));
doReturn(mCachedDevice).when(mFragment).getDevice(any());
mActivity = Robolectric.setupActivity(FragmentActivity.class);
}
@Test
public void cancelDialog() {
initDialog();
mDialog.getButton(AlertDialog.BUTTON_NEGATIVE).performClick();
verify(mCachedDevice, never()).unpair();
assertThat(mActivity.isFinishing()).isFalse();
}
@Test
public void confirmDialog() {
initDialog();
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
verify(mCachedDevice).unpair();
assertThat(mActivity.isFinishing()).isTrue();
}
@Test
public void createDialog_normalDevice_showNormalMessage() {
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn("false".getBytes());
FragmentController.setupFragment(mFragment, FragmentActivity.class,
0 /* containerViewId */, null /* bundle */);
final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
assertThat(shadowDialog.getMessage()).isEqualTo(
mContext.getString(R.string.bluetooth_unpair_dialog_body, DEVICE_NAME));
}
private void initDialog() {
mActivity.getSupportFragmentManager().beginTransaction().add(mFragment, null).commit();
mDialog = (AlertDialog) ShadowDialog.getLatestDialog();
}
}

View File

@@ -0,0 +1,138 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.DialogInterface;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowDialog;
import org.robolectric.shadows.androidx.fragment.FragmentController;
@RunWith(RobolectricTestRunner.class)
public class RemoteDeviceNameDialogFragmentTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CachedBluetoothDevice mCachedDevice;
private RemoteDeviceNameDialogFragment mFragment;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest();
String deviceAddress = "55:66:77:88:99:AA";
when(mCachedDevice.getAddress()).thenReturn(deviceAddress);
when(mCachedDevice.getIdentityAddress()).thenReturn(deviceAddress);
mFragment = spy(RemoteDeviceNameDialogFragment.newInstance(mCachedDevice));
doReturn(mCachedDevice).when(mFragment).getDevice(any());
}
/**
* Helper method to set the mock device's name and show the dialog.
*
* @param deviceName what name to set
* @return the dialog created
*/
AlertDialog startDialog(String deviceName) {
when(mCachedDevice.getName()).thenReturn(deviceName);
FragmentController.setupFragment(mFragment, FragmentActivity.class, 0 /* containerViewId */,
null /* bundle */);
return (AlertDialog) ShadowDialog.getLatestDialog();
}
@Ignore
@Test
public void deviceNameDisplayIsCorrect() {
String deviceName = "ABC Corp Headphones";
AlertDialog dialog = startDialog(deviceName);
EditText editText = dialog.findViewById(R.id.edittext);
assertThat(editText.getText().toString()).isEqualTo(deviceName);
// Make sure that the "rename" button isn't enabled since the text hasn't changed yet, but
// the "cancel" button should be enabled.
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
assertThat(positiveButton.isEnabled()).isFalse();
Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
assertThat(negativeButton.isEnabled()).isTrue();
}
@Ignore
@Test
public void deviceNameEditSucceeds() {
String deviceNameInitial = "ABC Corp Headphones";
String deviceNameModified = "My Headphones";
AlertDialog dialog = startDialog(deviceNameInitial);
// Before modifying the text the "rename" button should be disabled but the cancel button
// should be enabled.
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
assertThat(negativeButton.isEnabled()).isTrue();
assertThat(positiveButton.isEnabled()).isFalse();
// Once we modify the text, the positive button should be clickable, and clicking it should
// cause a call to change the name.
EditText editText = dialog.findViewById(R.id.edittext);
editText.setText(deviceNameModified);
assertThat(positiveButton.isEnabled()).isTrue();
positiveButton.performClick();
verify(mCachedDevice).setName(deviceNameModified);
}
@Ignore
@Test
public void deviceNameEditThenCancelDoesntRename() {
String deviceNameInitial = "ABC Corp Headphones";
String deviceNameModified = "My Headphones";
AlertDialog dialog = startDialog(deviceNameInitial);
// Modifying the text but then hitting cancel should not cause the name to change.
Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
assertThat(negativeButton.isEnabled()).isTrue();
EditText editText = dialog.findViewById(R.id.edittext);
editText.setText(deviceNameModified);
negativeButton.performClick();
verify(mCachedDevice, never()).setName(anyString());
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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 android.bluetooth.BluetoothAdapter
import android.content.Intent
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.android.controller.ActivityController
import org.robolectric.annotation.Config
import org.robolectric.shadow.api.Shadow
import org.robolectric.shadows.ShadowBluetoothAdapter
@RunWith(RobolectricTestRunner::class)
@Config(shadows = [ShadowAlertDialogCompat::class, ShadowBluetoothAdapter::class])
class RequestPermissionActivityTest {
private lateinit var activityController: ActivityController<RequestPermissionActivity>
private lateinit var bluetoothAdapter: ShadowBluetoothAdapter
@Before
fun setUp() {
bluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter())
}
@After
fun tearDown() {
activityController.pause().stop().destroy()
ShadowAlertDialogCompat.reset()
}
@Test
fun requestEnable_whenBluetoothIsOff_showConfirmDialog() {
bluetoothAdapter.setState(BluetoothAdapter.STATE_OFF)
createActivity(action = BluetoothAdapter.ACTION_REQUEST_ENABLE)
val dialog = ShadowAlertDialogCompat.getLatestAlertDialog()
val shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog)
assertThat(shadowDialog.message.toString())
.isEqualTo("An app wants to turn on Bluetooth")
}
@Test
fun requestEnable_whenBluetoothIsOn_doNothing() {
bluetoothAdapter.setState(BluetoothAdapter.STATE_ON)
createActivity(action = BluetoothAdapter.ACTION_REQUEST_ENABLE)
val dialog = ShadowAlertDialogCompat.getLatestAlertDialog()
assertThat(dialog).isNull()
}
@Test
fun requestDisable_whenBluetoothIsOff_doNothing() {
bluetoothAdapter.setState(BluetoothAdapter.STATE_OFF)
createActivity(action = BluetoothAdapter.ACTION_REQUEST_DISABLE)
val dialog = ShadowAlertDialogCompat.getLatestAlertDialog()
assertThat(dialog).isNull()
}
@Test
fun requestDisable_whenBluetoothIsOn_showConfirmDialog() {
bluetoothAdapter.setState(BluetoothAdapter.STATE_ON)
createActivity(action = BluetoothAdapter.ACTION_REQUEST_DISABLE)
val dialog = ShadowAlertDialogCompat.getLatestAlertDialog()
val shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog)
assertThat(shadowDialog.message.toString())
.isEqualTo("An app wants to turn off Bluetooth")
}
private fun createActivity(action: String) {
activityController =
ActivityController.of(RequestPermissionActivity(), Intent(action)).apply {
create()
start()
postCreate(null)
resume()
}
}
}

View File

@@ -0,0 +1,255 @@
/*
* 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 androidx.activity.ComponentActivity
import com.android.settings.R
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.spy
import org.robolectric.RobolectricTestRunner
import org.robolectric.android.controller.ActivityController
import org.robolectric.annotation.Config
import org.mockito.Mockito.`when` as whenever
@RunWith(RobolectricTestRunner::class)
@Config(shadows = [ShadowAlertDialogCompat::class])
class RequestPermissionHelperTest {
private lateinit var activityController: ActivityController<ComponentActivity>
@Before
fun setUp() {
activityController =
ActivityController.of(ComponentActivity()).create().start().postCreate(null).resume()
}
@After
fun tearDown() {
activityController.pause().stop().destroy()
}
@Test
fun requestEnable_withAppLabelAndNoTimeout_hasCorrectMessage() {
val activity = activityController.get()
RequestPermissionHelper.requestEnable(
context = activity,
appLabel = "App Label",
timeout = -1,
onAllow = {},
onDeny = {},
)!!.show()
assertLatestMessageIs("App Label wants to turn on Bluetooth")
}
@Test
fun requestEnable_withAppLabelAndZeroTimeout_hasCorrectMessage() {
val activity = activityController.get()
RequestPermissionHelper.requestEnable(
context = activity,
appLabel = "App Label",
timeout = 0,
onAllow = {},
onDeny = {},
)!!.show()
assertLatestMessageIs(
"App Label wants to turn on Bluetooth and make your phone visible to other devices. " +
"You can change this later in Bluetooth settings."
)
}
@Test
fun requestEnable_withAppLabelAndNormalTimeout_hasCorrectMessage() {
val activity = activityController.get()
RequestPermissionHelper.requestEnable(
context = activity,
appLabel = "App Label",
timeout = 120,
onAllow = {},
onDeny = {},
)!!.show()
assertLatestMessageIs(
"App Label wants to turn on Bluetooth and make your phone visible to other devices " +
"for 120 seconds."
)
}
@Test
fun requestEnable_withNoAppLabelAndNoTimeout_hasCorrectMessage() {
val activity = activityController.get()
RequestPermissionHelper.requestEnable(
context = activity,
appLabel = null,
timeout = -1,
onAllow = {},
onDeny = {},
)!!.show()
assertLatestMessageIs("An app wants to turn on Bluetooth")
}
@Test
fun requestEnable_withNoAppLabelAndZeroTimeout_hasCorrectMessage() {
val activity = activityController.get()
RequestPermissionHelper.requestEnable(
context = activity,
appLabel = null,
timeout = 0,
onAllow = {},
onDeny = {},
)!!.show()
assertLatestMessageIs(
"An app wants to turn on Bluetooth and make your phone visible to other devices. " +
"You can change this later in Bluetooth settings."
)
}
@Test
fun requestEnable_withNoAppLabelAndNormalTimeout_hasCorrectMessage() {
val activity = activityController.get()
RequestPermissionHelper.requestEnable(
context = activity,
appLabel = null,
timeout = 120,
onAllow = {},
onDeny = {},
)!!.show()
assertLatestMessageIs(
"An app wants to turn on Bluetooth and make your phone visible to other devices for " +
"120 seconds."
)
}
@Test
fun requestEnable_whenAutoConfirm_onAllowIsCalled() {
val activity = getActivityWith(autoConfirm = true)
var onAllowCalled = false
RequestPermissionHelper.requestEnable(
context = activity,
appLabel = null,
timeout = -1,
onAllow = { onAllowCalled = true },
onDeny = {},
)
assertThat(onAllowCalled).isTrue()
}
@Test
fun requestEnable_whenNotAutoConfirm_onAllowIsNotCalledWhenRequest() {
val activity = getActivityWith(autoConfirm = false)
var onAllowCalled = false
RequestPermissionHelper.requestEnable(
context = activity,
appLabel = null,
timeout = -1,
onAllow = { onAllowCalled = true },
onDeny = {},
)
assertThat(onAllowCalled).isFalse()
}
@Test
fun requestDisable_withAppLabel_hasCorrectMessage() {
val activity = activityController.get()
RequestPermissionHelper.requestDisable(
context = activity,
appLabel = "App Label",
onAllow = {},
onDeny = {},
)!!.show()
assertLatestMessageIs("App Label wants to turn off Bluetooth")
}
@Test
fun requestDisable_withNoAppLabel_hasCorrectMessage() {
val activity = activityController.get()
RequestPermissionHelper.requestDisable(
context = activity,
appLabel = null,
onAllow = {},
onDeny = {},
)!!.show()
assertLatestMessageIs("An app wants to turn off Bluetooth")
}
@Test
fun requestDisable_whenAutoConfirm_onAllowIsCalled() {
val activity = getActivityWith(autoConfirm = true)
var onAllowCalled = false
RequestPermissionHelper.requestDisable(
context = activity,
appLabel = null,
onAllow = { onAllowCalled = true },
onDeny = {},
)
assertThat(onAllowCalled).isTrue()
}
@Test
fun requestDisable_whenNotAutoConfirm_onAllowIsNotCalledWhenRequest() {
val activity = getActivityWith(autoConfirm = false)
var onAllowCalled = false
RequestPermissionHelper.requestDisable(
context = activity,
appLabel = null,
onAllow = { onAllowCalled = true },
onDeny = {},
)
assertThat(onAllowCalled).isFalse()
}
private fun getActivityWith(autoConfirm: Boolean): ComponentActivity {
val activity = spy(activityController.get())
val spyResources = spy(activity.resources)
whenever(activity.resources).thenReturn(spyResources)
whenever(spyResources.getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog))
.thenReturn(autoConfirm)
return activity
}
private fun assertLatestMessageIs(message: String) {
val dialog = ShadowAlertDialogCompat.getLatestAlertDialog()
val shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog)
assertThat(shadowDialog.message.toString()).isEqualTo(message)
}
}

View File

@@ -0,0 +1,440 @@
/*
* 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.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
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.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Pair;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class SavedBluetoothDeviceUpdaterTest {
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private DashboardFragment mDashboardFragment;
@Mock
private DevicePreferenceCallback mDevicePreferenceCallback;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private BluetoothDevice mBluetoothDevice;
@Mock
private BluetoothAdapter mBluetoothAdapter;
@Mock
private CachedBluetoothDeviceManager mDeviceManager;
@Mock
private LocalBluetoothManager mBluetoothManager;
@Mock
private Drawable mDrawable;
@Mock
private PackageManager mPackageManager;
private Context mContext;
private SavedBluetoothDeviceUpdater mBluetoothDeviceUpdater;
private BluetoothDevicePreference mPreference;
private List<CachedBluetoothDevice> mCachedDevices = new ArrayList<>();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
mContext = spy(RuntimeEnvironment.application);
doReturn(mContext).when(mDashboardFragment).getContext();
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
mBluetoothDeviceUpdater = spy(new SavedBluetoothDeviceUpdater(mContext,
mDevicePreferenceCallback, false, /* metricsCategory= */ 0));
mBluetoothDeviceUpdater.setPrefContext(mContext);
mBluetoothDeviceUpdater.mBluetoothAdapter = mBluetoothAdapter;
mBluetoothDeviceUpdater.mLocalManager = mBluetoothManager;
mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
false, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
mCachedDevices.add(mCachedBluetoothDevice);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_filterMatch_addPreference() {
doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState();
doReturn(false).when(mBluetoothDevice).isConnected();
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_filterNotMatch_removePreference() {
doReturn(BluetoothDevice.BOND_NONE).when(mBluetoothDevice).getBondState();
doReturn(true).when(mBluetoothDevice).isConnected();
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceConnected_removePreference() {
when(mBluetoothDevice.isConnected()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_addPreference() {
when(mBluetoothDevice.isConnected()).thenReturn(false);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
public void
onProfileConnectionStateChanged_leDeviceDisconnected_inDeviceList_invokesAddPreference()
{
when(mBluetoothDevice.isConnected()).thenReturn(false);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
public void
onProfileConnectionStateChanged_deviceDisconnected_notInDeviceList_invokesRemovePreference() {
when(mBluetoothDevice.isConnected()).thenReturn(false);
mCachedDevices.clear();
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void onClick_Preference_setConnect() {
mBluetoothDeviceUpdater.onPreferenceClick(mPreference);
verify(mCachedBluetoothDevice).connect();
}
@Test
public void onClick_Preference_connected_setActive() {
when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
mBluetoothDeviceUpdater.onPreferenceClick(mPreference);
verify(mCachedBluetoothDevice).setActive();
}
@Test
public void forceUpdate_findCachedBluetoothDeviceIsMatched_addPreference() {
final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
bluetoothDevices.add(mBluetoothDevice);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothAdapter.getMostRecentlyConnectedDevices()).thenReturn(bluetoothDevices);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
mBluetoothDeviceUpdater.forceUpdate();
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
public void forceUpdate_findCachedBluetoothDeviceNotMatched_removePreference() {
final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
bluetoothDevices.add(mBluetoothDevice);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothAdapter.getMostRecentlyConnectedDevices()).thenReturn(bluetoothDevices);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
mBluetoothDeviceUpdater.forceUpdate();
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
@Test
public void forceUpdate_notFindCachedBluetoothDevice_doNothing() {
final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
bluetoothDevices.add(mBluetoothDevice);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothAdapter.getMostRecentlyConnectedDevices()).thenReturn(bluetoothDevices);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null);
mBluetoothDeviceUpdater.forceUpdate();
verify(mBluetoothDeviceUpdater, never()).removePreference(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
public void forceUpdate_bluetoothAdapterNotEnable_removeAllDevicesFromPreference() {
final Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
cachedDevices.add(mCachedBluetoothDevice);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
when(mBluetoothAdapter.isEnabled()).thenReturn(false);
mBluetoothDeviceUpdater.forceUpdate();
verify(mBluetoothDeviceUpdater).removeAllDevicesFromPreference();
}
@Test
public void forceUpdate_deviceNotContain_removePreference() {
final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
bluetoothDevices.add(mBluetoothDevice);
final BluetoothDevice device2 = mock(BluetoothDevice.class);
final CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class);
mBluetoothDeviceUpdater.mPreferenceMap.put(device2, mPreference);
when(cachedDevice2.getDevice()).thenReturn(device2);
when(cachedDevice2.getAddress()).thenReturn("04:52:C7:0B:D8:3S");
when(mDeviceManager.findDevice(device2)).thenReturn(cachedDevice2);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothAdapter.getMostRecentlyConnectedDevices()).thenReturn(bluetoothDevices);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
mBluetoothDeviceUpdater.forceUpdate();
verify(mBluetoothDeviceUpdater).removePreference(cachedDevice2);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
public void forceUpdate_deviceIsSubDevice_doesNothing() {
final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
bluetoothDevices.add(mBluetoothDevice);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothAdapter.getMostRecentlyConnectedDevices()).thenReturn(bluetoothDevices);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
when(mDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
mBluetoothDeviceUpdater.forceUpdate();
verify(mBluetoothDeviceUpdater, never()).removePreference(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_notExclusivelyManagedDevice_addDevice() {
final Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
cachedDevices.add(mCachedBluetoothDevice);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
null);
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_notAllowedExclusivelyManagedDevice_addDevice() {
final Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
cachedDevices.add(mCachedBluetoothDevice);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_existingExclusivelyManagedDeviceWithPackageInstalled_removePreference()
throws Exception {
final Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
final String exclusiveManagerName =
BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
FAKE_EXCLUSIVE_MANAGER_NAME);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
exclusiveManagerName.getBytes());
doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference);
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_newExclusivelyManagedDeviceWithPackageInstalled_doNotAddPreference()
throws Exception {
final Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
final String exclusiveManagerName =
BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
FAKE_EXCLUSIVE_MANAGER_NAME);
cachedDevices.add(mCachedBluetoothDevice);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
exclusiveManagerName.getBytes());
doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_exclusivelyManagedDeviceWithoutPackageInstalled_addDevice()
throws Exception {
final Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
final String exclusiveManagerName =
BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
FAKE_EXCLUSIVE_MANAGER_NAME);
cachedDevices.add(mCachedBluetoothDevice);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
exclusiveManagerName.getBytes());
doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
exclusiveManagerName, 0);
mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
BluetoothDevicePreference.SortType.TYPE_NO_SORT);
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class UtilsTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private MetricsFeatureProvider mMetricsFeatureProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mMetricsFeatureProvider = FakeFeatureFactory.setupForTest().getMetricsFeatureProvider();
}
@Test
public void showConnectingError_shouldLogBluetoothConnectError() {
when(mContext.getString(anyInt(), anyString())).thenReturn("testMessage");
Utils.showConnectingError(mContext, "testName", mock(LocalBluetoothManager.class));
verify(mMetricsFeatureProvider).visible(eq(mContext), anyInt(),
eq(MetricsEvent.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR), anyInt());
}
}