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