| Index: webrtc/examples/androidjunit/src/org/appspot/apprtc/BluetoothManagerTest.java
 | 
| diff --git a/webrtc/examples/androidjunit/src/org/appspot/apprtc/BluetoothManagerTest.java b/webrtc/examples/androidjunit/src/org/appspot/apprtc/BluetoothManagerTest.java
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..0d077f37e7067dcf102a4b677ab8ec516816217e
 | 
| --- /dev/null
 | 
| +++ b/webrtc/examples/androidjunit/src/org/appspot/apprtc/BluetoothManagerTest.java
 | 
| @@ -0,0 +1,271 @@
 | 
| +/*
 | 
| + *  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);
 | 
| +  }
 | 
| +}
 | 
| 
 |