• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.hearingaid;
18 
19 import static org.mockito.Mockito.*;
20 
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothProfile;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.os.HandlerThread;
27 import android.support.test.InstrumentationRegistry;
28 import android.support.test.filters.MediumTest;
29 import android.support.test.runner.AndroidJUnit4;
30 
31 import com.android.bluetooth.R;
32 import com.android.bluetooth.TestUtils;
33 import com.android.bluetooth.btservice.AdapterService;
34 
35 import org.hamcrest.core.IsInstanceOf;
36 import org.junit.After;
37 import org.junit.Assert;
38 import org.junit.Assume;
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.mockito.ArgumentCaptor;
43 import org.mockito.Mock;
44 import org.mockito.MockitoAnnotations;
45 
46 @MediumTest
47 @RunWith(AndroidJUnit4.class)
48 public class HearingAidStateMachineTest {
49     private BluetoothAdapter mAdapter;
50     private Context mTargetContext;
51     private HandlerThread mHandlerThread;
52     private HearingAidStateMachine mHearingAidStateMachine;
53     private BluetoothDevice mTestDevice;
54     private static final int TIMEOUT_MS = 1000;
55 
56     @Mock private AdapterService mAdapterService;
57     @Mock private HearingAidService mHearingAidService;
58     @Mock private HearingAidNativeInterface mHearingAidNativeInterface;
59 
60     @Before
setUp()61     public void setUp() throws Exception {
62         mTargetContext = InstrumentationRegistry.getTargetContext();
63         Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
64                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
65         // Set up mocks and test assets
66         MockitoAnnotations.initMocks(this);
67         TestUtils.setAdapterService(mAdapterService);
68 
69         mAdapter = BluetoothAdapter.getDefaultAdapter();
70 
71         // Get a device for testing
72         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
73 
74         // Set up thread and looper
75         mHandlerThread = new HandlerThread("HearingAidStateMachineTestHandlerThread");
76         mHandlerThread.start();
77         mHearingAidStateMachine = new HearingAidStateMachine(mTestDevice, mHearingAidService,
78                 mHearingAidNativeInterface, mHandlerThread.getLooper());
79         // Override the timeout value to speed up the test
80         mHearingAidStateMachine.sConnectTimeoutMs = 1000;     // 1s
81         mHearingAidStateMachine.start();
82     }
83 
84     @After
tearDown()85     public void tearDown() throws Exception {
86         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid)) {
87             return;
88         }
89         mHearingAidStateMachine.doQuit();
90         mHandlerThread.quit();
91         TestUtils.clearAdapterService(mAdapterService);
92     }
93 
94     /**
95      * Test that default state is disconnected
96      */
97     @Test
testDefaultDisconnectedState()98     public void testDefaultDisconnectedState() {
99         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
100                 mHearingAidStateMachine.getConnectionState());
101     }
102 
103     /**
104      * Allow/disallow connection to any device.
105      *
106      * @param allow if true, connection is allowed
107      */
allowConnection(boolean allow)108     private void allowConnection(boolean allow) {
109         doReturn(allow).when(mHearingAidService).okToConnect(any(BluetoothDevice.class));
110     }
111 
112     /**
113      * Test that an incoming connection with low priority is rejected
114      */
115     @Test
testIncomingPriorityReject()116     public void testIncomingPriorityReject() {
117         allowConnection(false);
118 
119         // Inject an event for when incoming connection is requested
120         HearingAidStackEvent connStCh =
121                 new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
122         connStCh.device = mTestDevice;
123         connStCh.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
124         mHearingAidStateMachine.sendMessage(HearingAidStateMachine.STACK_EVENT, connStCh);
125 
126         // Verify that no connection state broadcast is executed
127         verify(mHearingAidService, after(TIMEOUT_MS).never()).sendBroadcast(any(Intent.class),
128                 anyString());
129         // Check that we are in Disconnected state
130         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
131                 IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
132     }
133 
134     /**
135      * Test that an incoming connection with high priority is accepted
136      */
137     @Test
testIncomingPriorityAccept()138     public void testIncomingPriorityAccept() {
139         allowConnection(true);
140 
141         // Inject an event for when incoming connection is requested
142         HearingAidStackEvent connStCh =
143                 new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
144         connStCh.device = mTestDevice;
145         connStCh.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTING;
146         mHearingAidStateMachine.sendMessage(HearingAidStateMachine.STACK_EVENT, connStCh);
147 
148         // Verify that one connection state broadcast is executed
149         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
150         verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
151                 intentArgument1.capture(), anyString());
152         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
153                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
154 
155         // Check that we are in Connecting state
156         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
157                 IsInstanceOf.instanceOf(HearingAidStateMachine.Connecting.class));
158 
159         // Send a message to trigger connection completed
160         HearingAidStackEvent connCompletedEvent =
161                 new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
162         connCompletedEvent.device = mTestDevice;
163         connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
164         mHearingAidStateMachine.sendMessage(HearingAidStateMachine.STACK_EVENT, connCompletedEvent);
165 
166         // Verify that the expected number of broadcasts are executed:
167         // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
168         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
169         verify(mHearingAidService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(
170                 intentArgument2.capture(), anyString());
171         // Check that we are in Connected state
172         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
173                 IsInstanceOf.instanceOf(HearingAidStateMachine.Connected.class));
174     }
175 
176     /**
177      * Test that an outgoing connection times out
178      */
179     @Test
testOutgoingTimeout()180     public void testOutgoingTimeout() {
181         allowConnection(true);
182         doReturn(true).when(mHearingAidNativeInterface).connectHearingAid(any(
183                 BluetoothDevice.class));
184         doReturn(true).when(mHearingAidNativeInterface).disconnectHearingAid(any(
185                 BluetoothDevice.class));
186 
187         // Send a connect request
188         mHearingAidStateMachine.sendMessage(HearingAidStateMachine.CONNECT, mTestDevice);
189 
190         // Verify that one connection state broadcast is executed
191         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
192         verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
193                 intentArgument1.capture(),
194                 anyString());
195         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
196                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
197 
198         // Check that we are in Connecting state
199         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
200                 IsInstanceOf.instanceOf(HearingAidStateMachine.Connecting.class));
201 
202         // Verify that one connection state broadcast is executed
203         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
204         verify(mHearingAidService, timeout(HearingAidStateMachine.sConnectTimeoutMs * 2).times(
205                 2)).sendBroadcast(intentArgument2.capture(), anyString());
206         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
207                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
208 
209         // Check that we are in Disconnected state
210         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
211                 IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
212     }
213 
214     /**
215      * Test that an incoming connection times out
216      */
217     @Test
testIncomingTimeout()218     public void testIncomingTimeout() {
219         allowConnection(true);
220         doReturn(true).when(mHearingAidNativeInterface).connectHearingAid(any(
221                 BluetoothDevice.class));
222         doReturn(true).when(mHearingAidNativeInterface).disconnectHearingAid(any(
223                 BluetoothDevice.class));
224 
225         // Inject an event for when incoming connection is requested
226         HearingAidStackEvent connStCh =
227                 new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
228         connStCh.device = mTestDevice;
229         connStCh.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTING;
230         mHearingAidStateMachine.sendMessage(HearingAidStateMachine.STACK_EVENT, connStCh);
231 
232         // Verify that one connection state broadcast is executed
233         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
234         verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
235                 intentArgument1.capture(),
236                 anyString());
237         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
238                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
239 
240         // Check that we are in Connecting state
241         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
242                 IsInstanceOf.instanceOf(HearingAidStateMachine.Connecting.class));
243 
244         // Verify that one connection state broadcast is executed
245         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
246         verify(mHearingAidService, timeout(HearingAidStateMachine.sConnectTimeoutMs * 2).times(
247                 2)).sendBroadcast(intentArgument2.capture(), anyString());
248         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
249                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
250 
251         // Check that we are in Disconnected state
252         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
253                 IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
254     }
255 }
256