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