• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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.mapclient;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 import static org.mockito.Mockito.*;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothMapClient;
25 import android.bluetooth.BluetoothProfile;
26 import android.bluetooth.SdpMasRecord;
27 import android.content.ContentValues;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.database.Cursor;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.Message;
36 import android.provider.Telephony.Sms;
37 import android.telephony.SubscriptionManager;
38 import android.test.mock.MockContentProvider;
39 import android.test.mock.MockContentResolver;
40 import android.util.Log;
41 
42 import androidx.test.InstrumentationRegistry;
43 import androidx.test.filters.MediumTest;
44 import androidx.test.rule.ServiceTestRule;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import com.android.bluetooth.R;
48 import com.android.bluetooth.TestUtils;
49 import com.android.bluetooth.btservice.AdapterService;
50 import com.android.bluetooth.btservice.storage.DatabaseManager;
51 import com.android.vcard.VCardConstants;
52 import com.android.vcard.VCardEntry;
53 import com.android.vcard.VCardProperty;
54 
55 import org.junit.After;
56 import org.junit.Assert;
57 import org.junit.Assume;
58 import org.junit.Before;
59 import org.junit.Rule;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.ArgumentCaptor;
63 import org.mockito.Mock;
64 import org.mockito.Mockito;
65 import org.mockito.MockitoAnnotations;
66 
67 import java.util.HashMap;
68 import java.util.Map;
69 
70 @MediumTest
71 @RunWith(AndroidJUnit4.class)
72 public class MapClientStateMachineTest {
73 
74     private static final String TAG = "MapStateMachineTest";
75     private static final String FOLDER_SENT = "sent";
76 
77     private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
78     private static final int DISCONNECT_TIMEOUT = 3000;
79     @Rule
80     public final ServiceTestRule mServiceRule = new ServiceTestRule();
81     private BluetoothAdapter mAdapter;
82     private MceStateMachine mMceStateMachine = null;
83     private BluetoothDevice mTestDevice;
84     private Context mTargetContext;
85     private Handler mHandler;
86     private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
87     @Mock
88     private AdapterService mAdapterService;
89     @Mock
90     private DatabaseManager mDatabaseManager;
91     @Mock
92     private MapClientService mMockMapClientService;
93     private MockContentResolver mMockContentResolver;
94     private MockSmsContentProvider mMockContentProvider;
95     @Mock
96     private MasClient mMockMasClient;
97 
98     @Mock
99     private RequestPushMessage mMockRequestPushMessage;
100 
101     @Mock
102     private SubscriptionManager mMockSubscriptionManager;
103 
104     @Before
setUp()105     public void setUp() throws Exception {
106         MockitoAnnotations.initMocks(this);
107         mTargetContext = InstrumentationRegistry.getTargetContext();
108         mMockContentProvider = new MockSmsContentProvider();
109         mMockContentResolver = new MockContentResolver();
110 
111         Assume.assumeTrue("Ignore test when MapClientService is not enabled",
112                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce));
113         TestUtils.setAdapterService(mAdapterService);
114         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
115         doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
116         TestUtils.startService(mServiceRule, MapClientService.class);
117         mMockContentResolver.addProvider("sms", mMockContentProvider);
118         mMockContentResolver.addProvider("mms", mMockContentProvider);
119         mMockContentResolver.addProvider("mms-sms", mMockContentProvider);
120 
121         when(mMockMapClientService.getContentResolver()).thenReturn(mMockContentResolver);
122         when(mMockMapClientService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
123                 .thenReturn(mMockSubscriptionManager);
124 
125         doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources();
126 
127         // This line must be called to make sure relevant objects are initialized properly
128         mAdapter = BluetoothAdapter.getDefaultAdapter();
129         // Get a device for testing
130         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
131 
132         when(mMockMasClient.makeRequest(any(Request.class))).thenReturn(true);
133         mMceStateMachine = new MceStateMachine(mMockMapClientService, mTestDevice, mMockMasClient);
134         Assert.assertNotNull(mMceStateMachine);
135         if (Looper.myLooper() == null) {
136             Looper.prepare();
137         }
138         mHandler = new Handler();
139     }
140 
141     @After
tearDown()142     public void tearDown() throws Exception {
143         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)) {
144             return;
145         }
146 
147         if (mMceStateMachine != null) {
148             mMceStateMachine.doQuit();
149         }
150         TestUtils.stopService(mServiceRule, MapClientService.class);
151         TestUtils.clearAdapterService(mAdapterService);
152     }
153 
154     /**
155      * Test that default state is STATE_CONNECTING
156      */
157     @Test
testDefaultConnectingState()158     public void testDefaultConnectingState() {
159         Log.i(TAG, "in testDefaultConnectingState");
160         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());
161     }
162 
163     /**
164      * Test transition from STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) -->
165      * STATE_DISCONNECTED
166      */
167     @Test
testStateTransitionFromConnectingToDisconnected()168     public void testStateTransitionFromConnectingToDisconnected() {
169         Log.i(TAG, "in testStateTransitionFromConnectingToDisconnected");
170         setupSdpRecordReceipt();
171         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
172         mMceStateMachine.sendMessage(msg);
173 
174         // Wait until the message is processed and a broadcast request is sent to
175         // to MapClientService to change
176         // state from STATE_CONNECTING to STATE_DISCONNECTED
177         verify(mMockMapClientService,
178                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
179                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
180                 any(Bundle.class));
181         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
182     }
183 
184     /**
185      * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED
186      */
187     @Test
testStateTransitionFromConnectingToConnected()188     public void testStateTransitionFromConnectingToConnected() {
189         Log.i(TAG, "in testStateTransitionFromConnectingToConnected");
190 
191         setupSdpRecordReceipt();
192         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
193         mMceStateMachine.sendMessage(msg);
194 
195         // Wait until the message is processed and a broadcast request is sent to
196         // to MapClientService to change
197         // state from STATE_CONNECTING to STATE_CONNECTED
198         verify(mMockMapClientService,
199                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
200                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
201                 any(Bundle.class));
202         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
203     }
204 
205     /**
206      * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED -->
207      * (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
208      */
209     @Test
testStateTransitionFromConnectedWithMasDisconnected()210     public void testStateTransitionFromConnectedWithMasDisconnected() {
211         Log.i(TAG, "in testStateTransitionFromConnectedWithMasDisconnected");
212 
213         setupSdpRecordReceipt();
214         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
215         mMceStateMachine.sendMessage(msg);
216 
217         // Wait until the message is processed and a broadcast request is sent to
218         // to MapClientService to change
219         // state from STATE_CONNECTING to STATE_CONNECTED
220         verify(mMockMapClientService,
221                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
222                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
223                 any(Bundle.class));
224         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
225 
226         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
227         mMceStateMachine.sendMessage(msg);
228         verify(mMockMapClientService,
229                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcast(
230                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
231                 any(Bundle.class));
232 
233         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
234     }
235 
236 
237     /**
238      * Test receiving an empty event report
239      */
240     @Test
testReceiveEmptyEvent()241     public void testReceiveEmptyEvent() {
242         setupSdpRecordReceipt();
243         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
244         mMceStateMachine.sendMessage(msg);
245 
246         // Wait until the message is processed and a broadcast request is sent to
247         // to MapClientService to change
248         // state from STATE_CONNECTING to STATE_CONNECTED
249         verify(mMockMapClientService,
250                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
251                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
252                 any(Bundle.class));
253         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
254 
255         // Send an empty notification event, verify the mMceStateMachine is still connected
256         Message notification = Message.obtain(mHandler, MceStateMachine.MSG_NOTIFICATION);
257         mMceStateMachine.getCurrentState().processMessage(msg);
258         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
259     }
260 
261     /**
262      * Test set message status
263      */
264     @Test
testSetMessageStatus()265     public void testSetMessageStatus() {
266         setupSdpRecordReceipt();
267         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
268         mMceStateMachine.sendMessage(msg);
269 
270         // Wait until the message is processed and a broadcast request is sent to
271         // to MapClientService to change
272         // state from STATE_CONNECTING to STATE_CONNECTED
273         verify(mMockMapClientService,
274                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
275                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
276                 any(Bundle.class));
277         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
278         Assert.assertTrue(
279                 mMceStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ));
280     }
281 
282 
283     /**
284      * Test disconnect
285      */
286     @Test
testDisconnect()287     public void testDisconnect() {
288         setupSdpRecordReceipt();
289         doAnswer(invocation -> {
290             mMceStateMachine.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED);
291             return null;
292         }).when(mMockMasClient).shutdown();
293         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
294         mMceStateMachine.sendMessage(msg);
295 
296         // Wait until the message is processed and a broadcast request is sent to
297         // to MapClientService to change
298         // state from STATE_CONNECTING to STATE_CONNECTED
299         verify(mMockMapClientService,
300                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
301                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
302                 any(Bundle.class));
303         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
304 
305         mMceStateMachine.disconnect();
306         verify(mMockMapClientService,
307                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcast(
308                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
309                 any(Bundle.class));
310         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
311     }
312 
313     /**
314      * Test disconnect timeout
315      */
316     @Test
testDisconnectTimeout()317     public void testDisconnectTimeout() {
318         setupSdpRecordReceipt();
319         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
320         mMceStateMachine.sendMessage(msg);
321 
322         // Wait until the message is processed and a broadcast request is sent to
323         // to MapClientService to change
324         // state from STATE_CONNECTING to STATE_CONNECTED
325         verify(mMockMapClientService,
326                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
327                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
328                 any(Bundle.class));
329         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
330 
331         mMceStateMachine.disconnect();
332         verify(mMockMapClientService,
333                 after(DISCONNECT_TIMEOUT / 2).times(3)).sendBroadcast(
334                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
335                 any(Bundle.class));
336         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, mMceStateMachine.getState());
337 
338         verify(mMockMapClientService,
339                 timeout(DISCONNECT_TIMEOUT).times(4)).sendBroadcast(
340                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
341                 any(Bundle.class));
342         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
343     }
344 
345     /**
346      * Test sending a message
347      */
348     @Test
testSendSMSMessage()349     public void testSendSMSMessage() {
350         setupSdpRecordReceipt();
351         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
352         mMceStateMachine.sendMessage(msg);
353         TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
354         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
355 
356         String testMessage = "Hello World!";
357         Uri[] contacts = new Uri[] {Uri.parse("tel://5551212")};
358 
359         verify(mMockMasClient, times(0)).makeRequest(any(RequestPushMessage.class));
360         mMceStateMachine.sendMapMessage(contacts, testMessage, null, null);
361         verify(mMockMasClient, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
362                 .makeRequest(any(RequestPushMessage.class));
363     }
364 
365     /**
366      * Test message sent successfully
367      */
368     @Test
testSMSMessageSent()369     public void testSMSMessageSent() {
370         setupSdpRecordReceipt();
371         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
372         mMceStateMachine.sendMessage(msg);
373         TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
374         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
375 
376         String testMessage = "Hello World!";
377         VCardEntry recipient = new VCardEntry();
378         VCardProperty property = new VCardProperty();
379         property.setName(VCardConstants.PROPERTY_TEL);
380         property.addValues("555-1212");
381         recipient.addProperty(property);
382         Bmessage testBmessage = new Bmessage();
383         testBmessage.setType(Bmessage.Type.SMS_GSM);
384         testBmessage.setBodyContent(testMessage);
385         testBmessage.addRecipient(recipient);
386         RequestPushMessage testRequest =
387                 new RequestPushMessage(FOLDER_SENT, testBmessage, null, false, false);
388         when(mMockRequestPushMessage.getMsgHandle()).thenReturn("12345");
389         when(mMockRequestPushMessage.getBMsg()).thenReturn(testBmessage);
390         Message msgSent = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
391                 mMockRequestPushMessage);
392 
393         mMceStateMachine.sendMessage(msgSent);
394         TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
395         Assert.assertEquals(1, mMockContentProvider.mInsertOperationCount);
396     }
397 
setupSdpRecordReceipt()398     private void setupSdpRecordReceipt() {
399         // Perform first part of MAP connection logic.
400         verify(mMockMapClientService,
401                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcast(
402                 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
403                 any(Bundle.class));
404         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());
405 
406         // Setup receipt of SDP record
407         SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord");
408         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record);
409         mMceStateMachine.sendMessage(msg);
410     }
411 
412     private class MockSmsContentProvider extends MockContentProvider {
413         Map<Uri, ContentValues> mContentValues = new HashMap<>();
414         int mInsertOperationCount = 0;
415 
416         @Override
delete(Uri uri, String selection, String[] selectionArgs)417         public int delete(Uri uri, String selection, String[] selectionArgs) {
418             return 0;
419         }
420 
421         @Override
insert(Uri uri, ContentValues values)422         public Uri insert(Uri uri, ContentValues values) {
423             mInsertOperationCount++;
424             return Uri.withAppendedPath(Sms.CONTENT_URI, String.valueOf(mInsertOperationCount));
425         }
426 
427         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)428         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
429                 String sortOrder) {
430             Cursor cursor = Mockito.mock(Cursor.class);
431 
432             when(cursor.moveToFirst()).thenReturn(true);
433             when(cursor.moveToNext()).thenReturn(true).thenReturn(false);
434 
435             when(cursor.getLong(anyInt())).thenReturn((long) mContentValues.size());
436             when(cursor.getString(anyInt())).thenReturn(String.valueOf(mContentValues.size()));
437             return cursor;
438         }
439     }
440 
441 }
442