1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.a2dp; 18 19 import static org.mockito.Mockito.*; 20 21 import android.bluetooth.BluetoothA2dp; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.os.HandlerThread; 28 import android.support.test.InstrumentationRegistry; 29 import android.support.test.filters.MediumTest; 30 import android.support.test.runner.AndroidJUnit4; 31 32 import com.android.bluetooth.R; 33 import com.android.bluetooth.TestUtils; 34 import com.android.bluetooth.btservice.AdapterService; 35 36 import org.hamcrest.core.IsInstanceOf; 37 import org.junit.After; 38 import org.junit.Assert; 39 import org.junit.Assume; 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 import org.mockito.ArgumentCaptor; 44 import org.mockito.Mock; 45 import org.mockito.MockitoAnnotations; 46 47 @MediumTest 48 @RunWith(AndroidJUnit4.class) 49 public class A2dpStateMachineTest { 50 private BluetoothAdapter mAdapter; 51 private Context mTargetContext; 52 private HandlerThread mHandlerThread; 53 private A2dpStateMachine mA2dpStateMachine; 54 private BluetoothDevice mTestDevice; 55 private static final int TIMEOUT_MS = 1000; // 1s 56 57 @Mock private AdapterService mAdapterService; 58 @Mock private A2dpService mA2dpService; 59 @Mock private A2dpNativeInterface mA2dpNativeInterface; 60 61 @Before setUp()62 public void setUp() throws Exception { 63 mTargetContext = InstrumentationRegistry.getTargetContext(); 64 Assume.assumeTrue("Ignore test when A2dpService is not enabled", 65 mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp)); 66 // Set up mocks and test assets 67 MockitoAnnotations.initMocks(this); 68 TestUtils.setAdapterService(mAdapterService); 69 70 mAdapter = BluetoothAdapter.getDefaultAdapter(); 71 72 // Get a device for testing 73 mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); 74 75 // Set up thread and looper 76 mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread"); 77 mHandlerThread.start(); 78 mA2dpStateMachine = new A2dpStateMachine(mTestDevice, mA2dpService, 79 mA2dpNativeInterface, mHandlerThread.getLooper()); 80 // Override the timeout value to speed up the test 81 A2dpStateMachine.sConnectTimeoutMs = 1000; // 1s 82 mA2dpStateMachine.start(); 83 } 84 85 @After tearDown()86 public void tearDown() throws Exception { 87 if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp)) { 88 return; 89 } 90 mA2dpStateMachine.doQuit(); 91 mHandlerThread.quit(); 92 TestUtils.clearAdapterService(mAdapterService); 93 } 94 95 /** 96 * Test that default state is disconnected 97 */ 98 @Test testDefaultDisconnectedState()99 public void testDefaultDisconnectedState() { 100 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 101 mA2dpStateMachine.getConnectionState()); 102 } 103 104 /** 105 * Allow/disallow connection to any device. 106 * 107 * @param allow if true, connection is allowed 108 */ allowConnection(boolean allow)109 private void allowConnection(boolean allow) { 110 doReturn(allow).when(mA2dpService).okToConnect(any(BluetoothDevice.class), 111 anyBoolean()); 112 } 113 114 /** 115 * Test that an incoming connection with low priority is rejected 116 */ 117 @Test testIncomingPriorityReject()118 public void testIncomingPriorityReject() { 119 allowConnection(false); 120 121 // Inject an event for when incoming connection is requested 122 A2dpStackEvent connStCh = 123 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 124 connStCh.device = mTestDevice; 125 connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED; 126 mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh); 127 128 // Verify that no connection state broadcast is executed 129 verify(mA2dpService, after(TIMEOUT_MS).never()).sendBroadcast(any(Intent.class), 130 anyString()); 131 // Check that we are in Disconnected state 132 Assert.assertThat(mA2dpStateMachine.getCurrentState(), 133 IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class)); 134 } 135 136 /** 137 * Test that an incoming connection with high priority is accepted 138 */ 139 @Test testIncomingPriorityAccept()140 public void testIncomingPriorityAccept() { 141 allowConnection(true); 142 143 // Inject an event for when incoming connection is requested 144 A2dpStackEvent connStCh = 145 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 146 connStCh.device = mTestDevice; 147 connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTING; 148 mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh); 149 150 // Verify that one connection state broadcast is executed 151 ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); 152 verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(), 153 anyString()); 154 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 155 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 156 157 // Check that we are in Connecting state 158 Assert.assertThat(mA2dpStateMachine.getCurrentState(), 159 IsInstanceOf.instanceOf(A2dpStateMachine.Connecting.class)); 160 161 // Send a message to trigger connection completed 162 A2dpStackEvent connCompletedEvent = 163 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 164 connCompletedEvent.device = mTestDevice; 165 connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED; 166 mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connCompletedEvent); 167 168 // Verify that the expected number of broadcasts are executed: 169 // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected 170 // - one call to broadcastAudioState() when entering Connected state 171 ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); 172 verify(mA2dpService, timeout(TIMEOUT_MS).times(3)).sendBroadcast(intentArgument2.capture(), 173 anyString()); 174 // Verify that the last broadcast was to change the A2DP playing state 175 // to STATE_NOT_PLAYING 176 Assert.assertEquals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED, 177 intentArgument2.getValue().getAction()); 178 Assert.assertEquals(BluetoothA2dp.STATE_NOT_PLAYING, 179 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 180 // Check that we are in Connected state 181 Assert.assertThat(mA2dpStateMachine.getCurrentState(), 182 IsInstanceOf.instanceOf(A2dpStateMachine.Connected.class)); 183 } 184 185 /** 186 * Test that an outgoing connection times out 187 */ 188 @Test testOutgoingTimeout()189 public void testOutgoingTimeout() { 190 allowConnection(true); 191 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 192 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 193 194 // Send a connect request 195 mA2dpStateMachine.sendMessage(A2dpStateMachine.CONNECT, mTestDevice); 196 197 // Verify that one connection state broadcast is executed 198 ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); 199 verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(), 200 anyString()); 201 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 202 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 203 204 // Check that we are in Connecting state 205 Assert.assertThat(mA2dpStateMachine.getCurrentState(), 206 IsInstanceOf.instanceOf(A2dpStateMachine.Connecting.class)); 207 208 // Verify that one connection state broadcast is executed 209 ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); 210 verify(mA2dpService, timeout(A2dpStateMachine.sConnectTimeoutMs * 2).times( 211 2)).sendBroadcast(intentArgument2.capture(), anyString()); 212 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 213 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 214 215 // Check that we are in Disconnected state 216 Assert.assertThat(mA2dpStateMachine.getCurrentState(), 217 IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class)); 218 } 219 220 /** 221 * Test that an incoming connection times out 222 */ 223 @Test testIncomingTimeout()224 public void testIncomingTimeout() { 225 allowConnection(true); 226 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 227 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 228 229 // Inject an event for when incoming connection is requested 230 A2dpStackEvent connStCh = 231 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 232 connStCh.device = mTestDevice; 233 connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTING; 234 mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh); 235 236 // Verify that one connection state broadcast is executed 237 ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); 238 verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(), 239 anyString()); 240 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 241 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 242 243 // Check that we are in Connecting state 244 Assert.assertThat(mA2dpStateMachine.getCurrentState(), 245 IsInstanceOf.instanceOf(A2dpStateMachine.Connecting.class)); 246 247 // Verify that one connection state broadcast is executed 248 ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); 249 verify(mA2dpService, timeout(A2dpStateMachine.sConnectTimeoutMs * 2).times( 250 2)).sendBroadcast(intentArgument2.capture(), anyString()); 251 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 252 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 253 254 // Check that we are in Disconnected state 255 Assert.assertThat(mA2dpStateMachine.getCurrentState(), 256 IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class)); 257 } 258 } 259