1 /* 2 * Copyright (C) 2023 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.settings.accessibility; 18 19 20 import static com.google.common.truth.Truth.assertThat; 21 22 import static org.mockito.Mockito.doReturn; 23 import static org.mockito.Mockito.mock; 24 import static org.mockito.Mockito.never; 25 import static org.mockito.Mockito.verify; 26 27 import android.bluetooth.BluetoothAdapter; 28 import android.bluetooth.BluetoothDevice; 29 import android.bluetooth.BluetoothProfile; 30 import android.bluetooth.BluetoothUuid; 31 import android.bluetooth.le.ScanRecord; 32 import android.bluetooth.le.ScanResult; 33 import android.content.Context; 34 import android.graphics.drawable.Drawable; 35 import android.util.Pair; 36 37 import androidx.preference.Preference; 38 import androidx.test.core.app.ApplicationProvider; 39 40 import com.android.settings.bluetooth.BluetoothDevicePreference; 41 import com.android.settings.bluetooth.BluetoothProgressCategory; 42 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; 43 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 44 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 45 import com.android.settingslib.bluetooth.HearingAidInfo; 46 import com.android.settingslib.bluetooth.LocalBluetoothManager; 47 48 import org.junit.Before; 49 import org.junit.Rule; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.mockito.Mock; 53 import org.mockito.Spy; 54 import org.mockito.junit.MockitoJUnit; 55 import org.mockito.junit.MockitoRule; 56 import org.robolectric.RobolectricTestRunner; 57 import org.robolectric.annotation.Config; 58 59 import java.util.List; 60 61 /** Tests for {@link HearingDevicePairingFragment}. */ 62 @RunWith(RobolectricTestRunner.class) 63 @Config(shadows = {ShadowBluetoothAdapter.class}) 64 public class HearingDevicePairingFragmentTest { 65 66 private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; 67 68 @Rule 69 public final MockitoRule mockito = MockitoJUnit.rule(); 70 71 @Spy 72 private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 73 @Spy 74 private final HearingDevicePairingFragment mFragment = new TestHearingDevicePairingFragment(); 75 76 @Mock 77 private LocalBluetoothManager mLocalManager; 78 @Mock 79 private CachedBluetoothDeviceManager mCachedDeviceManager; 80 @Mock 81 private CachedBluetoothDevice mCachedDevice; 82 @Mock 83 private BluetoothProgressCategory mAvailableHearingDeviceGroup; 84 85 private final Context mContext = ApplicationProvider.getApplicationContext(); 86 private BluetoothDevice mDevice; 87 private BluetoothDevicePreference mDevicePreference; 88 89 90 @Before setUp()91 public void setUp() { 92 mFragment.mLocalManager = mLocalManager; 93 mFragment.mCachedDeviceManager = mCachedDeviceManager; 94 mFragment.mBluetoothAdapter = mBluetoothAdapter; 95 doReturn(mContext).when(mFragment).getContext(); 96 doReturn(mAvailableHearingDeviceGroup).when(mFragment).findPreference( 97 "available_hearing_devices"); 98 mFragment.initPreferencesFromPreferenceScreen(); 99 100 101 mDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); 102 doReturn(mDevice).when(mCachedDevice).getDevice(); 103 final Pair<Drawable, String> pair = new Pair<>(mock(Drawable.class), "test_device"); 104 doReturn(pair).when(mCachedDevice).getDrawableWithDescription(); 105 106 mDevicePreference = new BluetoothDevicePreference(mContext, mCachedDevice, true, 107 BluetoothDevicePreference.SortType.TYPE_DEFAULT); 108 } 109 110 @Test startAndStopScanning_stateIsCorrect()111 public void startAndStopScanning_stateIsCorrect() { 112 mFragment.startScanning(); 113 114 verify(mFragment).startLeScanning(); 115 116 mFragment.stopScanning(); 117 118 verify(mFragment).stopLeScanning(); 119 } 120 121 @Test onDeviceDeleted_stateIsCorrect()122 public void onDeviceDeleted_stateIsCorrect() { 123 mFragment.mDevicePreferenceMap.put(mCachedDevice, mDevicePreference); 124 125 assertThat(mFragment.mDevicePreferenceMap).isNotEmpty(); 126 127 mFragment.onDeviceDeleted(mCachedDevice); 128 129 assertThat(mFragment.mDevicePreferenceMap).isEmpty(); 130 verify(mAvailableHearingDeviceGroup).removePreference(mDevicePreference); 131 } 132 133 @Test addDevice_bluetoothOff_doNothing()134 public void addDevice_bluetoothOff_doNothing() { 135 doReturn(BluetoothAdapter.STATE_OFF).when(mBluetoothAdapter).getState(); 136 137 assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0); 138 139 mFragment.addDevice(mCachedDevice); 140 141 verify(mAvailableHearingDeviceGroup, never()).addPreference(mDevicePreference); 142 assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0); 143 } 144 145 @Test addDevice_addToAvailableHearingDeviceGroup()146 public void addDevice_addToAvailableHearingDeviceGroup() { 147 doReturn(BluetoothAdapter.STATE_ON).when(mBluetoothAdapter).getState(); 148 149 assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0); 150 151 mFragment.addDevice(mCachedDevice); 152 153 verify(mAvailableHearingDeviceGroup).addPreference(mDevicePreference); 154 assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(1); 155 } 156 157 @Test handleLeScanResult_markDeviceAsHearingAid()158 public void handleLeScanResult_markDeviceAsHearingAid() { 159 ScanResult scanResult = mock(ScanResult.class); 160 doReturn(mDevice).when(scanResult).getDevice(); 161 doReturn(mCachedDevice).when(mCachedDeviceManager).findDevice(mDevice); 162 163 mFragment.handleLeScanResult(scanResult); 164 165 verify(mCachedDevice).setHearingAidInfo(new HearingAidInfo.Builder().build()); 166 } 167 168 @Test handleLeScanResult_isAndroidCompatible_addDevice()169 public void handleLeScanResult_isAndroidCompatible_addDevice() { 170 ScanResult scanResult = mock(ScanResult.class); 171 doReturn(mDevice).when(scanResult).getDevice(); 172 doReturn(mCachedDevice).when(mCachedDeviceManager).findDevice(mDevice); 173 doReturn(true).when(mFragment).isAndroidCompatibleHearingAid(scanResult); 174 175 mFragment.handleLeScanResult(scanResult); 176 177 verify(mFragment).addDevice(mCachedDevice); 178 } 179 180 @Test handleLeScanResult_isNotAndroidCompatible_discoverServices()181 public void handleLeScanResult_isNotAndroidCompatible_discoverServices() { 182 ScanResult scanResult = mock(ScanResult.class); 183 doReturn(mDevice).when(scanResult).getDevice(); 184 doReturn(mCachedDevice).when(mCachedDeviceManager).findDevice(mDevice); 185 doReturn(false).when(mFragment).isAndroidCompatibleHearingAid(scanResult); 186 187 mFragment.handleLeScanResult(scanResult); 188 189 verify(mFragment).discoverServices(mCachedDevice); 190 } 191 192 @Test handleLeScanResult_alreadyBonded_doNothing()193 public void handleLeScanResult_alreadyBonded_doNothing() { 194 ScanResult scanResult = mock(ScanResult.class); 195 doReturn(mDevice).when(scanResult).getDevice(); 196 doReturn(mCachedDevice).when(mCachedDeviceManager).findDevice(mDevice); 197 doReturn(BluetoothDevice.BOND_BONDED).when(mCachedDevice).getBondState(); 198 199 mFragment.handleLeScanResult(scanResult); 200 201 verify(mFragment, never()).addDevice(mCachedDevice); 202 verify(mFragment, never()).discoverServices(mCachedDevice); 203 } 204 205 @Test onProfileConnectionStateChanged_deviceConnected_inSelectedList_finish()206 public void onProfileConnectionStateChanged_deviceConnected_inSelectedList_finish() { 207 doReturn(true).when(mCachedDevice).isConnected(); 208 mFragment.mSelectedDeviceList.add(mDevice); 209 210 mFragment.onProfileConnectionStateChanged(mCachedDevice, BluetoothAdapter.STATE_CONNECTED, 211 BluetoothProfile.A2DP); 212 213 verify(mFragment).finish(); 214 } 215 216 @Test onProfileConnectionStateChanged_deviceConnected_notInSelectedList_deleteDevice()217 public void onProfileConnectionStateChanged_deviceConnected_notInSelectedList_deleteDevice() { 218 doReturn(true).when(mCachedDevice).isConnected(); 219 220 mFragment.onProfileConnectionStateChanged(mCachedDevice, BluetoothAdapter.STATE_CONNECTED, 221 BluetoothProfile.A2DP); 222 223 verify(mFragment).removeDevice(mCachedDevice); 224 } 225 226 @Test onProfileConnectionStateChanged_deviceNotConnected_doNothing()227 public void onProfileConnectionStateChanged_deviceNotConnected_doNothing() { 228 doReturn(false).when(mCachedDevice).isConnected(); 229 230 mFragment.onProfileConnectionStateChanged(mCachedDevice, BluetoothAdapter.STATE_CONNECTED, 231 BluetoothProfile.A2DP); 232 233 verify(mFragment, never()).finish(); 234 verify(mFragment, never()).removeDevice(mCachedDevice); 235 } 236 237 @Test onBluetoothStateChanged_stateOn_startScanningAndShowToast()238 public void onBluetoothStateChanged_stateOn_startScanningAndShowToast() { 239 mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); 240 241 verify(mFragment).startScanning(); 242 verify(mFragment).showBluetoothTurnedOnToast(); 243 } 244 245 @Test onBluetoothStateChanged_stateOff_finish()246 public void onBluetoothStateChanged_stateOff_finish() { 247 mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); 248 249 verify(mFragment).finish(); 250 } 251 252 @Test onDeviceBondStateChanged_bonded_finish()253 public void onDeviceBondStateChanged_bonded_finish() { 254 mFragment.onDeviceBondStateChanged(mCachedDevice, BluetoothDevice.BOND_BONDED); 255 256 verify(mFragment).finish(); 257 } 258 259 @Test onDeviceBondStateChanged_selectedDeviceNotBonded_startScanning()260 public void onDeviceBondStateChanged_selectedDeviceNotBonded_startScanning() { 261 mFragment.mSelectedDevice = mDevice; 262 263 mFragment.onDeviceBondStateChanged(mCachedDevice, BluetoothDevice.BOND_NONE); 264 265 verify(mFragment).startScanning(); 266 } 267 268 @Test isAndroidCompatibleHearingAid_asha_returnTrue()269 public void isAndroidCompatibleHearingAid_asha_returnTrue() { 270 ScanResult scanResult = createAshaScanResult(); 271 272 boolean isCompatible = mFragment.isAndroidCompatibleHearingAid(scanResult); 273 274 assertThat(isCompatible).isTrue(); 275 } 276 277 @Test isAndroidCompatibleHearingAid_has_returnTrue()278 public void isAndroidCompatibleHearingAid_has_returnTrue() { 279 ScanResult scanResult = createHasScanResult(); 280 281 boolean isCompatible = mFragment.isAndroidCompatibleHearingAid(scanResult); 282 283 assertThat(isCompatible).isTrue(); 284 } 285 286 @Test isAndroidCompatibleHearingAid_mfiHas_returnFalse()287 public void isAndroidCompatibleHearingAid_mfiHas_returnFalse() { 288 ScanResult scanResult = createMfiHasScanResult(); 289 290 boolean isCompatible = mFragment.isAndroidCompatibleHearingAid(scanResult); 291 292 assertThat(isCompatible).isFalse(); 293 } 294 createAshaScanResult()295 private ScanResult createAshaScanResult() { 296 ScanResult scanResult = mock(ScanResult.class); 297 ScanRecord scanRecord = mock(ScanRecord.class); 298 byte[] fakeAshaServiceData = new byte[] { 299 0x09, 0x16, (byte) 0xf0, (byte) 0xfd, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04}; 300 doReturn(scanRecord).when(scanResult).getScanRecord(); 301 doReturn(fakeAshaServiceData).when(scanRecord).getServiceData(BluetoothUuid.HEARING_AID); 302 return scanResult; 303 } 304 createHasScanResult()305 private ScanResult createHasScanResult() { 306 ScanResult scanResult = mock(ScanResult.class); 307 ScanRecord scanRecord = mock(ScanRecord.class); 308 doReturn(scanRecord).when(scanResult).getScanRecord(); 309 doReturn(List.of(BluetoothUuid.HAS)).when(scanRecord).getServiceUuids(); 310 return scanResult; 311 } 312 createMfiHasScanResult()313 private ScanResult createMfiHasScanResult() { 314 ScanResult scanResult = mock(ScanResult.class); 315 ScanRecord scanRecord = mock(ScanRecord.class); 316 byte[] fakeMfiServiceData = new byte[] {0x00, 0x00, 0x00, 0x00}; 317 doReturn(scanRecord).when(scanResult).getScanRecord(); 318 doReturn(fakeMfiServiceData).when(scanRecord).getServiceData(BluetoothUuid.MFI_HAS); 319 return scanResult; 320 } 321 322 private class TestHearingDevicePairingFragment extends HearingDevicePairingFragment { 323 @Override getCachedPreference(String key)324 protected Preference getCachedPreference(String key) { 325 if (key.equals(TEST_DEVICE_ADDRESS)) { 326 return mDevicePreference; 327 } 328 return super.getCachedPreference(key); 329 } 330 } 331 } 332