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