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.eq; 32 import static org.mockito.Mockito.mock; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.when; 35 36 import android.bluetooth.BluetoothAdapter; 37 import android.bluetooth.BluetoothClass; 38 import android.bluetooth.BluetoothDevice; 39 import android.bluetooth.BluetoothProfile; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.os.UserManager; 43 44 import androidx.test.core.app.ApplicationProvider; 45 import androidx.test.ext.junit.runners.AndroidJUnit4; 46 47 import com.android.car.qc.QCActionItem; 48 import com.android.car.qc.QCItem; 49 import com.android.car.qc.QCList; 50 import com.android.car.qc.QCRow; 51 import com.android.car.settings.R; 52 import com.android.dx.mockito.inline.extended.ExtendedMockito; 53 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 54 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 55 import com.android.settingslib.bluetooth.LocalBluetoothAdapter; 56 import com.android.settingslib.bluetooth.LocalBluetoothManager; 57 import com.android.settingslib.bluetooth.LocalBluetoothProfile; 58 59 import org.junit.After; 60 import org.junit.Before; 61 import org.junit.Test; 62 import org.junit.runner.RunWith; 63 import org.mockito.Mock; 64 import org.mockito.MockitoAnnotations; 65 import org.mockito.MockitoSession; 66 import org.mockito.quality.Strictness; 67 68 import java.util.ArrayList; 69 import java.util.Arrays; 70 import java.util.HashSet; 71 import java.util.Set; 72 73 @RunWith(AndroidJUnit4.class) 74 public class PairedBluetoothDevicesTest { 75 private static final String DEFAULT_NAME = "DEFAULT_NAME"; 76 private static final String DEFAULT_SUMMARY = "DEFAULT_SUMMARY"; 77 private static final String DEFAULT_ADDRESS = "F6:8F:AC:E8:32:50"; 78 79 private Context mContext = ApplicationProvider.getApplicationContext(); 80 private PairedBluetoothDevices mPairedBluetoothDevices; 81 private MockitoSession mSession; 82 private ArrayList<CachedBluetoothDevice> mCachedDevices = new ArrayList<>(); 83 private Set<BluetoothDevice> mBondedDevices = new HashSet<>(); 84 85 @Mock 86 private BluetoothAdapter mBluetoothAdapter; 87 @Mock 88 private LocalBluetoothAdapter mLocalBluetoothAdapter; 89 @Mock 90 private LocalBluetoothManager mBluetoothManager; 91 @Mock 92 private UserManager mUserManager; 93 94 @Before setUp()95 public void setUp() { 96 MockitoAnnotations.initMocks(this); 97 mSession = ExtendedMockito.mockitoSession() 98 .strictness(Strictness.LENIENT) 99 .mockStatic(BluetoothAdapter.class) 100 .mockStatic(LocalBluetoothManager.class) 101 .mockStatic(UserManager.class) 102 .startMocking(); 103 when(BluetoothAdapter.getDefaultAdapter()).thenReturn(mBluetoothAdapter); 104 when(mBluetoothAdapter.isEnabled()).thenReturn(true); 105 when(LocalBluetoothManager.getInstance(any(), any())).thenReturn(mBluetoothManager); 106 when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter); 107 when(mLocalBluetoothAdapter.getBondedDevices()).thenReturn(mBondedDevices); 108 when(UserManager.get(any())).thenReturn(mUserManager); 109 when(mUserManager.hasUserRestriction(DISALLOW_BLUETOOTH)).thenReturn(false); 110 111 CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class); 112 when(mBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager); 113 when(deviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices); 114 115 mPairedBluetoothDevices = new PairedBluetoothDevices(mContext); 116 } 117 118 @After tearDown()119 public void tearDown() { 120 if (mSession != null) { 121 mSession.finishMocking(); 122 } 123 } 124 125 @Test getQCItem_bluetoothDisallowed_returnsNull()126 public void getQCItem_bluetoothDisallowed_returnsNull() { 127 when(mUserManager.hasUserRestriction(DISALLOW_BLUETOOTH)).thenReturn(true); 128 QCItem item = mPairedBluetoothDevices.getQCItem(); 129 assertThat(item).isNull(); 130 } 131 132 @Test getQCItem_bluetoothDisabled_returnsBluetoothDisabledMessage()133 public void getQCItem_bluetoothDisabled_returnsBluetoothDisabledMessage() { 134 when(mBluetoothAdapter.isEnabled()).thenReturn(false); 135 QCItem item = mPairedBluetoothDevices.getQCItem(); 136 assertThat(item).isNotNull(); 137 QCList list = (QCList) item; 138 assertThat(list.getRows().size()).isEqualTo(1); 139 QCRow row = list.getRows().get(0); 140 assertThat(row.getTitle()).isEqualTo( 141 mContext.getString(R.string.qc_bluetooth_off_devices_info)); 142 assertThat(row.getStartIcon()).isNotNull(); 143 } 144 145 @Test getQCItem_noDevices_returnsPairMessage()146 public void getQCItem_noDevices_returnsPairMessage() { 147 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 148 assertThat(list.getRows().size()).isEqualTo(1); 149 QCRow row = list.getRows().get(0); 150 assertThat(row.getTitle()).isEqualTo( 151 mContext.getString(R.string.qc_bluetooth_on_no_devices_info)); 152 assertThat(row.getStartIcon()).isNotNull(); 153 } 154 155 @Test getQCItem_hasDevices_hasRows()156 public void getQCItem_hasDevices_hasRows() { 157 addBluetoothDevice("Device1", /* connected= */ true, /* busy= */ false, 158 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 159 addBluetoothDevice("Device2", /* connected= */ true, /* busy= */ false, 160 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 161 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 162 assertThat(list.getRows().size()).isEqualTo(2); 163 } 164 165 @Test getQCItem_limitsDeviceCount()166 public void getQCItem_limitsDeviceCount() { 167 addBluetoothDevice("Device1", /* connected= */ true, /* busy= */ false, 168 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 169 addBluetoothDevice("Device2", /* connected= */ false, /* busy= */ false, 170 /* phoneEnabled= */ false, /* mediaEnabled= */ false); 171 addBluetoothDevice("Device3", /* connected= */ false, /* busy= */ false, 172 /* phoneEnabled= */ false, /* mediaEnabled= */ false); 173 addBluetoothDevice("Device4", /* connected= */ false, /* busy= */ false, 174 /* phoneEnabled= */ false, /* mediaEnabled= */ false); 175 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 176 assertThat(list.getRows().size()).isEqualTo(3); 177 } 178 179 @Test getQCItem_setsTitle()180 public void getQCItem_setsTitle() { 181 addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false, 182 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 183 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 184 QCRow row = list.getRows().get(0); 185 assertThat(row.getTitle()).isEqualTo(DEFAULT_NAME); 186 } 187 188 @Test getQCItem_bluetoothDisabled_togglesUpdated()189 public void getQCItem_bluetoothDisabled_togglesUpdated() { 190 addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ false, 191 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 192 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 193 QCRow row = list.getRows().get(0); 194 QCActionItem btToggle = row.getEndItems().get(0); 195 QCActionItem phoneToggle = row.getEndItems().get(1); 196 QCActionItem mediaToggle = row.getEndItems().get(2); 197 assertThat(btToggle.isChecked()).isFalse(); 198 assertThat(btToggle.isEnabled()).isTrue(); 199 assertThat(phoneToggle.isAvailable()).isFalse(); 200 assertThat(mediaToggle.isAvailable()).isFalse(); 201 } 202 203 @Test getQCItem_bluetoothEnabled_togglesUpdated()204 public void getQCItem_bluetoothEnabled_togglesUpdated() { 205 addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false, 206 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 207 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 208 QCRow row = list.getRows().get(0); 209 QCActionItem btToggle = row.getEndItems().get(0); 210 QCActionItem phoneToggle = row.getEndItems().get(1); 211 QCActionItem mediaToggle = row.getEndItems().get(2); 212 assertThat(btToggle.isChecked()).isTrue(); 213 assertThat(btToggle.isEnabled()).isTrue(); 214 assertThat(phoneToggle.isChecked()).isTrue(); 215 assertThat(phoneToggle.isEnabled()).isTrue(); 216 assertThat(phoneToggle.isAvailable()).isTrue(); 217 assertThat(mediaToggle.isChecked()).isTrue(); 218 assertThat(mediaToggle.isEnabled()).isTrue(); 219 assertThat(mediaToggle.isAvailable()).isTrue(); 220 } 221 222 @Test getQCItem_isBusy_togglesDisabled()223 public void getQCItem_isBusy_togglesDisabled() { 224 addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ true, 225 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 226 QCList list = (QCList) mPairedBluetoothDevices.getQCItem(); 227 QCRow row = list.getRows().get(0); 228 QCActionItem btToggle = row.getEndItems().get(0); 229 QCActionItem phoneToggle = row.getEndItems().get(1); 230 QCActionItem mediaToggle = row.getEndItems().get(2); 231 assertThat(btToggle.isEnabled()).isFalse(); 232 assertThat(phoneToggle.isEnabled()).isFalse(); 233 assertThat(mediaToggle.isEnabled()).isFalse(); 234 } 235 236 @Test onNotifyChange_toggleBluetooth()237 public void onNotifyChange_toggleBluetooth() { 238 addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true, 239 /* phoneEnabled= */ true, /* mediaEnabled= */ true); 240 Intent intent = new Intent(); 241 intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS); 242 intent.putExtra(EXTRA_BUTTON_TYPE, BLUETOOTH_BUTTON); 243 intent.putExtra(QC_ACTION_TOGGLE_STATE, true); 244 mPairedBluetoothDevices.onNotifyChange(intent); 245 CachedBluetoothDevice device = mCachedDevices.get(0); 246 verify(device).connect(); 247 } 248 249 @Test onNotifyChange_togglePhone()250 public void onNotifyChange_togglePhone() { 251 addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true, 252 /* phoneEnabled= */ false, /* mediaEnabled= */ true); 253 Intent intent = new Intent(); 254 intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS); 255 intent.putExtra(EXTRA_BUTTON_TYPE, PHONE_BUTTON); 256 intent.putExtra(QC_ACTION_TOGGLE_STATE, true); 257 mPairedBluetoothDevices.onNotifyChange(intent); 258 CachedBluetoothDevice device = mCachedDevices.get(0); 259 LocalBluetoothProfile profile = getProfile(device, BluetoothProfile.HEADSET_CLIENT); 260 verify(profile).setEnabled(any(), eq(true)); 261 } 262 263 @Test onNotifyChange_toggleMedia()264 public void onNotifyChange_toggleMedia() { 265 addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true, 266 /* phoneEnabled= */ true, /* mediaEnabled= */ false); 267 Intent intent = new Intent(); 268 intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS); 269 intent.putExtra(EXTRA_BUTTON_TYPE, MEDIA_BUTTON); 270 intent.putExtra(QC_ACTION_TOGGLE_STATE, true); 271 mPairedBluetoothDevices.onNotifyChange(intent); 272 CachedBluetoothDevice device = mCachedDevices.get(0); 273 LocalBluetoothProfile profile = getProfile(device, BluetoothProfile.A2DP_SINK); 274 verify(profile).setEnabled(any(), eq(true)); 275 } 276 addBluetoothDevice(String name, boolean connected, boolean busy, boolean phoneEnabled, boolean mediaEnabled)277 private void addBluetoothDevice(String name, boolean connected, boolean busy, 278 boolean phoneEnabled, boolean mediaEnabled) { 279 CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); 280 BluetoothDevice device = mock(BluetoothDevice.class); 281 when(cachedDevice.getDevice()).thenReturn(device); 282 when(device.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); 283 when(cachedDevice.getName()).thenReturn(name); 284 when(cachedDevice.getAddress()).thenReturn(DEFAULT_ADDRESS); 285 when(cachedDevice.isConnected()).thenReturn(connected); 286 when(cachedDevice.isBusy()).thenReturn(busy); 287 when(cachedDevice.getCarConnectionSummary()).thenReturn(DEFAULT_SUMMARY); 288 BluetoothClass btClass = mock(BluetoothClass.class); 289 when(cachedDevice.getBtClass()).thenReturn(btClass); 290 when(btClass.getMajorDeviceClass()).thenReturn(BluetoothClass.Device.Major.PHONE); 291 LocalBluetoothProfile phoneProfile = mock(LocalBluetoothProfile.class); 292 LocalBluetoothProfile mediaProfile = mock(LocalBluetoothProfile.class); 293 when(phoneProfile.getProfileId()).thenReturn(BluetoothProfile.HEADSET_CLIENT); 294 when(mediaProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP_SINK); 295 when(phoneProfile.isEnabled(any())).thenReturn(phoneEnabled); 296 when(mediaProfile.isEnabled(any())).thenReturn(mediaEnabled); 297 when(cachedDevice.getProfiles()).thenReturn(Arrays.asList(phoneProfile, mediaProfile)); 298 mCachedDevices.add(cachedDevice); 299 mBondedDevices.add(device); 300 } 301 getProfile(CachedBluetoothDevice device, int profileId)302 private LocalBluetoothProfile getProfile(CachedBluetoothDevice device, int profileId) { 303 for (LocalBluetoothProfile profile : device.getProfiles()) { 304 if (profile.getProfileId() == profileId) { 305 return profile; 306 } 307 } 308 return null; 309 } 310 } 311