1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.settings.qc; 18 19 import static android.os.UserManager.DISALLOW_BLUETOOTH; 20 21 import static com.android.car.qc.QCItem.QC_ACTION_TOGGLE_STATE; 22 import static com.android.car.settings.qc.PairedBluetoothDevices.BLUETOOTH_BUTTON; 23 import static com.android.car.settings.qc.PairedBluetoothDevices.EXTRA_BUTTON_TYPE; 24 import static com.android.car.settings.qc.PairedBluetoothDevices.EXTRA_DEVICE_KEY; 25 import static com.android.car.settings.qc.PairedBluetoothDevices.MEDIA_BUTTON; 26 import static com.android.car.settings.qc.PairedBluetoothDevices.PHONE_BUTTON; 27 28 import static com.google.common.truth.Truth.assertThat; 29 30 import static org.mockito.ArgumentMatchers.any; 31 import static org.mockito.ArgumentMatchers.anyBoolean; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.mock; 34 import static org.mockito.Mockito.verify; 35 import static org.mockito.Mockito.when; 36 37 import android.bluetooth.BluetoothAdapter; 38 import android.bluetooth.BluetoothClass; 39 import android.bluetooth.BluetoothDevice; 40 import android.bluetooth.BluetoothProfile; 41 import android.content.Intent; 42 import android.os.UserManager; 43 44 import androidx.test.ext.junit.runners.AndroidJUnit4; 45 46 import com.android.car.qc.QCActionItem; 47 import com.android.car.qc.QCItem; 48 import com.android.car.qc.QCList; 49 import com.android.car.qc.QCRow; 50 import com.android.car.settings.R; 51 import com.android.dx.mockito.inline.extended.ExtendedMockito; 52 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 53 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 54 import com.android.settingslib.bluetooth.LocalBluetoothAdapter; 55 import com.android.settingslib.bluetooth.LocalBluetoothManager; 56 import com.android.settingslib.bluetooth.LocalBluetoothProfile; 57 58 import org.junit.After; 59 import org.junit.Before; 60 import org.junit.Test; 61 import org.junit.runner.RunWith; 62 import org.mockito.Mock; 63 import org.mockito.MockitoAnnotations; 64 import org.mockito.MockitoSession; 65 import org.mockito.quality.Strictness; 66 67 import java.util.ArrayList; 68 import java.util.Arrays; 69 import java.util.HashSet; 70 import java.util.Set; 71 72 @RunWith(AndroidJUnit4.class) 73 public class PairedBluetoothDevicesTest extends BaseSettingsQCItemTestCase { 74 private static final String DEFAULT_NAME = "DEFAULT_NAME"; 75 private static final String DEFAULT_SUMMARY = "DEFAULT_SUMMARY"; 76 private static final String DEFAULT_ADDRESS = "F6:8F:AC:E8:32:50"; 77 78 private PairedBluetoothDevices mPairedBluetoothDevices; 79 private MockitoSession mSession; 80 private ArrayList<CachedBluetoothDevice> mCachedDevices = new ArrayList<>(); 81 private Set<BluetoothDevice> mBondedDevices = new HashSet<>(); 82 83 @Mock 84 private BluetoothAdapter mBluetoothAdapter; 85 @Mock 86 private LocalBluetoothAdapter mLocalBluetoothAdapter; 87 @Mock 88 private LocalBluetoothManager mBluetoothManager; 89 90 @Before setUp()91 public void setUp() { 92 MockitoAnnotations.initMocks(this); 93 mSession = ExtendedMockito.mockitoSession() 94 .strictness(Strictness.LENIENT) 95 .mockStatic(BluetoothAdapter.class) 96 .mockStatic(LocalBluetoothManager.class) 97 .startMocking(); 98 when(BluetoothAdapter.getDefaultAdapter()).thenReturn(mBluetoothAdapter); 99 when(mBluetoothAdapter.isEnabled()).thenReturn(true); 100 when(LocalBluetoothManager.getInstance(any(), any())).thenReturn(mBluetoothManager); 101 when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter); 102 when(mLocalBluetoothAdapter.getBondedDevices()).thenReturn(mBondedDevices); 103 104 CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class); 105 when(mBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager); 106 when(deviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices); 107 108 mPairedBluetoothDevices = new PairedBluetoothDevices(mContext); 109 } 110 111 @After tearDown()112 public void tearDown() { 113 if (mSession != null) { 114 mSession.finishMocking(); 115 } 116 } 117 118 @Test getQCItem_bluetoothDisallowed_returnsNull()119 public void getQCItem_bluetoothDisallowed_returnsNull() { 120 setUserRestriction(DISALLOW_BLUETOOTH, true); 121 QCItem item = mPairedBluetoothDevices.getQCItem(); 122 assertThat(item).isNull(); 123 } 124 125 @Test getQCItem_bluetoothDisabled_returnsBluetoothDisabledMessage()126 public void getQCItem_bluetoothDisabled_returnsBluetoothDisabledMessage() { 127 when(mBluetoothAdapter.isEnabled()).thenReturn(false); 128 QCItem item = mPairedBluetoothDevices.getQCItem(); 129 assertThat(item).isNotNull(); 130 QCList list = (QCList) item; 131 assertThat(list.getRows().size()).isEqualTo(1); 132 QCRow row = list.getRows().get(0); 133 assertThat(row.getTitle()).isEqualTo( 134 mContext.getString(R.string.qc_bluetooth_off_devices_info)); 135 assertThat(row.getStartIcon()).isNotNull(); 136 } 137 138 @Test getQCItem_noDevices_returnsPairMessage()139 public void getQCItem_noDevices_returnsPairMessage() { 140 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 141 assertThat(list.getRows().size()).isEqualTo(1); 142 QCRow row = list.getRows().get(0); 143 assertThat(row.getTitle()).isEqualTo( 144 mContext.getString(R.string.qc_bluetooth_on_no_devices_info)); 145 assertThat(row.getStartIcon()).isNotNull(); 146 } 147 148 @Test getQCItem_hasDevices_hasRows()149 public void getQCItem_hasDevices_hasRows() { 150 addBluetoothDevice("Device1", /* connected= */ true, /* busy= */ false, 151 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 152 addBluetoothDevice("Device2", /* connected= */ true, /* busy= */ false, 153 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 154 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 155 assertThat(list.getRows().size()).isEqualTo(2); 156 } 157 158 @Test getQCItem_limitsDeviceCount()159 public void getQCItem_limitsDeviceCount() { 160 addBluetoothDevice("Device1", /* connected= */ true, /* busy= */ false, 161 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 162 addBluetoothDevice("Device2", /* connected= */ false, /* busy= */ false, 163 /* phoneEnabled= */ false, /* mediaEnabled= */ false); 164 addBluetoothDevice("Device3", /* connected= */ false, /* busy= */ false, 165 /* phoneEnabled= */ false, /* mediaEnabled= */ false); 166 addBluetoothDevice("Device4", /* connected= */ false, /* busy= */ false, 167 /* phoneEnabled= */ false, /* mediaEnabled= */ false); 168 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 169 assertThat(list.getRows().size()).isEqualTo(3); 170 } 171 172 @Test getQCItem_setsTitle()173 public void getQCItem_setsTitle() { 174 addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false, 175 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 176 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 177 QCRow row = list.getRows().get(0); 178 assertThat(row.getTitle()).isEqualTo(DEFAULT_NAME); 179 } 180 181 @Test getQCItem_bluetoothDisabled_togglesUpdated()182 public void getQCItem_bluetoothDisabled_togglesUpdated() { 183 addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ false, 184 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 185 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 186 QCRow row = list.getRows().get(0); 187 QCActionItem btToggle = row.getEndItems().get(0); 188 QCActionItem phoneToggle = row.getEndItems().get(1); 189 QCActionItem mediaToggle = row.getEndItems().get(2); 190 assertThat(btToggle.isChecked()).isFalse(); 191 assertThat(btToggle.isEnabled()).isTrue(); 192 assertThat(phoneToggle.isAvailable()).isFalse(); 193 assertThat(mediaToggle.isAvailable()).isFalse(); 194 } 195 196 @Test getQCItem_bluetoothEnabled_togglesUpdated()197 public void getQCItem_bluetoothEnabled_togglesUpdated() { 198 addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false, 199 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 200 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 201 QCRow row = list.getRows().get(0); 202 QCActionItem btToggle = row.getEndItems().get(0); 203 QCActionItem phoneToggle = row.getEndItems().get(1); 204 QCActionItem mediaToggle = row.getEndItems().get(2); 205 assertThat(btToggle.isChecked()).isTrue(); 206 assertThat(btToggle.isEnabled()).isTrue(); 207 assertThat(phoneToggle.isChecked()).isTrue(); 208 assertThat(phoneToggle.isEnabled()).isTrue(); 209 assertThat(phoneToggle.isAvailable()).isTrue(); 210 assertThat(mediaToggle.isChecked()).isTrue(); 211 assertThat(mediaToggle.isEnabled()).isTrue(); 212 assertThat(mediaToggle.isAvailable()).isTrue(); 213 } 214 215 @Test getQCItem_isBusy_togglesDisabled()216 public void getQCItem_isBusy_togglesDisabled() { 217 addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ true, 218 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 219 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 220 QCRow row = list.getRows().get(0); 221 QCActionItem btToggle = row.getEndItems().get(0); 222 QCActionItem phoneToggle = row.getEndItems().get(1); 223 QCActionItem mediaToggle = row.getEndItems().get(2); 224 assertThat(btToggle.isEnabled()).isFalse(); 225 assertThat(phoneToggle.isEnabled()).isFalse(); 226 assertThat(mediaToggle.isEnabled()).isFalse(); 227 } 228 229 @Test getQCItem_hasBaseUmRestriction_togglesDisabled()230 public void getQCItem_hasBaseUmRestriction_togglesDisabled() { 231 addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false, 232 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 233 setBaseUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* restricted= */ true); 234 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 235 QCRow row = list.getRows().get(0); 236 QCActionItem btToggle = row.getEndItems().get(0); 237 QCActionItem phoneToggle = row.getEndItems().get(1); 238 QCActionItem mediaToggle = row.getEndItems().get(2); 239 assertThat(btToggle.isEnabled()).isTrue(); 240 assertThat(phoneToggle.isEnabled()).isFalse(); 241 assertThat(phoneToggle.isClickableWhileDisabled()).isFalse(); 242 assertThat(mediaToggle.isEnabled()).isFalse(); 243 assertThat(mediaToggle.isClickableWhileDisabled()).isFalse(); 244 } 245 246 @Test getQCItem_hasUmRestriction_togglesClickableWhileDisabled()247 public void getQCItem_hasUmRestriction_togglesClickableWhileDisabled() { 248 addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false, 249 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 250 setUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* restricted= */ true); 251 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 252 QCRow row = list.getRows().get(0); 253 QCActionItem btToggle = row.getEndItems().get(0); 254 QCActionItem phoneToggle = row.getEndItems().get(1); 255 QCActionItem mediaToggle = row.getEndItems().get(2); 256 assertThat(btToggle.isEnabled()).isTrue(); 257 assertThat(phoneToggle.isEnabled()).isFalse(); 258 assertThat(phoneToggle.isClickableWhileDisabled()).isTrue(); 259 assertThat(mediaToggle.isEnabled()).isFalse(); 260 assertThat(mediaToggle.isClickableWhileDisabled()).isTrue(); 261 } 262 263 @Test onNotifyChange_toggleBluetooth()264 public void onNotifyChange_toggleBluetooth() { 265 addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true, 266 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 267 Intent intent = new Intent(); 268 intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS); 269 intent.putExtra(EXTRA_BUTTON_TYPE, BLUETOOTH_BUTTON); 270 intent.putExtra(QC_ACTION_TOGGLE_STATE, true); 271 mPairedBluetoothDevices.onNotifyChange(intent); 272 CachedBluetoothDevice device = mCachedDevices.get(0); 273 verify(device).connect(); 274 } 275 276 @Test onNotifyChange_togglePhone()277 public void onNotifyChange_togglePhone() { 278 addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true, 279 /* phoneEnabled= */ false, /* mediaEnabled= */ true); 280 Intent intent = new Intent(); 281 intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS); 282 intent.putExtra(EXTRA_BUTTON_TYPE, PHONE_BUTTON); 283 intent.putExtra(QC_ACTION_TOGGLE_STATE, true); 284 mPairedBluetoothDevices.onNotifyChange(intent); 285 CachedBluetoothDevice device = mCachedDevices.get(0); 286 LocalBluetoothProfile profile = getProfile(device, BluetoothProfile.HEADSET_CLIENT); 287 verify(profile).setEnabled(any(), eq(true)); 288 } 289 290 @Test onNotifyChange_toggleMedia()291 public void onNotifyChange_toggleMedia() { 292 addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true, 293 /* phoneEnabled= */ true, /* mediaEnabled= */ false); 294 Intent intent = new Intent(); 295 intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS); 296 intent.putExtra(EXTRA_BUTTON_TYPE, MEDIA_BUTTON); 297 intent.putExtra(QC_ACTION_TOGGLE_STATE, true); 298 mPairedBluetoothDevices.onNotifyChange(intent); 299 CachedBluetoothDevice device = mCachedDevices.get(0); 300 LocalBluetoothProfile profile = getProfile(device, BluetoothProfile.A2DP_SINK); 301 verify(profile).setEnabled(any(), eq(true)); 302 } 303 addBluetoothDevice(String name, boolean connected, boolean busy, boolean phoneEnabled, boolean mediaEnabled)304 private void addBluetoothDevice(String name, boolean connected, boolean busy, 305 boolean phoneEnabled, boolean mediaEnabled) { 306 CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); 307 BluetoothDevice device = mock(BluetoothDevice.class); 308 when(cachedDevice.getDevice()).thenReturn(device); 309 when(device.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); 310 when(cachedDevice.getName()).thenReturn(name); 311 when(cachedDevice.getAddress()).thenReturn(DEFAULT_ADDRESS); 312 when(cachedDevice.isConnected()).thenReturn(connected); 313 when(cachedDevice.isBusy()).thenReturn(busy); 314 when(cachedDevice.getCarConnectionSummary(anyBoolean())).thenReturn(DEFAULT_SUMMARY); 315 BluetoothClass btClass = mock(BluetoothClass.class); 316 when(cachedDevice.getBtClass()).thenReturn(btClass); 317 when(btClass.getMajorDeviceClass()).thenReturn(BluetoothClass.Device.Major.PHONE); 318 LocalBluetoothProfile phoneProfile = mock(LocalBluetoothProfile.class); 319 LocalBluetoothProfile mediaProfile = mock(LocalBluetoothProfile.class); 320 when(phoneProfile.getProfileId()).thenReturn(BluetoothProfile.HEADSET_CLIENT); 321 when(mediaProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP_SINK); 322 when(phoneProfile.isEnabled(any())).thenReturn(phoneEnabled); 323 when(mediaProfile.isEnabled(any())).thenReturn(mediaEnabled); 324 when(cachedDevice.getProfiles()).thenReturn(Arrays.asList(phoneProfile, mediaProfile)); 325 mCachedDevices.add(cachedDevice); 326 mBondedDevices.add(device); 327 } 328 getProfile(CachedBluetoothDevice device, int profileId)329 private LocalBluetoothProfile getProfile(CachedBluetoothDevice device, int profileId) { 330 for (LocalBluetoothProfile profile : device.getProfiles()) { 331 if (profile.getProfileId() == profileId) { 332 return profile; 333 } 334 } 335 return null; 336 } 337 } 338