1 /* 2 * Copyright (C) 2017 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.bluetooth; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyBoolean; 23 import static org.mockito.ArgumentMatchers.anyInt; 24 import static org.mockito.Mockito.doNothing; 25 import static org.mockito.Mockito.doReturn; 26 import static org.mockito.Mockito.mock; 27 import static org.mockito.Mockito.never; 28 import static org.mockito.Mockito.spy; 29 import static org.mockito.Mockito.times; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.when; 32 33 import android.bluetooth.BluetoothAdapter; 34 import android.bluetooth.BluetoothDevice; 35 import android.bluetooth.BluetoothProfile; 36 import android.content.Context; 37 import android.content.res.Resources; 38 39 import androidx.preference.PreferenceGroup; 40 41 import com.android.settings.R; 42 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; 43 import com.android.settingslib.bluetooth.BluetoothDeviceFilter; 44 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 45 import com.android.settingslib.bluetooth.LocalBluetoothManager; 46 import com.android.settingslib.widget.FooterPreference; 47 48 import org.junit.Before; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 import org.mockito.Answers; 52 import org.mockito.Mock; 53 import org.mockito.MockitoAnnotations; 54 import org.robolectric.RobolectricTestRunner; 55 import org.robolectric.RuntimeEnvironment; 56 import org.robolectric.annotation.Config; 57 import org.robolectric.shadow.api.Shadow; 58 59 @RunWith(RobolectricTestRunner.class) 60 @Config(shadows = {ShadowBluetoothAdapter.class}) 61 public class BluetoothPairingDetailTest { 62 private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; 63 private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1"; 64 65 @Mock 66 private Resources mResource; 67 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 68 private LocalBluetoothManager mLocalManager; 69 @Mock 70 private PreferenceGroup mPreferenceGroup; 71 @Mock 72 private CachedBluetoothDevice mCachedBluetoothDevice; 73 private BluetoothPairingDetail mFragment; 74 private Context mContext; 75 private BluetoothProgressCategory mAvailableDevicesCategory; 76 private FooterPreference mFooterPreference; 77 private BluetoothAdapter mBluetoothAdapter; 78 private ShadowBluetoothAdapter mShadowBluetoothAdapter; 79 private BluetoothDevice mBluetoothDevice; 80 81 @Before setUp()82 public void setUp() { 83 MockitoAnnotations.initMocks(this); 84 85 mContext = RuntimeEnvironment.application; 86 mFragment = spy(new BluetoothPairingDetail()); 87 doReturn(mContext).when(mFragment).getContext(); 88 doReturn(mResource).when(mFragment).getResources(); 89 when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); 90 91 mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext)); 92 mFooterPreference = new FooterPreference(mContext); 93 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 94 mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); 95 mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); 96 97 mFragment.mBluetoothAdapter = mBluetoothAdapter; 98 mFragment.mLocalManager = mLocalManager; 99 mFragment.mDeviceListGroup = mPreferenceGroup; 100 mFragment.mAlwaysDiscoverable = new AlwaysDiscoverable(mContext); 101 } 102 103 @Test initPreferencesFromPreferenceScreen_findPreferences()104 public void initPreferencesFromPreferenceScreen_findPreferences() { 105 doReturn(mAvailableDevicesCategory).when(mFragment) 106 .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES); 107 doReturn(mFooterPreference).when(mFragment) 108 .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF); 109 110 mFragment.initPreferencesFromPreferenceScreen(); 111 112 assertThat(mFragment.mAvailableDevicesCategory).isEqualTo(mAvailableDevicesCategory); 113 assertThat(mFragment.mFooterPreference).isEqualTo(mFooterPreference); 114 } 115 116 @Test startScanning_startScanAndRemoveDevices()117 public void startScanning_startScanAndRemoveDevices() { 118 mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; 119 mFragment.mDeviceListGroup = mAvailableDevicesCategory; 120 121 mFragment.enableScanning(); 122 123 verify(mFragment).startScanning(); 124 verify(mAvailableDevicesCategory).removeAll(); 125 } 126 127 @Test updateContent_stateOn_addDevices()128 public void updateContent_stateOn_addDevices() { 129 mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; 130 mFragment.mFooterPreference = mFooterPreference; 131 doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean()); 132 133 mFragment.updateContent(BluetoothAdapter.STATE_ON); 134 135 verify(mFragment).addDeviceCategory(mAvailableDevicesCategory, 136 R.string.bluetooth_preference_found_media_devices, 137 BluetoothDeviceFilter.ALL_FILTER, false); 138 assertThat(mBluetoothAdapter.getScanMode()) 139 .isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); 140 } 141 142 @Test updateContent_stateOff_finish()143 public void updateContent_stateOff_finish() { 144 mFragment.updateContent(BluetoothAdapter.STATE_OFF); 145 146 verify(mFragment).finish(); 147 } 148 149 @Test updateBluetooth_bluetoothOff_turnOnBluetooth()150 public void updateBluetooth_bluetoothOff_turnOnBluetooth() { 151 mShadowBluetoothAdapter.setEnabled(false); 152 153 mFragment.updateBluetooth(); 154 155 assertThat(mBluetoothAdapter.isEnabled()).isTrue(); 156 } 157 158 @Test updateBluetooth_bluetoothOn_updateState()159 public void updateBluetooth_bluetoothOn_updateState() { 160 mShadowBluetoothAdapter.setEnabled(true); 161 doNothing().when(mFragment).updateContent(anyInt()); 162 163 mFragment.updateBluetooth(); 164 165 verify(mFragment).updateContent(anyInt()); 166 } 167 168 @Test onScanningStateChanged_restartScanAfterInitialScanning()169 public void onScanningStateChanged_restartScanAfterInitialScanning() { 170 mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; 171 mFragment.mFooterPreference = mFooterPreference; 172 mFragment.mDeviceListGroup = mAvailableDevicesCategory; 173 doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean()); 174 175 // Initial Bluetooth ON will trigger scan enable, list clear and scan start 176 mFragment.updateContent(BluetoothAdapter.STATE_ON); 177 verify(mFragment).enableScanning(); 178 assertThat(mAvailableDevicesCategory.getPreferenceCount()).isEqualTo(0); 179 verify(mFragment).startScanning(); 180 181 // Subsequent scan started event will not trigger start/stop nor list clear 182 mFragment.onScanningStateChanged(true); 183 verify(mFragment, times(1)).startScanning(); 184 verify(mAvailableDevicesCategory, times(1)).setProgress(true); 185 186 // Subsequent scan finished event will trigger scan start without list clean 187 mFragment.onScanningStateChanged(false); 188 verify(mFragment, times(2)).startScanning(); 189 verify(mAvailableDevicesCategory, times(2)).setProgress(true); 190 191 // Subsequent scan started event will not trigger any change 192 mFragment.onScanningStateChanged(true); 193 verify(mFragment, times(2)).startScanning(); 194 verify(mAvailableDevicesCategory, times(3)).setProgress(true); 195 verify(mFragment, never()).stopScanning(); 196 197 // Disable scanning will trigger scan stop 198 mFragment.disableScanning(); 199 verify(mFragment, times(1)).stopScanning(); 200 201 // Subsequent scan start event will not trigger any change besides progress circle 202 mFragment.onScanningStateChanged(true); 203 verify(mAvailableDevicesCategory, times(4)).setProgress(true); 204 205 // However, subsequent scan finished event won't trigger new scan start and will stop 206 // progress circle from spinning 207 mFragment.onScanningStateChanged(false); 208 verify(mAvailableDevicesCategory, times(1)).setProgress(false); 209 verify(mFragment, times(2)).startScanning(); 210 verify(mFragment, times(1)).stopScanning(); 211 212 // Verify that clean up only happen once at initialization 213 verify(mAvailableDevicesCategory, times(1)).removeAll(); 214 } 215 216 @Test onBluetoothStateChanged_whenTurnedOnBTShowToast()217 public void onBluetoothStateChanged_whenTurnedOnBTShowToast() { 218 doNothing().when(mFragment).updateContent(anyInt()); 219 220 mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); 221 222 verify(mFragment).showBluetoothTurnedOnToast(); 223 } 224 225 @Test onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish()226 public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() { 227 final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); 228 mFragment.mSelectedList.add(mBluetoothDevice); 229 mFragment.mSelectedList.add(device); 230 231 when(mCachedBluetoothDevice.isConnected()).thenReturn(true); 232 when(mCachedBluetoothDevice.getDevice()).thenReturn(device); 233 234 mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, 235 BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); 236 237 verify(mFragment).finish(); 238 } 239 240 @Test onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing()241 public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() { 242 final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); 243 mFragment.mSelectedList.add(device); 244 245 when(mCachedBluetoothDevice.isConnected()).thenReturn(true); 246 when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); 247 248 mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, 249 BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); 250 251 // not crash 252 } 253 254 @Test onProfileConnectionStateChanged_deviceDisconnected_doNothing()255 public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() { 256 final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); 257 mFragment.mSelectedList.add(mBluetoothDevice); 258 mFragment.mSelectedList.add(device); 259 260 when(mCachedBluetoothDevice.isConnected()).thenReturn(false); 261 when(mCachedBluetoothDevice.getDevice()).thenReturn(device); 262 263 mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, 264 BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED); 265 266 // not crash 267 } 268 269 @Test onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed()270 public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() { 271 final BluetoothDevicePreference preference = 272 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, 273 true, BluetoothDevicePreference.SortType.TYPE_FIFO); 274 final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); 275 mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); 276 277 when(mCachedBluetoothDevice.isConnected()).thenReturn(true); 278 when(mCachedBluetoothDevice.getDevice()).thenReturn(device); 279 280 mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, 281 BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); 282 283 assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0); 284 } 285 286 @Test onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing()287 public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() { 288 final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); 289 final BluetoothDevicePreference preference = 290 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, 291 true, BluetoothDevicePreference.SortType.TYPE_FIFO); 292 final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); 293 final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); 294 mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); 295 296 when(mCachedBluetoothDevice.isConnected()).thenReturn(true); 297 when(mCachedBluetoothDevice.getDevice()).thenReturn(device); 298 when(cachedDevice.isConnected()).thenReturn(true); 299 when(cachedDevice.getDevice()).thenReturn(device2); 300 when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); 301 302 mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP, 303 BluetoothAdapter.STATE_CONNECTED); 304 305 // not crash 306 } 307 }