|  | /* | 
|  | *  Copyright 2016 The WebRTC Project Authors. All rights reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | package org.appspot.apprtc; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertFalse; | 
|  | import static org.junit.Assert.assertNotNull; | 
|  | import static org.junit.Assert.assertNull; | 
|  | import static org.junit.Assert.assertTrue; | 
|  | import static org.mockito.Mockito.doCallRealMethod; | 
|  | import static org.mockito.Mockito.mock; | 
|  | import static org.mockito.Mockito.never; | 
|  | import static org.mockito.Mockito.times; | 
|  | import static org.mockito.Mockito.verify; | 
|  | import static org.mockito.Mockito.when; | 
|  |  | 
|  | import android.bluetooth.BluetoothAdapter; | 
|  | import android.bluetooth.BluetoothDevice; | 
|  | import android.bluetooth.BluetoothHeadset; | 
|  | import android.bluetooth.BluetoothProfile; | 
|  | import android.content.BroadcastReceiver; | 
|  | import android.content.Context; | 
|  | import android.content.Intent; | 
|  | import android.content.IntentFilter; | 
|  | import android.media.AudioManager; | 
|  | import android.util.Log; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  | import org.appspot.apprtc.AppRTCBluetoothManager.State; | 
|  | import org.chromium.testing.local.LocalRobolectricTestRunner; | 
|  | import org.junit.Before; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.robolectric.annotation.Config; | 
|  | import org.robolectric.shadows.ShadowApplication; | 
|  | import org.robolectric.shadows.ShadowLog; | 
|  |  | 
|  | /** | 
|  | * Verifies basic behavior of the AppRTCBluetoothManager class. | 
|  | * Note that the test object uses an AppRTCAudioManager (injected in ctor), | 
|  | * but a mocked version is used instead. Hence, the parts "driven" by the AppRTC | 
|  | * audio manager are not included in this test. | 
|  | */ | 
|  | @RunWith(LocalRobolectricTestRunner.class) | 
|  | @Config(manifest = Config.NONE) | 
|  | public class BluetoothManagerTest { | 
|  | private static final String TAG = "BluetoothManagerTest"; | 
|  | private static final String BLUETOOTH_TEST_DEVICE_NAME = "BluetoothTestDevice"; | 
|  |  | 
|  | private BroadcastReceiver bluetoothHeadsetStateReceiver; | 
|  | private BluetoothProfile.ServiceListener bluetoothServiceListener; | 
|  | private BluetoothHeadset mockedBluetoothHeadset; | 
|  | private BluetoothDevice mockedBluetoothDevice; | 
|  | private List<BluetoothDevice> mockedBluetoothDeviceList; | 
|  | private AppRTCBluetoothManager bluetoothManager; | 
|  | private AppRTCAudioManager mockedAppRtcAudioManager; | 
|  | private AudioManager mockedAudioManager; | 
|  | private Context context; | 
|  |  | 
|  | @Before | 
|  | public void setUp() { | 
|  | ShadowLog.stream = System.out; | 
|  | context = ShadowApplication.getInstance().getApplicationContext(); | 
|  | mockedAppRtcAudioManager = mock(AppRTCAudioManager.class); | 
|  | mockedAudioManager = mock(AudioManager.class); | 
|  | mockedBluetoothHeadset = mock(BluetoothHeadset.class); | 
|  | mockedBluetoothDevice = mock(BluetoothDevice.class); | 
|  | mockedBluetoothDeviceList = new ArrayList<BluetoothDevice>(); | 
|  |  | 
|  | // Simulate that bluetooth SCO audio is available by default. | 
|  | when(mockedAudioManager.isBluetoothScoAvailableOffCall()).thenReturn(true); | 
|  |  | 
|  | // Create the test object and override protected methods for this test. | 
|  | bluetoothManager = new AppRTCBluetoothManager(context, mockedAppRtcAudioManager) { | 
|  | @Override | 
|  | protected AudioManager getAudioManager(Context context) { | 
|  | Log.d(TAG, "getAudioManager"); | 
|  | return mockedAudioManager; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { | 
|  | Log.d(TAG, "registerReceiver"); | 
|  | if (filter.hasAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED) | 
|  | && filter.hasAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { | 
|  | // Gives access to the real broadcast receiver so the test can use it. | 
|  | bluetoothHeadsetStateReceiver = receiver; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void unregisterReceiver(BroadcastReceiver receiver) { | 
|  | Log.d(TAG, "unregisterReceiver"); | 
|  | if (receiver == bluetoothHeadsetStateReceiver) { | 
|  | bluetoothHeadsetStateReceiver = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected boolean getBluetoothProfileProxy( | 
|  | Context context, BluetoothProfile.ServiceListener listener, int profile) { | 
|  | Log.d(TAG, "getBluetoothProfileProxy"); | 
|  | if (profile == BluetoothProfile.HEADSET) { | 
|  | // Allows the test to access the real Bluetooth service listener object. | 
|  | bluetoothServiceListener = listener; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected boolean hasPermission(Context context, String permission) { | 
|  | Log.d(TAG, "hasPermission(" + permission + ")"); | 
|  | // Ensure that the client asks for Bluetooth permission. | 
|  | return android.Manifest.permission.BLUETOOTH.equals(permission); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) { | 
|  | // Do nothing in tests. No need to mock BluetoothAdapter. | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | // Verify that Bluetooth service listener for headset profile is properly initialized. | 
|  | @Test | 
|  | public void testBluetoothServiceListenerInitialized() { | 
|  | bluetoothManager.start(); | 
|  | assertNotNull(bluetoothServiceListener); | 
|  | verify(mockedAppRtcAudioManager, never()).updateAudioDeviceState(); | 
|  | } | 
|  |  | 
|  | // Verify that broadcast receivers for Bluetooth SCO audio state and Bluetooth headset state | 
|  | // are properly registered and unregistered. | 
|  | @Test | 
|  | public void testBluetoothBroadcastReceiversAreRegistered() { | 
|  | bluetoothManager.start(); | 
|  | assertNotNull(bluetoothHeadsetStateReceiver); | 
|  | bluetoothManager.stop(); | 
|  | assertNull(bluetoothHeadsetStateReceiver); | 
|  | } | 
|  |  | 
|  | // Verify that the Bluetooth manager starts and stops with correct states. | 
|  | @Test | 
|  | public void testBluetoothDefaultStartStopStates() { | 
|  | bluetoothManager.start(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); | 
|  | bluetoothManager.stop(); | 
|  | assertEquals(bluetoothManager.getState(), State.UNINITIALIZED); | 
|  | } | 
|  |  | 
|  | // Verify correct state after receiving BluetoothServiceListener.onServiceConnected() | 
|  | // when no BT device is enabled. | 
|  | @Test | 
|  | public void testBluetoothServiceListenerConnectedWithNoHeadset() { | 
|  | bluetoothManager.start(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); | 
|  | simulateBluetoothServiceConnectedWithNoConnectedHeadset(); | 
|  | verify(mockedAppRtcAudioManager, times(1)).updateAudioDeviceState(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); | 
|  | } | 
|  |  | 
|  | // Verify correct state after receiving BluetoothServiceListener.onServiceConnected() | 
|  | // when one emulated (test) BT device is enabled. Android does not support more than | 
|  | // one connected BT headset. | 
|  | @Test | 
|  | public void testBluetoothServiceListenerConnectedWithHeadset() { | 
|  | bluetoothManager.start(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); | 
|  | simulateBluetoothServiceConnectedWithConnectedHeadset(); | 
|  | verify(mockedAppRtcAudioManager, times(1)).updateAudioDeviceState(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_AVAILABLE); | 
|  | } | 
|  |  | 
|  | // Verify correct state after receiving BluetoothProfile.ServiceListener.onServiceDisconnected(). | 
|  | @Test | 
|  | public void testBluetoothServiceListenerDisconnected() { | 
|  | bluetoothManager.start(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); | 
|  | simulateBluetoothServiceDisconnected(); | 
|  | verify(mockedAppRtcAudioManager, times(1)).updateAudioDeviceState(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); | 
|  | } | 
|  |  | 
|  | // Verify correct state after BluetoothServiceListener.onServiceConnected() and | 
|  | // the intent indicating that the headset is actually connected. Both these callbacks | 
|  | // results in calls to updateAudioDeviceState() on the AppRTC audio manager. | 
|  | // No BT SCO is enabled here to keep the test limited. | 
|  | @Test | 
|  | public void testBluetoothHeadsetConnected() { | 
|  | bluetoothManager.start(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); | 
|  | simulateBluetoothServiceConnectedWithConnectedHeadset(); | 
|  | simulateBluetoothHeadsetConnected(); | 
|  | verify(mockedAppRtcAudioManager, times(2)).updateAudioDeviceState(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_AVAILABLE); | 
|  | } | 
|  |  | 
|  | // Verify correct state sequence for a case when a BT headset is available, | 
|  | // followed by BT SCO audio being enabled and then stopped. | 
|  | @Test | 
|  | public void testBluetoothScoAudioStartAndStop() { | 
|  | bluetoothManager.start(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_UNAVAILABLE); | 
|  | simulateBluetoothServiceConnectedWithConnectedHeadset(); | 
|  | assertEquals(bluetoothManager.getState(), State.HEADSET_AVAILABLE); | 
|  | bluetoothManager.startScoAudio(); | 
|  | assertEquals(bluetoothManager.getState(), State.SCO_CONNECTING); | 
|  | simulateBluetoothScoConnectionConnected(); | 
|  | assertEquals(bluetoothManager.getState(), State.SCO_CONNECTED); | 
|  | bluetoothManager.stopScoAudio(); | 
|  | simulateBluetoothScoConnectionDisconnected(); | 
|  | assertEquals(bluetoothManager.getState(), State.SCO_DISCONNECTING); | 
|  | bluetoothManager.stop(); | 
|  | assertEquals(bluetoothManager.getState(), State.UNINITIALIZED); | 
|  | verify(mockedAppRtcAudioManager, times(3)).updateAudioDeviceState(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Private helper methods. | 
|  | */ | 
|  | private void simulateBluetoothServiceConnectedWithNoConnectedHeadset() { | 
|  | mockedBluetoothDeviceList.clear(); | 
|  | when(mockedBluetoothHeadset.getConnectedDevices()).thenReturn(mockedBluetoothDeviceList); | 
|  | bluetoothServiceListener.onServiceConnected(BluetoothProfile.HEADSET, mockedBluetoothHeadset); | 
|  | // In real life, the AppRTC audio manager makes this call. | 
|  | bluetoothManager.updateDevice(); | 
|  | } | 
|  |  | 
|  | private void simulateBluetoothServiceConnectedWithConnectedHeadset() { | 
|  | mockedBluetoothDeviceList.clear(); | 
|  | mockedBluetoothDeviceList.add(mockedBluetoothDevice); | 
|  | when(mockedBluetoothHeadset.getConnectedDevices()).thenReturn(mockedBluetoothDeviceList); | 
|  | when(mockedBluetoothDevice.getName()).thenReturn(BLUETOOTH_TEST_DEVICE_NAME); | 
|  | bluetoothServiceListener.onServiceConnected(BluetoothProfile.HEADSET, mockedBluetoothHeadset); | 
|  | // In real life, the AppRTC audio manager makes this call. | 
|  | bluetoothManager.updateDevice(); | 
|  | } | 
|  |  | 
|  | private void simulateBluetoothServiceDisconnected() { | 
|  | bluetoothServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); | 
|  | } | 
|  |  | 
|  | private void simulateBluetoothHeadsetConnected() { | 
|  | Intent intent = new Intent(); | 
|  | intent.setAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); | 
|  | intent.putExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_CONNECTED); | 
|  | bluetoothHeadsetStateReceiver.onReceive(context, intent); | 
|  | } | 
|  |  | 
|  | private void simulateBluetoothScoConnectionConnected() { | 
|  | Intent intent = new Intent(); | 
|  | intent.setAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); | 
|  | intent.putExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_CONNECTED); | 
|  | bluetoothHeadsetStateReceiver.onReceive(context, intent); | 
|  | } | 
|  |  | 
|  | private void simulateBluetoothScoConnectionDisconnected() { | 
|  | Intent intent = new Intent(); | 
|  | intent.setAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); | 
|  | intent.putExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED); | 
|  | bluetoothHeadsetStateReceiver.onReceive(context, intent); | 
|  | } | 
|  | } |