• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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