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 com.google.common.truth.Truth.assertThat; 22 23 import static org.mockito.ArgumentMatchers.eq; 24 import static org.mockito.Mockito.*; 25 26 import android.annotation.Nullable; 27 import android.app.BroadcastOptions; 28 import android.bluetooth.BluetoothAdapter; 29 import android.bluetooth.BluetoothDevice; 30 import android.bluetooth.BluetoothMapClient; 31 import android.bluetooth.BluetoothProfile; 32 import android.bluetooth.SdpMasRecord; 33 import android.content.ContentValues; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.database.Cursor; 37 import android.net.Uri; 38 import android.os.Handler; 39 import android.os.Looper; 40 import android.os.Message; 41 import android.provider.Telephony.Sms; 42 import android.provider.Telephony.Mms; 43 import android.telephony.TelephonyManager; 44 import android.telephony.SubscriptionManager; 45 import android.test.mock.MockContentProvider; 46 import android.test.mock.MockContentResolver; 47 import android.util.Log; 48 49 import androidx.test.InstrumentationRegistry; 50 import androidx.test.filters.MediumTest; 51 import androidx.test.rule.ServiceTestRule; 52 import androidx.test.runner.AndroidJUnit4; 53 54 import com.android.bluetooth.R; 55 import com.android.bluetooth.TestUtils; 56 import com.android.bluetooth.btservice.AdapterService; 57 import com.android.bluetooth.btservice.storage.DatabaseManager; 58 import com.android.obex.HeaderSet; 59 import com.android.vcard.VCardConstants; 60 import com.android.vcard.VCardEntry; 61 import com.android.vcard.VCardProperty; 62 63 import com.google.common.truth.Correspondence; 64 65 import org.junit.After; 66 import org.junit.Assert; 67 import org.junit.Assume; 68 import org.junit.Before; 69 import org.junit.Rule; 70 import org.junit.Test; 71 import org.junit.runner.RunWith; 72 import org.mockito.ArgumentCaptor; 73 import org.mockito.Mock; 74 import org.mockito.Mockito; 75 import org.mockito.MockitoAnnotations; 76 77 import java.time.Instant; 78 import java.util.ArrayList; 79 import java.util.Date; 80 import java.util.HashMap; 81 import java.util.List; 82 import java.util.Map; 83 84 @MediumTest 85 @RunWith(AndroidJUnit4.class) 86 public class MapClientStateMachineTest { 87 88 private static final String TAG = "MapStateMachineTest"; 89 private static final String FOLDER_SENT = "sent"; 90 91 private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100; 92 private static final int DISCONNECT_TIMEOUT = 3000; 93 94 private Bmessage mTestIncomingSmsBmessage; 95 private Bmessage mTestIncomingMmsBmessage; 96 private String mTestMessageSmsHandle = "0001"; 97 private String mTestMessageMmsHandle = "0002"; 98 99 private static final boolean MESSAGE_SEEN = true; 100 private static final boolean MESSAGE_NOT_SEEN = false; 101 102 private VCardEntry mOriginator; 103 104 @Rule 105 public final ServiceTestRule mServiceRule = new ServiceTestRule(); 106 private BluetoothAdapter mAdapter; 107 private MceStateMachine mMceStateMachine = null; 108 private BluetoothDevice mTestDevice; 109 private Context mTargetContext; 110 private Handler mHandler; 111 private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class); 112 @Mock 113 private AdapterService mAdapterService; 114 @Mock 115 private DatabaseManager mDatabaseManager; 116 @Mock 117 private MapClientService mMockMapClientService; 118 @Mock 119 private MapClientContent mMockDatabase; 120 private MockContentResolver mMockContentResolver; 121 private MockSmsContentProvider mMockContentProvider; 122 123 @Mock 124 private TelephonyManager mMockTelephonyManager; 125 126 @Mock 127 private MasClient mMockMasClient; 128 129 @Mock 130 private RequestPushMessage mMockRequestPushMessage; 131 132 @Mock 133 private SubscriptionManager mMockSubscriptionManager; 134 135 private static final String TEST_OWN_PHONE_NUMBER = "555-1234"; 136 @Mock 137 private RequestGetMessagesListingForOwnNumber mMockRequestOwnNumberCompletedWithNumber; 138 @Mock 139 private RequestGetMessagesListingForOwnNumber mMockRequestOwnNumberIncompleteSearch; 140 @Mock 141 private RequestGetMessage mMockRequestGetMessage; 142 @Mock 143 private RequestGetMessagesListing mMockRequestGetMessagesListing; 144 145 private static final Correspondence<Request, String> GET_FOLDER_NAME = 146 Correspondence.transforming( 147 MapClientStateMachineTest::getFolderNameFromRequestGetMessagesListing, 148 "has folder name of"); 149 150 @Before setUp()151 public void setUp() throws Exception { 152 mTargetContext = InstrumentationRegistry.getTargetContext(); 153 MockitoAnnotations.initMocks(this); 154 mMockContentProvider = new MockSmsContentProvider(); 155 mMockContentResolver = new MockContentResolver(); 156 TestUtils.setAdapterService(mAdapterService); 157 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 158 doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); 159 TestUtils.startService(mServiceRule, MapClientService.class); 160 mMockContentResolver.addProvider("sms", mMockContentProvider); 161 mMockContentResolver.addProvider("mms", mMockContentProvider); 162 mMockContentResolver.addProvider("mms-sms", mMockContentProvider); 163 164 when(mMockMapClientService.getContentResolver()).thenReturn(mMockContentResolver); 165 when(mMockMapClientService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)) 166 .thenReturn(mMockSubscriptionManager); 167 when(mMockMapClientService.getSystemServiceName(SubscriptionManager.class)) 168 .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE); 169 170 doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources(); 171 172 // This line must be called to make sure relevant objects are initialized properly 173 mAdapter = BluetoothAdapter.getDefaultAdapter(); 174 // Get a device for testing 175 mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); 176 177 when(mMockMasClient.makeRequest(any(Request.class))).thenReturn(true); 178 mMceStateMachine = new MceStateMachine(mMockMapClientService, mTestDevice, mMockMasClient, 179 mMockDatabase); 180 TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper()); 181 Assert.assertNotNull(mMceStateMachine); 182 if (Looper.myLooper() == null) { 183 Looper.prepare(); 184 } 185 mHandler = new Handler(); 186 187 when(mMockRequestOwnNumberCompletedWithNumber.isSearchCompleted()).thenReturn(true); 188 when(mMockRequestOwnNumberCompletedWithNumber.getOwnNumber()).thenReturn( 189 TEST_OWN_PHONE_NUMBER); 190 when(mMockRequestOwnNumberIncompleteSearch.isSearchCompleted()).thenReturn(false); 191 when(mMockRequestOwnNumberIncompleteSearch.getOwnNumber()).thenReturn(null); 192 193 createTestMessages(); 194 195 when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingSmsBmessage); 196 when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageSmsHandle); 197 198 when(mMockMapClientService.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn( 199 mMockTelephonyManager); 200 when(mMockTelephonyManager.isSmsCapable()).thenReturn(false); 201 202 } 203 204 @After tearDown()205 public void tearDown() throws Exception { 206 if (mMceStateMachine != null) { 207 mMceStateMachine.doQuit(); 208 } 209 TestUtils.stopService(mServiceRule, MapClientService.class); 210 TestUtils.clearAdapterService(mAdapterService); 211 } 212 213 /** 214 * Test that default state is STATE_CONNECTING 215 */ 216 @Test testDefaultConnectingState()217 public void testDefaultConnectingState() { 218 Log.i(TAG, "in testDefaultConnectingState"); 219 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState()); 220 } 221 222 /** 223 * Test transition from STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) --> 224 * STATE_DISCONNECTED 225 */ 226 @Test testStateTransitionFromConnectingToDisconnected()227 public void testStateTransitionFromConnectingToDisconnected() { 228 Log.i(TAG, "in testStateTransitionFromConnectingToDisconnected"); 229 setupSdpRecordReceipt(); 230 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED); 231 mMceStateMachine.sendMessage(msg); 232 233 // Wait until the message is processed and a broadcast request is sent to 234 // to MapClientService to change 235 // state from STATE_CONNECTING to STATE_DISCONNECTED 236 verify(mMockMapClientService, 237 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 238 mIntentArgument.capture(), any(String[].class), 239 any(BroadcastOptions.class)); 240 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); 241 } 242 243 /** 244 * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED 245 */ 246 @Test testStateTransitionFromConnectingToConnected()247 public void testStateTransitionFromConnectingToConnected() { 248 Log.i(TAG, "in testStateTransitionFromConnectingToConnected"); 249 250 setupSdpRecordReceipt(); 251 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 252 mMceStateMachine.sendMessage(msg); 253 254 // Wait until the message is processed and a broadcast request is sent to 255 // to MapClientService to change 256 // state from STATE_CONNECTING to STATE_CONNECTED 257 verify(mMockMapClientService, 258 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 259 mIntentArgument.capture(), any(String[].class), 260 any(BroadcastOptions.class)); 261 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 262 } 263 264 /** 265 * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED --> 266 * (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED 267 */ 268 @Test testStateTransitionFromConnectedWithMasDisconnected()269 public void testStateTransitionFromConnectedWithMasDisconnected() { 270 Log.i(TAG, "in testStateTransitionFromConnectedWithMasDisconnected"); 271 272 setupSdpRecordReceipt(); 273 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 274 mMceStateMachine.sendMessage(msg); 275 276 // Wait until the message is processed and a broadcast request is sent to 277 // to MapClientService to change 278 // state from STATE_CONNECTING to STATE_CONNECTED 279 verify(mMockMapClientService, 280 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 281 mIntentArgument.capture(), any(String[].class), 282 any(BroadcastOptions.class)); 283 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 284 285 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED); 286 mMceStateMachine.sendMessage(msg); 287 verify(mMockMapClientService, 288 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcastMultiplePermissions( 289 mIntentArgument.capture(), any(String[].class), 290 any(BroadcastOptions.class)); 291 292 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); 293 } 294 295 /** 296 * Test receiving an empty event report 297 */ 298 @Test testReceiveEmptyEvent()299 public void testReceiveEmptyEvent() { 300 setupSdpRecordReceipt(); 301 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 302 mMceStateMachine.sendMessage(msg); 303 304 // Wait until the message is processed and a broadcast request is sent to 305 // to MapClientService to change 306 // state from STATE_CONNECTING to STATE_CONNECTED 307 verify(mMockMapClientService, 308 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 309 mIntentArgument.capture(), any(String[].class), 310 any(BroadcastOptions.class)); 311 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 312 313 // Send an empty notification event, verify the mMceStateMachine is still connected 314 Message notification = Message.obtain(mHandler, MceStateMachine.MSG_NOTIFICATION); 315 mMceStateMachine.getCurrentState().processMessage(msg); 316 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 317 } 318 319 /** 320 * Test set message status 321 */ 322 @Test testSetMessageStatus()323 public void testSetMessageStatus() { 324 setupSdpRecordReceipt(); 325 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 326 mMceStateMachine.sendMessage(msg); 327 328 // Wait until the message is processed and a broadcast request is sent to 329 // to MapClientService to change 330 // state from STATE_CONNECTING to STATE_CONNECTED 331 verify(mMockMapClientService, 332 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 333 mIntentArgument.capture(), any(String[].class), 334 any(BroadcastOptions.class)); 335 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 336 Assert.assertTrue( 337 mMceStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ)); 338 } 339 340 /** 341 * Test disconnect 342 */ 343 @Test testDisconnect()344 public void testDisconnect() { 345 setupSdpRecordReceipt(); 346 doAnswer(invocation -> { 347 mMceStateMachine.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED); 348 return null; 349 }).when(mMockMasClient).shutdown(); 350 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 351 mMceStateMachine.sendMessage(msg); 352 353 // Wait until the message is processed and a broadcast request is sent to 354 // to MapClientService to change 355 // state from STATE_CONNECTING to STATE_CONNECTED 356 verify(mMockMapClientService, 357 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 358 mIntentArgument.capture(), any(String[].class), 359 any(BroadcastOptions.class)); 360 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 361 362 mMceStateMachine.disconnect(); 363 verify(mMockMapClientService, 364 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcastMultiplePermissions( 365 mIntentArgument.capture(), any(String[].class), 366 any(BroadcastOptions.class)); 367 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); 368 } 369 370 /** 371 * Test disconnect timeout 372 */ 373 @Test testDisconnectTimeout()374 public void testDisconnectTimeout() { 375 setupSdpRecordReceipt(); 376 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 377 mMceStateMachine.sendMessage(msg); 378 379 // Wait until the message is processed and a broadcast request is sent to 380 // to MapClientService to change 381 // state from STATE_CONNECTING to STATE_CONNECTED 382 verify(mMockMapClientService, 383 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 384 mIntentArgument.capture(), any(String[].class), 385 any(BroadcastOptions.class)); 386 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 387 388 mMceStateMachine.disconnect(); 389 verify(mMockMapClientService, 390 after(DISCONNECT_TIMEOUT / 2).times(3)).sendBroadcastMultiplePermissions( 391 mIntentArgument.capture(), any(String[].class), 392 any(BroadcastOptions.class)); 393 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, mMceStateMachine.getState()); 394 395 verify(mMockMapClientService, 396 timeout(DISCONNECT_TIMEOUT).times(4)).sendBroadcastMultiplePermissions( 397 mIntentArgument.capture(), any(String[].class), 398 any(BroadcastOptions.class)); 399 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); 400 } 401 402 /** 403 * Test sending a message to a phone 404 */ 405 @Test testSendSMSMessageToPhone()406 public void testSendSMSMessageToPhone() { 407 setupSdpRecordReceipt(); 408 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 409 mMceStateMachine.sendMessage(msg); 410 TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper()); 411 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 412 413 String testMessage = "Hello World!"; 414 Uri[] contacts = new Uri[] {Uri.parse("tel://5551212")}; 415 416 verify(mMockMasClient, times(0)).makeRequest(any(RequestPushMessage.class)); 417 mMceStateMachine.sendMapMessage(contacts, testMessage, null, null); 418 verify(mMockMasClient, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) 419 .makeRequest(any(RequestPushMessage.class)); 420 } 421 422 /** 423 * Test sending a message to an email 424 */ 425 @Test testSendSMSMessageToEmail()426 public void testSendSMSMessageToEmail() { 427 setupSdpRecordReceipt(); 428 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 429 mMceStateMachine.sendMessage(msg); 430 TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper()); 431 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 432 433 String testMessage = "Hello World!"; 434 Uri[] contacts = new Uri[] {Uri.parse("mailto://sms-test@google.com")}; 435 436 verify(mMockMasClient, times(0)).makeRequest(any(RequestPushMessage.class)); 437 mMceStateMachine.sendMapMessage(contacts, testMessage, null, null); 438 verify(mMockMasClient, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) 439 .makeRequest(any(RequestPushMessage.class)); 440 } 441 442 /** 443 * Test message sent successfully 444 */ 445 @Test testSMSMessageSent()446 public void testSMSMessageSent() { 447 setupSdpRecordReceipt(); 448 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 449 mMceStateMachine.sendMessage(msg); 450 TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper()); 451 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 452 453 RequestPushMessage testRequest = 454 new RequestPushMessage(FOLDER_SENT, mTestIncomingSmsBmessage, null, false, false); 455 when(mMockRequestPushMessage.getMsgHandle()).thenReturn(mTestMessageSmsHandle); 456 when(mMockRequestPushMessage.getBMsg()).thenReturn(mTestIncomingSmsBmessage); 457 Message msgSent = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 458 mMockRequestPushMessage); 459 460 mMceStateMachine.sendMessage(msgSent); 461 462 TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper()); 463 verify(mMockDatabase, times(1)).storeMessage(eq(mTestIncomingSmsBmessage), 464 eq(mTestMessageSmsHandle), any(), eq(MESSAGE_SEEN)); 465 } 466 467 /** 468 * Preconditions: 469 * - In {@code STATE_CONNECTED}. 470 * - {@code MSG_SEARCH_OWN_NUMBER_TIMEOUT} has been set. 471 * - Next stage of connection process has NOT begun, i.e.: 472 * - Request for Notification Registration not sent 473 * - Request for MessageListing of SENT folder not sent 474 * - Request for MessageListing of INBOX folder not sent 475 */ testGetOwnNumber_setup()476 private void testGetOwnNumber_setup() { 477 testStateTransitionFromConnectingToConnected(); 478 verify(mMockMasClient, after(ASYNC_CALL_TIMEOUT_MILLIS).never()).makeRequest( 479 any(RequestSetNotificationRegistration.class)); 480 verify(mMockMasClient, never()).makeRequest(any(RequestGetMessagesListing.class)); 481 assertThat(mMceStateMachine.getHandler().hasMessages( 482 MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)).isTrue(); 483 } 484 485 /** 486 * Assert whether the next stage of connection process has begun, i.e., whether the following 487 * {@link Request} are sent or not: 488 * - Request for Notification Registration, 489 * - Request for MessageListing of SENT folder (to start downloading), 490 * - Request for MessageListing of INBOX folder (to start downloading). 491 */ testGetOwnNumber_assertNextStageStarted(boolean hasStarted)492 private void testGetOwnNumber_assertNextStageStarted(boolean hasStarted) { 493 if (hasStarted) { 494 verify(mMockMasClient).makeRequest(any(RequestSetNotificationRegistration.class)); 495 verify(mMockMasClient, times(2)).makeRequest(any(RequestGetMessagesListing.class)); 496 497 ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class); 498 verify(mMockMasClient, atLeastOnce()).makeRequest(requestCaptor.capture()); 499 // There will be multiple calls to {@link MasClient#makeRequest} with different 500 // {@link Request} subtypes; not all of them will be {@link 501 // RequestGetMessagesListing}. 502 List<Request> capturedRequests = requestCaptor.getAllValues(); 503 assertThat(capturedRequests).comparingElementsUsing(GET_FOLDER_NAME).contains( 504 MceStateMachine.FOLDER_INBOX); 505 assertThat(capturedRequests).comparingElementsUsing(GET_FOLDER_NAME).contains( 506 MceStateMachine.FOLDER_SENT); 507 } else { 508 verify(mMockMasClient, never()).makeRequest( 509 any(RequestSetNotificationRegistration.class)); 510 verify(mMockMasClient, never()).makeRequest(any(RequestGetMessagesListing.class)); 511 } 512 } 513 514 /** 515 * Preconditions: 516 * - See {@link testGetOwnNumber_setup}. 517 * 518 * Actions: 519 * - Send a {@code MSG_MAS_REQUEST_COMPLETED} with a {@link 520 * RequestGetMessagesListingForOwnNumber} object that has completed its search. 521 * 522 * Outcome: 523 * - {@code MSG_SEARCH_OWN_NUMBER_TIMEOUT} has been cancelled. 524 * - Next stage of connection process has begun, i.e.: 525 * - Request for Notification Registration is made. 526 * - Request for MessageListing of SENT folder is made (to start downloading). 527 * - Request for MessageListing of INBOX folder is made (to start downloading). 528 */ 529 @Test testGetOwnNumberCompleted()530 public void testGetOwnNumberCompleted() { 531 testGetOwnNumber_setup(); 532 533 Message requestCompletedMsg = Message.obtain(mHandler, 534 MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 535 mMockRequestOwnNumberCompletedWithNumber); 536 mMceStateMachine.sendMessage(requestCompletedMsg); 537 538 verify(mMockMasClient, after(ASYNC_CALL_TIMEOUT_MILLIS).never()).makeRequest( 539 eq(mMockRequestOwnNumberCompletedWithNumber)); 540 assertThat(mMceStateMachine.getHandler().hasMessages( 541 MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)).isFalse(); 542 testGetOwnNumber_assertNextStageStarted(true); 543 } 544 545 /** 546 * Preconditions: 547 * - See {@link testGetOwnNumber_setup}. 548 * 549 * Actions: 550 * - Send a {@code MSG_SEARCH_OWN_NUMBER_TIMEOUT}. 551 * 552 * Outcome: 553 * - {@link MasClient#abortRequest} invoked on a {@link RequestGetMessagesListingForOwnNumber}. 554 * - Any existing {@code MSG_MAS_REQUEST_COMPLETED} (corresponding to a 555 * {@link RequestGetMessagesListingForOwnNumber}) has been dropped. 556 * - Next stage of connection process has begun, i.e.: 557 * - Request for Notification Registration is made. 558 * - Request for MessageListing of SENT folder is made (to start downloading). 559 * - Request for MessageListing of INBOX folder is made (to start downloading). 560 */ 561 @Test testGetOwnNumberTimedOut()562 public void testGetOwnNumberTimedOut() { 563 testGetOwnNumber_setup(); 564 Message requestIncompleteMsg = Message.obtain(mHandler, 565 MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 566 mMockRequestOwnNumberIncompleteSearch); 567 mMceStateMachine.sendMessage(requestIncompleteMsg); 568 assertThat(mMceStateMachine.getHandler().hasMessages( 569 MceStateMachine.MSG_MAS_REQUEST_COMPLETED)).isTrue(); 570 571 Message timeoutMsg = Message.obtain(mHandler, 572 MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT, 573 mMockRequestOwnNumberIncompleteSearch); 574 mMceStateMachine.sendMessage(timeoutMsg); 575 576 verify(mMockMasClient, after(ASYNC_CALL_TIMEOUT_MILLIS)).abortRequest( 577 mMockRequestOwnNumberIncompleteSearch); 578 assertThat(mMceStateMachine.getHandler().hasMessages( 579 MceStateMachine.MSG_MAS_REQUEST_COMPLETED)).isFalse(); 580 testGetOwnNumber_assertNextStageStarted(true); 581 } 582 583 /** 584 * Preconditions: 585 * - See {@link testGetOwnNumber_setup}. 586 * 587 * Actions: 588 * - Send a {@code MSG_MAS_REQUEST_COMPLETED} with a {@link 589 * RequestGetMessagesListingForOwnNumber} object that has not completed its search. 590 * 591 * Outcome: 592 * - {@link Request} made to continue searching for own number (using existing/same 593 * {@link Request}). 594 * - {@code MSG_SEARCH_OWN_NUMBER_TIMEOUT} has not been cancelled. 595 * - Next stage of connection process has not begun, i.e.: 596 * - No Request for Notification Registration, 597 * - No Request for MessageListing of SENT folder is made (to start downloading), 598 * - No Request for MessageListing of INBOX folder is made (to start downloading). 599 */ 600 @Test testGetOwnNumberIncomplete()601 public void testGetOwnNumberIncomplete() { 602 testGetOwnNumber_setup(); 603 604 Message requestIncompleteMsg = Message.obtain(mHandler, 605 MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 606 mMockRequestOwnNumberIncompleteSearch); 607 mMceStateMachine.sendMessage(requestIncompleteMsg); 608 609 verify(mMockMasClient, after(ASYNC_CALL_TIMEOUT_MILLIS)).makeRequest( 610 eq(mMockRequestOwnNumberIncompleteSearch)); 611 assertThat(mMceStateMachine.getHandler().hasMessages( 612 MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)).isTrue(); 613 testGetOwnNumber_assertNextStageStarted(false); 614 } 615 616 /** 617 * Test seen status set for new SMS 618 */ 619 @Test testReceivedNewSms_messageStoredAsUnseen()620 public void testReceivedNewSms_messageStoredAsUnseen() { 621 setupSdpRecordReceipt(); 622 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 623 mMceStateMachine.sendMessage(msg); 624 625 //verifying that state machine is in the Connected state 626 verify(mMockMapClientService, 627 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 628 mIntentArgument.capture(), any(String[].class), 629 any(BroadcastOptions.class)); 630 assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); 631 632 String dateTime = new ObexTime(Instant.now()).toString(); 633 EventReport event = createNewEventReport("NewMessage", dateTime, mTestMessageSmsHandle, 634 "telecom/msg/inbox", null, "SMS_GSM"); 635 636 mMceStateMachine.receiveEvent(event); 637 638 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 639 verify(mMockMasClient, times(1)).makeRequest 640 (any(RequestGetMessage.class)); 641 642 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 643 mMockRequestGetMessage); 644 mMceStateMachine.sendMessage(msg); 645 646 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 647 verify(mMockDatabase, times(1)).storeMessage(eq(mTestIncomingSmsBmessage), 648 eq(mTestMessageSmsHandle), any(), eq(MESSAGE_NOT_SEEN)); 649 } 650 651 /** 652 * Test seen status set for new MMS 653 */ 654 @Test testReceivedNewMms_messageStoredAsUnseen()655 public void testReceivedNewMms_messageStoredAsUnseen() { 656 setupSdpRecordReceipt(); 657 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 658 mMceStateMachine.sendMessage(msg); 659 660 //verifying that state machine is in the Connected state 661 verify(mMockMapClientService, 662 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 663 mIntentArgument.capture(), any(String[].class), 664 any(BroadcastOptions.class)); 665 assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); 666 667 String dateTime = new ObexTime(Instant.now()).toString(); 668 EventReport event = createNewEventReport("NewMessage", dateTime, mTestMessageMmsHandle, 669 "telecom/msg/inbox", null, "MMS"); 670 671 when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage); 672 when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageMmsHandle); 673 674 mMceStateMachine.receiveEvent(event); 675 676 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 677 verify(mMockMasClient, times(1)).makeRequest 678 (any(RequestGetMessage.class)); 679 680 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 681 mMockRequestGetMessage); 682 mMceStateMachine.sendMessage(msg); 683 684 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 685 verify(mMockDatabase, times(1)).storeMessage(eq(mTestIncomingMmsBmessage), 686 eq(mTestMessageMmsHandle), any(), eq(MESSAGE_NOT_SEEN)); 687 } 688 689 /** 690 * Test seen status set in database on initial download 691 */ 692 @Test testDownloadExistingSms_messageStoredAsSeen()693 public void testDownloadExistingSms_messageStoredAsSeen() { 694 setupSdpRecordReceipt(); 695 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 696 mMceStateMachine.sendMessage(msg); 697 698 verify(mMockMapClientService, 699 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 700 mIntentArgument.capture(), any(String[].class), 701 any(BroadcastOptions.class)); 702 assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); 703 704 com.android.bluetooth.mapclient.Message testMessageListingSms = createNewMessage("SMS_GSM", 705 mTestMessageSmsHandle); 706 ArrayList<com.android.bluetooth.mapclient.Message> messageListSms = new ArrayList<>(); 707 messageListSms.add(testMessageListingSms); 708 when(mMockRequestGetMessagesListing.getList()).thenReturn(messageListSms); 709 710 msg = Message.obtain(mHandler, MceStateMachine.MSG_GET_MESSAGE_LISTING, 711 MceStateMachine.FOLDER_INBOX); 712 mMceStateMachine.sendMessage(msg); 713 714 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 715 verify(mMockMasClient, times(1)).makeRequest(any( 716 RequestGetMessagesListing.class)); 717 718 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 719 mMockRequestGetMessagesListing); 720 mMceStateMachine.sendMessage(msg); 721 722 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 723 verify(mMockMasClient, times(1)).makeRequest(any( 724 RequestGetMessage.class)); 725 726 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 727 mMockRequestGetMessage); 728 mMceStateMachine.sendMessage(msg); 729 730 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 731 verify(mMockDatabase, times(1)).storeMessage(any(), any(), 732 any(), eq(MESSAGE_SEEN)); 733 } 734 735 /** 736 * Test seen status set in database on initial download 737 */ 738 @Test testDownloadExistingMms_messageStoredAsSeen()739 public void testDownloadExistingMms_messageStoredAsSeen() { 740 setupSdpRecordReceipt(); 741 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 742 mMceStateMachine.sendMessage(msg); 743 744 verify(mMockMapClientService, 745 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 746 mIntentArgument.capture(), any(String[].class), 747 any(BroadcastOptions.class)); 748 assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); 749 750 com.android.bluetooth.mapclient.Message testMessageListingMms = createNewMessage("MMS", 751 mTestMessageMmsHandle); 752 ArrayList<com.android.bluetooth.mapclient.Message> messageListMms = new ArrayList<>(); 753 messageListMms.add(testMessageListingMms); 754 755 when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage); 756 when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageMmsHandle); 757 when(mMockRequestGetMessagesListing.getList()).thenReturn(messageListMms); 758 759 msg = Message.obtain(mHandler, MceStateMachine.MSG_GET_MESSAGE_LISTING, 760 MceStateMachine.FOLDER_INBOX); 761 mMceStateMachine.sendMessage(msg); 762 763 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 764 verify(mMockMasClient, times(1)).makeRequest(any( 765 RequestGetMessagesListing.class)); 766 767 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 768 mMockRequestGetMessagesListing); 769 mMceStateMachine.sendMessage(msg); 770 771 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 772 verify(mMockMasClient, times(1)).makeRequest(any( 773 RequestGetMessage.class)); 774 775 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 776 mMockRequestGetMessage); 777 mMceStateMachine.sendMessage(msg); 778 779 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 780 verify(mMockDatabase, times(1)).storeMessage(any(), any(), 781 any(), eq(MESSAGE_SEEN)); 782 } 783 784 /** 785 * Test receiving a new message notification. 786 */ 787 @Test testReceiveNewMessageNotification()788 public void testReceiveNewMessageNotification() { 789 setupSdpRecordReceipt(); 790 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 791 mMceStateMachine.sendMessage(msg); 792 793 verify(mMockMapClientService, 794 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 795 mIntentArgument.capture(), any(String[].class), 796 any(BroadcastOptions.class)); 797 assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); 798 799 // Receive a new message notification. 800 String dateTime = new ObexTime(Instant.now()).toString(); 801 EventReport event = createNewEventReport("NewMessage", dateTime, mTestMessageSmsHandle, 802 "telecom/msg/inbox", null, "SMS_GSM"); 803 804 Message notificationMessage = 805 Message.obtain(mHandler, MceStateMachine.MSG_NOTIFICATION, (Object)event); 806 807 mMceStateMachine.getCurrentState().processMessage(notificationMessage); 808 809 verify(mMockMasClient, 810 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) 811 .makeRequest(any(RequestGetMessage.class)); 812 813 MceStateMachine.MessageMetadata messageMetadata = 814 mMceStateMachine.mMessages.get(mTestMessageSmsHandle); 815 Assert.assertEquals(messageMetadata.getHandle(), mTestMessageSmsHandle); 816 Assert.assertEquals( 817 new ObexTime(Instant.ofEpochMilli(messageMetadata.getTimestamp())).toString(), 818 dateTime); 819 } 820 821 @Test testReceivedNewMmsNoSMSDefaultPackage_broadcastToSMSReplyPackage()822 public void testReceivedNewMmsNoSMSDefaultPackage_broadcastToSMSReplyPackage() { 823 setupSdpRecordReceipt(); 824 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 825 mMceStateMachine.sendMessage(msg); 826 827 //verifying that state machine is in the Connected state 828 verify(mMockMapClientService, 829 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions( 830 mIntentArgument.capture(), any(String[].class), 831 any(BroadcastOptions.class)); 832 assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); 833 834 String dateTime = new ObexTime(Instant.now()).toString(); 835 EventReport event = createNewEventReport("NewMessage", dateTime, mTestMessageSmsHandle, 836 "telecom/msg/inbox", null, "SMS_GSM"); 837 838 mMceStateMachine.receiveEvent(event); 839 840 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 841 verify(mMockMasClient, times(1)).makeRequest 842 (any(RequestGetMessage.class)); 843 844 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 845 mMockRequestGetMessage); 846 mMceStateMachine.sendMessage(msg); 847 848 TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper()); 849 verify(mMockMapClientService, 850 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcast( 851 mIntentArgument.capture(), 852 eq(android.Manifest.permission.RECEIVE_SMS)); 853 Assert.assertNull(mIntentArgument.getValue().getPackage()); 854 } 855 setupSdpRecordReceipt()856 private void setupSdpRecordReceipt() { 857 // Perform first part of MAP connection logic. 858 verify(mMockMapClientService, 859 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcastMultiplePermissions( 860 mIntentArgument.capture(), any(String[].class), 861 any(BroadcastOptions.class)); 862 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState()); 863 864 // Setup receipt of SDP record 865 SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord"); 866 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record); 867 mMceStateMachine.sendMessage(msg); 868 } 869 870 private class MockSmsContentProvider extends MockContentProvider { 871 Map<Uri, ContentValues> mContentValues = new HashMap<>(); 872 int mInsertOperationCount = 0; 873 874 @Override delete(Uri uri, String selection, String[] selectionArgs)875 public int delete(Uri uri, String selection, String[] selectionArgs) { 876 return 0; 877 } 878 879 @Override insert(Uri uri, ContentValues values)880 public Uri insert(Uri uri, ContentValues values) { 881 mInsertOperationCount++; 882 return Uri.withAppendedPath(Sms.CONTENT_URI, String.valueOf(mInsertOperationCount)); 883 } 884 885 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)886 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 887 String sortOrder) { 888 Cursor cursor = Mockito.mock(Cursor.class); 889 890 when(cursor.moveToFirst()).thenReturn(true); 891 when(cursor.moveToNext()).thenReturn(true).thenReturn(false); 892 893 when(cursor.getLong(anyInt())).thenReturn((long) mContentValues.size()); 894 when(cursor.getString(anyInt())).thenReturn(String.valueOf(mContentValues.size())); 895 return cursor; 896 } 897 } 898 getFolderNameFromRequestGetMessagesListing( Request request)899 private static String getFolderNameFromRequestGetMessagesListing( 900 Request request) { 901 Log.d(TAG, "getFolderName, Request type=" + request); 902 String folderName = null; 903 if (request instanceof RequestGetMessagesListing) { 904 try { 905 folderName = (String) request.mHeaderSet.getHeader(HeaderSet.NAME); 906 } catch (Exception e) { 907 Log.e(TAG, "in getFolderNameFromRequestGetMessagesListing", e); 908 } 909 } 910 Log.d(TAG, "getFolderName, name=" + folderName); 911 return folderName; 912 } 913 914 // create new Messages from given input createNewMessage(String mType, String mHandle)915 com.android.bluetooth.mapclient.Message createNewMessage(String mType, String mHandle){ 916 HashMap<String, String> attrs = new HashMap<String, String>(); 917 918 attrs.put("type", mType); 919 attrs.put("handle", mHandle); 920 attrs.put("datetime", "20230223T160000"); 921 922 com.android.bluetooth.mapclient.Message message = new com.android.bluetooth.mapclient. 923 Message(attrs); 924 925 return message; 926 } 927 createNewEventReport(String mType, String mDateTime, String mHandle, String mFolder, String mOldFolder, String mMsgType)928 EventReport createNewEventReport(String mType, String mDateTime, String mHandle, String mFolder, 929 String mOldFolder, String mMsgType){ 930 931 HashMap<String, String> attrs = new HashMap<String, String>(); 932 933 attrs.put("type", mType); 934 attrs.put("datetime", mDateTime); 935 attrs.put("handle", mHandle); 936 attrs.put("folder", mFolder); 937 attrs.put("old_folder", mOldFolder); 938 attrs.put("msg_type", mMsgType); 939 940 EventReport event = new EventReport(attrs); 941 942 return event; 943 944 } 945 946 //create new Bmessages for testing createTestMessages()947 void createTestMessages() { 948 mOriginator = new VCardEntry(); 949 VCardProperty property = new VCardProperty(); 950 property.setName(VCardConstants.PROPERTY_TEL); 951 property.addValues("555-1212"); 952 mOriginator.addProperty(property); 953 954 mTestIncomingSmsBmessage = new Bmessage(); 955 mTestIncomingSmsBmessage.setBodyContent("HelloWorld"); 956 mTestIncomingSmsBmessage.setType(Bmessage.Type.SMS_GSM); 957 mTestIncomingSmsBmessage.setFolder("telecom/msg/inbox"); 958 mTestIncomingSmsBmessage.addOriginator(mOriginator); 959 mTestIncomingSmsBmessage.addRecipient(mOriginator); 960 961 mTestIncomingMmsBmessage = new Bmessage(); 962 mTestIncomingMmsBmessage.setBodyContent("HelloWorld"); 963 mTestIncomingMmsBmessage.setType(Bmessage.Type.MMS); 964 mTestIncomingMmsBmessage.setFolder("telecom/msg/inbox"); 965 mTestIncomingMmsBmessage.addOriginator(mOriginator); 966 mTestIncomingMmsBmessage.addRecipient(mOriginator); 967 } 968 } 969