blob: 0d077f37e7067dcf102a4b677ab8ec516816217e [file] [log] [blame]
/*
* 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.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertFalse;
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.LinkedList;
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 LinkedList<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 (permission == android.Manifest.permission.BLUETOOTH);
}
@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);
}
}