1 /* 2 * Copyright 2016 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.appspot.apprtc; 12 13 import static org.junit.Assert.assertEquals; 14 import static org.junit.Assert.assertNotNull; 15 import static org.junit.Assert.assertNull; 16 import static org.mockito.Mockito.mock; 17 import static org.mockito.Mockito.never; 18 import static org.mockito.Mockito.times; 19 import static org.mockito.Mockito.verify; 20 import static org.mockito.Mockito.when; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothHeadset; 25 import android.bluetooth.BluetoothProfile; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.media.AudioManager; 31 import android.util.Log; 32 import androidx.test.core.app.ApplicationProvider; 33 import java.util.ArrayList; 34 import java.util.List; 35 import org.appspot.apprtc.AppRTCBluetoothManager.State; 36 import org.chromium.testing.local.LocalRobolectricTestRunner; 37 import org.junit.Before; 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 import org.robolectric.annotation.Config; 41 import org.robolectric.shadows.ShadowLog; 42 43 /** 44 * Verifies basic behavior of the AppRTCBluetoothManager class. 45 * Note that the test object uses an AppRTCAudioManager (injected in ctor), 46 * but a mocked version is used instead. Hence, the parts "driven" by the AppRTC 47 * audio manager are not included in this test. 48 */ 49 @RunWith(LocalRobolectricTestRunner.class) 50 @Config(manifest = Config.NONE) 51 public class BluetoothManagerTest { 52 private static final String TAG = "BluetoothManagerTest"; 53 private static final String BLUETOOTH_TEST_DEVICE_NAME = "BluetoothTestDevice"; 54 55 private BroadcastReceiver bluetoothHeadsetStateReceiver; 56 private BluetoothProfile.ServiceListener bluetoothServiceListener; 57 private BluetoothHeadset mockedBluetoothHeadset; 58 private BluetoothDevice mockedBluetoothDevice; 59 private List<BluetoothDevice> mockedBluetoothDeviceList; 60 private AppRTCBluetoothManager bluetoothManager; 61 private AppRTCAudioManager mockedAppRtcAudioManager; 62 private AudioManager mockedAudioManager; 63 private Context context; 64 65 @Before setUp()66 public void setUp() { 67 ShadowLog.stream = System.out; 68 context = ApplicationProvider.getApplicationContext(); 69 mockedAppRtcAudioManager = mock(AppRTCAudioManager.class); 70 mockedAudioManager = mock(AudioManager.class); 71 mockedBluetoothHeadset = mock(BluetoothHeadset.class); 72 mockedBluetoothDevice = mock(BluetoothDevice.class); 73 mockedBluetoothDeviceList = new ArrayList<BluetoothDevice>(); 74 75 // Simulate that bluetooth SCO audio is available by default. 76 when(mockedAudioManager.isBluetoothScoAvailableOffCall()).thenReturn(true); 77 78 // Create the test object and override protected methods for this test. 79 bluetoothManager = new AppRTCBluetoothManager(context, mockedAppRtcAudioManager) { 80 @Override 81 protected AudioManager getAudioManager(Context context) { 82 Log.d(TAG, "getAudioManager"); 83 return mockedAudioManager; 84 } 85 86 @Override 87 protected void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { 88 Log.d(TAG, "registerReceiver"); 89 if (filter.hasAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED) 90 && filter.hasAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { 91 // Gives access to the real broadcast receiver so the test can use it. 92 bluetoothHeadsetStateReceiver = receiver; 93 } 94 } 95 96 @Override 97 protected void unregisterReceiver(BroadcastReceiver receiver) { 98 Log.d(TAG, "unregisterReceiver"); 99 if (receiver == bluetoothHeadsetStateReceiver) { 100 bluetoothHeadsetStateReceiver = null; 101 } 102 } 103 104 @Override 105 protected boolean getBluetoothProfileProxy( 106 Context context, BluetoothProfile.ServiceListener listener, int profile) { 107 Log.d(TAG, "getBluetoothProfileProxy"); 108 if (profile == BluetoothProfile.HEADSET) { 109 // Allows the test to access the real Bluetooth service listener object. 110 bluetoothServiceListener = listener; 111 } 112 return true; 113 } 114 115 @Override 116 protected boolean hasPermission(Context context, String permission) { 117 Log.d(TAG, "hasPermission(" + permission + ")"); 118 // Ensure that the client asks for Bluetooth permission. 119 return android.Manifest.permission.BLUETOOTH.equals(permission); 120 } 121 122 @Override 123 protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) { 124 // Do nothing in tests. No need to mock BluetoothAdapter. 125 } 126 }; 127 } 128 129 // Verify that Bluetooth service listener for headset profile is properly initialized. 130 @Test testBluetoothServiceListenerInitialized()131 public void testBluetoothServiceListenerInitialized() { 132 bluetoothManager.start(); 133 assertNotNull(bluetoothServiceListener); 134 verify(mockedAppRtcAudioManager, never()).updateAudioDeviceState(); 135 } 136 137 // Verify that broadcast receivers for Bluetooth SCO audio state and Bluetooth headset state 138 // are properly registered and unregistered. 139 @Test testBluetoothBroadcastReceiversAreRegistered()140 public void testBluetoothBroadcastReceiversAreRegistered() { 141 bluetoothManager.start(); 142 assertNotNull(bluetoothHeadsetStateReceiver); 143 bluetoothManager.stop(); 144 assertNull(bluetoothHeadsetStateReceiver); 145 } 146 147 // Verify that the Bluetooth manager starts and stops with correct states. 148 @Test testBluetoothDefaultStartStopStates()149 public void testBluetoothDefaultStartStopStates() { 150 bluetoothManager.start(); 151 assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); 152 bluetoothManager.stop(); 153 assertEquals(bluetoothManager.getState(), State.UNINITIALIZED); 154 } 155 156 // Verify correct state after receiving BluetoothServiceListener.onServiceConnected() 157 // when no BT device is enabled. 158 @Test testBluetoothServiceListenerConnectedWithNoHeadset()159 public void testBluetoothServiceListenerConnectedWithNoHeadset() { 160 bluetoothManager.start(); 161 assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); 162 simulateBluetoothServiceConnectedWithNoConnectedHeadset(); 163 verify(mockedAppRtcAudioManager, times(1)).updateAudioDeviceState(); 164 assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); 165 } 166 167 // Verify correct state after receiving BluetoothServiceListener.onServiceConnected() 168 // when one emulated (test) BT device is enabled. Android does not support more than 169 // one connected BT headset. 170 @Test testBluetoothServiceListenerConnectedWithHeadset()171 public void testBluetoothServiceListenerConnectedWithHeadset() { 172 bluetoothManager.start(); 173 assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); 174 simulateBluetoothServiceConnectedWithConnectedHeadset(); 175 verify(mockedAppRtcAudioManager, times(1)).updateAudioDeviceState(); 176 assertEquals(bluetoothManager.getState(), State.HEADSET_AVAILABLE); 177 } 178 179 // Verify correct state after receiving BluetoothProfile.ServiceListener.onServiceDisconnected(). 180 @Test testBluetoothServiceListenerDisconnected()181 public void testBluetoothServiceListenerDisconnected() { 182 bluetoothManager.start(); 183 assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); 184 simulateBluetoothServiceDisconnected(); 185 verify(mockedAppRtcAudioManager, times(1)).updateAudioDeviceState(); 186 assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); 187 } 188 189 // Verify correct state after BluetoothServiceListener.onServiceConnected() and 190 // the intent indicating that the headset is actually connected. Both these callbacks 191 // results in calls to updateAudioDeviceState() on the AppRTC audio manager. 192 // No BT SCO is enabled here to keep the test limited. 193 @Test testBluetoothHeadsetConnected()194 public void testBluetoothHeadsetConnected() { 195 bluetoothManager.start(); 196 assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); 197 simulateBluetoothServiceConnectedWithConnectedHeadset(); 198 simulateBluetoothHeadsetConnected(); 199 verify(mockedAppRtcAudioManager, times(2)).updateAudioDeviceState(); 200 assertEquals(bluetoothManager.getState(), State.HEADSET_AVAILABLE); 201 } 202 203 // Verify correct state sequence for a case when a BT headset is available, 204 // followed by BT SCO audio being enabled and then stopped. 205 @Test testBluetoothScoAudioStartAndStop()206 public void testBluetoothScoAudioStartAndStop() { 207 bluetoothManager.start(); 208 assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); 209 simulateBluetoothServiceConnectedWithConnectedHeadset(); 210 assertEquals(bluetoothManager.getState(), State.HEADSET_AVAILABLE); 211 bluetoothManager.startScoAudio(); 212 assertEquals(bluetoothManager.getState(), State.SCO_CONNECTING); 213 simulateBluetoothScoConnectionConnected(); 214 assertEquals(bluetoothManager.getState(), State.SCO_CONNECTED); 215 bluetoothManager.stopScoAudio(); 216 simulateBluetoothScoConnectionDisconnected(); 217 assertEquals(bluetoothManager.getState(), State.SCO_DISCONNECTING); 218 bluetoothManager.stop(); 219 assertEquals(bluetoothManager.getState(), State.UNINITIALIZED); 220 verify(mockedAppRtcAudioManager, times(3)).updateAudioDeviceState(); 221 } 222 223 /** 224 * Private helper methods. 225 */ simulateBluetoothServiceConnectedWithNoConnectedHeadset()226 private void simulateBluetoothServiceConnectedWithNoConnectedHeadset() { 227 mockedBluetoothDeviceList.clear(); 228 when(mockedBluetoothHeadset.getConnectedDevices()).thenReturn(mockedBluetoothDeviceList); 229 bluetoothServiceListener.onServiceConnected(BluetoothProfile.HEADSET, mockedBluetoothHeadset); 230 // In real life, the AppRTC audio manager makes this call. 231 bluetoothManager.updateDevice(); 232 } 233 simulateBluetoothServiceConnectedWithConnectedHeadset()234 private void simulateBluetoothServiceConnectedWithConnectedHeadset() { 235 mockedBluetoothDeviceList.clear(); 236 mockedBluetoothDeviceList.add(mockedBluetoothDevice); 237 when(mockedBluetoothHeadset.getConnectedDevices()).thenReturn(mockedBluetoothDeviceList); 238 when(mockedBluetoothDevice.getName()).thenReturn(BLUETOOTH_TEST_DEVICE_NAME); 239 bluetoothServiceListener.onServiceConnected(BluetoothProfile.HEADSET, mockedBluetoothHeadset); 240 // In real life, the AppRTC audio manager makes this call. 241 bluetoothManager.updateDevice(); 242 } 243 simulateBluetoothServiceDisconnected()244 private void simulateBluetoothServiceDisconnected() { 245 bluetoothServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); 246 } 247 simulateBluetoothHeadsetConnected()248 private void simulateBluetoothHeadsetConnected() { 249 Intent intent = new Intent(); 250 intent.setAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 251 intent.putExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_CONNECTED); 252 bluetoothHeadsetStateReceiver.onReceive(context, intent); 253 } 254 simulateBluetoothScoConnectionConnected()255 private void simulateBluetoothScoConnectionConnected() { 256 Intent intent = new Intent(); 257 intent.setAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); 258 intent.putExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_CONNECTED); 259 bluetoothHeadsetStateReceiver.onReceive(context, intent); 260 } 261 simulateBluetoothScoConnectionDisconnected()262 private void simulateBluetoothScoConnectionDisconnected() { 263 Intent intent = new Intent(); 264 intent.setAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); 265 intent.putExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 266 bluetoothHeadsetStateReceiver.onReceive(context, intent); 267 } 268 } 269