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