1 /* 2 * Copyright (C) 2021 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.services.telephony.rcs; 18 19 import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING; 20 21 import static junit.framework.Assert.assertEquals; 22 import static junit.framework.Assert.assertNotNull; 23 import static junit.framework.Assert.assertTrue; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.Mockito.doReturn; 27 import static org.mockito.Mockito.eq; 28 import static org.mockito.Mockito.mock; 29 import static org.mockito.Mockito.verify; 30 31 import android.content.Context; 32 import android.net.Uri; 33 import android.os.RemoteException; 34 import android.telephony.BinderCacheManager; 35 import android.telephony.ims.ImsException; 36 import android.telephony.ims.SipDelegateManager; 37 import android.telephony.ims.SipDialogState; 38 import android.telephony.ims.SipDialogStateCallback; 39 import android.telephony.ims.SipMessage; 40 import android.telephony.ims.aidl.IImsRcsController; 41 import android.util.ArraySet; 42 import android.util.Base64; 43 44 import androidx.test.ext.junit.runners.AndroidJUnit4; 45 46 import com.android.TelephonyTestBase; 47 import com.android.internal.telephony.ISipDialogStateCallback; 48 import com.android.internal.telephony.ITelephony; 49 import com.android.internal.telephony.PhoneFactory; 50 import com.android.internal.telephony.metrics.RcsStats; 51 52 import org.junit.Before; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.mockito.ArgumentCaptor; 56 import org.mockito.Mock; 57 import org.mockito.MockitoAnnotations; 58 59 import java.nio.ByteBuffer; 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.Collections; 63 import java.util.List; 64 import java.util.Set; 65 import java.util.stream.Collectors; 66 67 @RunWith(AndroidJUnit4.class) 68 public class SipSessionTrackerTest extends TelephonyTestBase { 69 70 private class DialogAttributes { 71 public final String branchId; 72 public final String callId; 73 public final String fromHeader; 74 public final String fromTag; 75 public final String toUri; 76 public final String toHeader; 77 private final String mFromUri; 78 // This may be populated later. 79 public String toTag; 80 DialogAttributes()81 DialogAttributes() { 82 branchId = getNextString(); 83 callId = getNextString(); 84 mFromUri = generateRandomSipUri(); 85 fromHeader = generateContactUri(mFromUri); 86 fromTag = getNextString(); 87 toUri = generateRandomSipUri(); 88 toHeader = generateContactUri(toUri); 89 } 90 DialogAttributes(String branchId, String callId, String fromUri, String fromTag, String toUri, String toTag)91 private DialogAttributes(String branchId, String callId, String fromUri, 92 String fromTag, String toUri, String toTag) { 93 this.branchId = branchId; 94 this.callId = callId; 95 this.mFromUri = fromUri; 96 this.fromHeader = generateContactUri(fromUri); 97 this.fromTag = fromTag; 98 this.toUri = toUri; 99 this.toHeader = generateContactUri(toUri); 100 this.toTag = toTag; 101 } 102 setToTag()103 public void setToTag() { 104 if (toTag == null) { 105 toTag = getNextString(); 106 } 107 } 108 fromExisting()109 public DialogAttributes fromExisting() { 110 return new DialogAttributes(branchId, callId, mFromUri, fromTag, toUri, null); 111 } 112 invertFromTo()113 public DialogAttributes invertFromTo() { 114 return new DialogAttributes(branchId, callId, toUri, fromTag, mFromUri, toTag); 115 } 116 } 117 118 // Keep track of the string entry so we can generate unique strings. 119 private int mStringEntryCounter = 0; 120 private SipSessionTracker mTrackerUT; 121 private static final int TEST_SUB_ID = 1; 122 private static final String TEST_INVITE_SIP_METHOD = "INVITE"; 123 private static final int TEST_SIP_RESPONSE_CODE = 200; 124 private static final int TEST_SIP_CLOSE_RESPONSE_CODE = 0; 125 126 @Mock private RcsStats mRcsStats; 127 private boolean mUpdatedState = false; 128 private SipDialogStateCallback mCallback; 129 private SipDelegateManager mSipManager; 130 private ISipDialogStateCallback mCbBinder; 131 IImsRcsController mMockImsRcsInterface; 132 BinderCacheManager<ITelephony> mBinderCache; 133 BinderCacheManager<IImsRcsController> mRcsBinderCache; 134 135 @Before setUp()136 public void setUp() throws Exception { 137 super.setUp(); 138 139 mStringEntryCounter = 0; 140 MockitoAnnotations.initMocks(this); 141 mTrackerUT = new SipSessionTracker(TEST_SUB_ID, mRcsStats); 142 mMockImsRcsInterface = mock(IImsRcsController.class); 143 mBinderCache = mock(BinderCacheManager.class); 144 mRcsBinderCache = mock(BinderCacheManager.class); 145 doReturn(mMockImsRcsInterface).when(mRcsBinderCache) 146 .listenOnBinder(any(), any(Runnable.class)); 147 doReturn(mMockImsRcsInterface).when(mRcsBinderCache) 148 .removeRunnable(any(SipDialogStateCallback.class)); 149 doReturn(mMockImsRcsInterface).when(mRcsBinderCache).getBinder(); 150 } 151 152 @Test testMetricsEndedGracefullyBye()153 public void testMetricsEndedGracefullyBye() { 154 DialogAttributes attr = new DialogAttributes(); 155 // INVITE 156 SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr); 157 filterMessage(inviteRequest, attr); 158 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 159 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr); 160 161 // confirmed dialog 162 attr.setToTag(); 163 SipMessage inviteConfirm = generateSipResponse("200", "OK", attr); 164 filterMessage(inviteConfirm, attr); 165 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 166 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr); 167 168 // Gracefully Ended 169 SipMessage inviteClose = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr); 170 filterMessage(inviteClose, attr); 171 172 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 173 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 174 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr); 175 176 // verify Metrics information 177 verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId), 178 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(true)); 179 } 180 181 @Test testMetricsCloseCleanupSession()182 public void testMetricsCloseCleanupSession() { 183 //mTrackerUT.setRcsStats(mRcsStats); 184 DialogAttributes attr = new DialogAttributes(); 185 // INVITE A -> B 186 SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr); 187 filterMessage(inviteRequest, attr); 188 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 189 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr); 190 191 // confirmed dialog 192 attr.setToTag(); 193 SipMessage inviteConfirm = generateSipResponse("200", "OK", attr); 194 filterMessage(inviteConfirm, attr); 195 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 196 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr); 197 198 //forcefully close session 199 mTrackerUT.cleanupSession(attr.callId); 200 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 201 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 202 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 203 204 // verify Metrics information 205 verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId), 206 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(false)); 207 } 208 209 @Test testMetricsCloseClearAllSessions()210 public void testMetricsCloseClearAllSessions() { 211 //mTrackerUT.setRcsStats(mRcsStats); 212 DialogAttributes attr = new DialogAttributes(); 213 214 // INVITE 215 SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr); 216 filterMessage(inviteRequest, attr); 217 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 218 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr); 219 220 // confirmed dialog 221 attr.setToTag(); 222 SipMessage inviteConfirm = generateSipResponse("200", "OK", attr); 223 filterMessage(inviteConfirm, attr); 224 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 225 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr); 226 227 //forcefully close session 228 mTrackerUT.clearAllSessions(); 229 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 230 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 231 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 232 233 // verify Metrics information 234 verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId), 235 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(false)); 236 } 237 238 @Test testEarlyDialogToConfirmed()239 public void testEarlyDialogToConfirmed() { 240 DialogAttributes attr = new DialogAttributes(); 241 // INVITE A -> B 242 SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr); 243 filterMessage(inviteRequest, attr); 244 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 245 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr); 246 // 100 TRYING A <- proxy 247 SipMessage inviteTrying = generateSipResponse("100", "Trying", attr); 248 filterMessage(inviteTrying, attr); 249 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 250 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr); 251 // INVITE proxy -> B 252 // (BOB generates To tag) 253 attr.setToTag(); 254 // 180 RINGING proxy <- B 255 // 180 RINGING A <- proxy 256 SipMessage inviteRinging = generateSipResponse("180", "Ringing", attr); 257 filterMessage(inviteRinging, attr); 258 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 259 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr); 260 // User answers phone 261 // 200 OK proxy <- B 262 // 200 OK A <- proxy 263 SipMessage inviteConfirm = generateSipResponse("200", "OK", attr); 264 filterMessage(inviteConfirm, attr); 265 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 266 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr); 267 } 268 269 @Test testForkDialog()270 public void testForkDialog() { 271 DialogAttributes attrB1 = new DialogAttributes(); 272 // INVITE A -> B 273 SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attrB1); 274 filterMessage(inviteRequest, attrB1); 275 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 276 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1); 277 // INVITE proxy -> B 278 // (BOB generates To tag) 279 attrB1.setToTag(); 280 // 180 RINGING proxy <- B1 281 // 180 RINGING A <- proxy 282 SipMessage inviteRingingB1 = generateSipResponse("180", "Ringing", attrB1); 283 filterMessage(inviteRingingB1, attrB1); 284 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 285 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1); 286 // Now get another RINGING indication from another device associated with the same user. 287 // 180 RINGING proxy <- B2 288 // 180 RINGING A <- proxy 289 DialogAttributes attrB2 = attrB1.fromExisting(); 290 // set different To tag 291 attrB2.setToTag(); 292 SipMessage inviteRingingB2 = generateSipResponse("180", "Ringing", attrB2); 293 filterMessage(inviteRingingB2, attrB2); 294 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 295 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1, attrB2); 296 // User answers B1 297 // 200 OK proxy <- B1 298 // 200 OK A <- proxy 299 SipMessage inviteConfirm = generateSipResponse("200", "OK", attrB1); 300 filterMessage(inviteConfirm, attrB1); 301 verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB2); 302 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attrB1); 303 // Receive indication that B2 is terminated because user answered on B1 304 // 487 A <- proxy 305 SipMessage terminatedResponse = generateSipResponse("487", 306 "Request Terminated", attrB2); 307 filterMessage(terminatedResponse, attrB2); 308 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 309 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attrB1); 310 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attrB2); 311 SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attrB1); 312 // Send BYE request for the open dialog. 313 filterMessage(byeRequest, attrB1); 314 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 315 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 316 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attrB1, attrB2); 317 // Clean up the session and ensure the close dialog is completely removed from the tracker. 318 mTrackerUT.cleanupSession(attrB1.callId); 319 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 320 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 321 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 322 } 323 324 @Test testCloseLocalDialog()325 public void testCloseLocalDialog() { 326 DialogAttributes attr = new DialogAttributes(); 327 attr.setToTag(); 328 createConfirmedDialog(attr); 329 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 330 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr); 331 332 // Send BYE request for a dialog that was started locally and ensure that we see the call id 333 // move to the closed list. 334 SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr); 335 filterMessage(byeRequest, attr); 336 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 337 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 338 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr); 339 // Clean up the session and ensure the close dialog is completely removed from the tracker. 340 mTrackerUT.cleanupSession(attr.callId); 341 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 342 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 343 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 344 } 345 346 @Test testAcceptContactFts()347 public void testAcceptContactFts() { 348 DialogAttributes attr = new DialogAttributes(); 349 attr.setToTag(); 350 SipMessage inviteRequest = generateSipRequest( 351 SipMessageUtils.INVITE_SIP_METHOD, 352 attr); 353 // add accept contact header 354 inviteRequest = new SipMessage(inviteRequest.getStartLine(), 355 inviteRequest.getHeaderSection() + "\nAccept-Contact:*;+test", 356 new byte[0]); 357 filterMessage(inviteRequest, attr); 358 assertTrue(mTrackerUT.getCallIdsAssociatedWithFeatureTag(Collections.singleton("+test")) 359 .contains(attr.callId)); 360 } 361 362 @Test testCloseRemoteDialog()363 public void testCloseRemoteDialog() { 364 DialogAttributes remoteAttr = new DialogAttributes(); 365 remoteAttr.setToTag(); 366 createConfirmedDialog(remoteAttr); 367 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 368 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), remoteAttr); 369 370 // Send BYE request on a dialog that was started from the remote party. 371 DialogAttributes localAttr = remoteAttr.invertFromTo(); 372 SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, localAttr); 373 filterMessage(byeRequest, localAttr); 374 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 375 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 376 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), remoteAttr); 377 // Clean up the session and ensure the dialog is completely removed from the tracker. 378 mTrackerUT.cleanupSession(remoteAttr.callId); 379 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 380 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 381 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 382 } 383 384 @Test testCleanupConfirmedDialog()385 public void testCleanupConfirmedDialog() { 386 DialogAttributes attr = new DialogAttributes(); 387 attr.setToTag(); 388 createConfirmedDialog(attr); 389 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 390 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr); 391 // Clean up the session and ensure the dialog is completely removed from the tracker. 392 mTrackerUT.cleanupSession(attr.callId); 393 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 394 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 395 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 396 } 397 398 @Test testMultipleDialogs()399 public void testMultipleDialogs() { 400 DialogAttributes attr1 = new DialogAttributes(); 401 createConfirmedDialog(attr1); 402 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 403 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1); 404 // add a second dialog 405 DialogAttributes attr2 = new DialogAttributes(); 406 createConfirmedDialog(attr2); 407 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 408 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2); 409 // Send BYE request on dialogs 410 SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr1); 411 filterMessage(byeRequest, attr1); 412 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 413 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr2); 414 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr1); 415 mTrackerUT.cleanupSession(attr1.callId); 416 // Send BYE request on dialogs 417 byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr2); 418 filterMessage(byeRequest, attr2); 419 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 420 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 421 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr2); 422 mTrackerUT.cleanupSession(attr2.callId); 423 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 424 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 425 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 426 } 427 428 @Test testAcknowledgeMessageFailed()429 public void testAcknowledgeMessageFailed() { 430 DialogAttributes attr = new DialogAttributes(); 431 SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr); 432 mTrackerUT.filterSipMessage( 433 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteRequest); 434 // Do not acknowledge the request and ensure that the operation has not been applied yet. 435 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 436 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 437 // send message ack failed event, the operation shouldn't have been applied 438 mTrackerUT.pendingMessageFailed(attr.branchId); 439 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 440 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 441 } 442 443 @Test testAcknowledgeBatchEvents()444 public void testAcknowledgeBatchEvents() { 445 DialogAttributes attr = new DialogAttributes(); 446 SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr); 447 attr.setToTag(); 448 SipMessage inviteConfirm = generateSipResponse("200", "OK", attr); 449 // We unexpectedly received two filter requests for the same branchId without 450 // acknowledgePendingMessage being called in between. Ensure that when it is called, it 451 // applies both operations. 452 mTrackerUT.filterSipMessage( 453 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteRequest); 454 mTrackerUT.filterSipMessage( 455 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteConfirm); 456 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 457 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 458 // we should skip right to confirmed as both operations run back-to-back 459 mTrackerUT.acknowledgePendingMessage(attr.branchId); 460 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 461 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr); 462 } 463 464 @Test testActiveDialogsChanged()465 public void testActiveDialogsChanged() throws ImsException { 466 sipDialogStateCallback(); 467 468 // first dialog 469 DialogAttributes attr1 = new DialogAttributes(); 470 createConfirmedDialog(attr1); 471 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 472 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1); 473 474 verifyConfirmedStates(true); 475 476 // add a second dialog 477 DialogAttributes attr2 = new DialogAttributes(); 478 createConfirmedDialog(attr2); 479 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 480 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2); 481 verifyConfirmedStates(true); 482 483 // Send BYE request on first dialog 484 SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr1); 485 filterMessage(byeRequest, attr1); 486 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 487 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr2); 488 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr1); 489 mTrackerUT.cleanupSession(attr1.callId); 490 verifyConfirmedStates(true); 491 492 // Send BYE request on second dialog 493 byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr2); 494 filterMessage(byeRequest, attr2); 495 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 496 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 497 verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr2); 498 mTrackerUT.cleanupSession(attr2.callId); 499 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 500 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 501 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 502 verifyConfirmedStates(false); 503 unRegisterCallback(); 504 } 505 506 @Test testActiveSipDialogsChangedClearAll()507 public void testActiveSipDialogsChangedClearAll() throws ImsException { 508 sipDialogStateCallback(); 509 510 // first dialog 511 DialogAttributes attr1 = new DialogAttributes(); 512 createConfirmedDialog(attr1); 513 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 514 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1); 515 verifyConfirmedStates(true); 516 517 // add a second dialog 518 DialogAttributes attr2 = new DialogAttributes(); 519 createConfirmedDialog(attr2); 520 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 521 verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2); 522 verifyConfirmedStates(true); 523 524 // cleanAllSessions 525 mTrackerUT.clearAllSessions(); 526 assertTrue(mTrackerUT.getEarlyDialogs().isEmpty()); 527 assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty()); 528 assertTrue(mTrackerUT.getClosedDialogs().isEmpty()); 529 verifyConfirmedStates(false); 530 unRegisterCallback(); 531 } 532 sipDialogStateCallback()533 private void sipDialogStateCallback() throws ImsException { 534 mCallback = new SipDialogStateCallback() { 535 @Override 536 public void onActiveSipDialogsChanged(List<SipDialogState> dialogs) { 537 mUpdatedState = isSipDialogActiveState(dialogs); 538 } 539 540 @Override 541 public void onError() { } 542 }; 543 registerCallback(); 544 } 545 verifyConfirmedStates(boolean currentState)546 private void verifyConfirmedStates(boolean currentState) { 547 List<SipDialogState> dialogStates = new ArrayList<>(); 548 for (SipDialog d : (ArraySet<SipDialog>) mTrackerUT.getTrackedDialogs()) { 549 SipDialogState dialog = new SipDialogState.Builder(d.getState()).build(); 550 dialogStates.add(dialog); 551 } 552 try { 553 mCbBinder.onActiveSipDialogsChanged(dialogStates); 554 } catch (RemoteException e) { 555 //onActiveSipDialogsChanged error 556 } 557 assertEquals(currentState, mUpdatedState); 558 } 559 registerCallback()560 private void registerCallback() throws ImsException { 561 // Capture the Runnable that was registered. 562 ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); 563 // Capture the ISipDialogStateCallback that was registered. 564 ArgumentCaptor<ISipDialogStateCallback> callbackCaptor = 565 ArgumentCaptor.forClass(ISipDialogStateCallback.class); 566 567 Context context = PhoneFactory.getDefaultPhone().getContext(); 568 mSipManager = new SipDelegateManager(context, 569 TEST_SUB_ID, mRcsBinderCache, mBinderCache); 570 571 mSipManager.registerSipDialogStateCallback(Runnable::run, mCallback); 572 573 verify(mRcsBinderCache).listenOnBinder(any(), runnableCaptor.capture()); 574 try { 575 verify(mMockImsRcsInterface).registerSipDialogStateCallback( 576 eq(TEST_SUB_ID), callbackCaptor.capture()); 577 } catch (RemoteException e) { 578 //registerSipDialogStateCallback error 579 } 580 mCbBinder = callbackCaptor.getValue(); 581 } 582 unRegisterCallback()583 private void unRegisterCallback() { 584 try { 585 mSipManager.unregisterSipDialogStateCallback(mCallback); 586 } catch (ImsException e) { 587 //unregisterSipDialogStateCallback error 588 } 589 } 590 isSipDialogActiveState(List<SipDialogState> dialogs)591 private boolean isSipDialogActiveState(List<SipDialogState> dialogs) { 592 int confirmedSize = dialogs.stream().filter( 593 d -> d.getState() == SipDialogState.STATE_CONFIRMED) 594 .collect(Collectors.toSet()).size(); 595 if (confirmedSize > 0) { 596 return true; 597 } 598 return false; 599 } 600 filterMessage(SipMessage m, DialogAttributes attr)601 private void filterMessage(SipMessage m, DialogAttributes attr) { 602 mTrackerUT.filterSipMessage( 603 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, m); 604 mTrackerUT.acknowledgePendingMessage(attr.branchId); 605 } verifyContainsCallIds(Set<SipDialog> callIdSet, DialogAttributes... attrs)606 private void verifyContainsCallIds(Set<SipDialog> callIdSet, DialogAttributes... attrs) { 607 Set<String> callIds = Arrays.stream(attrs).map(a -> a.callId).collect( 608 Collectors.toSet()); 609 assertTrue(callIdSet.stream().map(SipDialog::getCallId).collect(Collectors.toSet()) 610 .containsAll(callIds)); 611 } 612 generateSipRequest(String requestMethod, DialogAttributes attr)613 private SipMessage generateSipRequest(String requestMethod, 614 DialogAttributes attr) { 615 return SipMessageUtils.generateSipRequest(requestMethod, attr.fromHeader, attr.toHeader, 616 attr.toUri, attr.branchId, attr.callId, attr.fromTag, attr.toTag); 617 } generateSipResponse(String statusCode, String statusString, DialogAttributes attr)618 private SipMessage generateSipResponse(String statusCode, String statusString, 619 DialogAttributes attr) { 620 return SipMessageUtils.generateSipResponse(statusCode, statusString, attr.fromHeader, 621 attr.toHeader, attr.branchId, attr.callId, attr.fromTag, attr.toTag); 622 } 623 generateContactUri(String sipUri)624 private String generateContactUri(String sipUri) { 625 Uri uri = Uri.parse(sipUri); 626 assertNotNull(uri); 627 String[] user = uri.getSchemeSpecificPart().split("@", 2); 628 assertNotNull(user); 629 assertEquals(2, user.length); 630 return user[0] + " <" + sipUri + ">"; 631 } 632 generateRandomSipUri()633 private String generateRandomSipUri() { 634 return "sip:" + getNextString() + "@" + SipMessageUtils.BASE_ADDRESS; 635 } 636 createConfirmedDialog(DialogAttributes attr)637 private void createConfirmedDialog(DialogAttributes attr) { 638 // INVITE ALICE -> BOB 639 SipMessage inviteRequest = generateSipRequest( 640 SipMessageUtils.INVITE_SIP_METHOD, 641 attr); 642 filterMessage(inviteRequest, attr); 643 attr.setToTag(); 644 // skip to confirmed state for test. 645 SipMessage inviteConfirm = generateSipResponse("200", "OK", 646 attr); 647 filterMessage(inviteConfirm, attr); 648 } 649 getNextString()650 private String getNextString() { 651 // Get a string representation of the entry counter 652 byte[] idByteArray = ByteBuffer.allocate(4).putInt(mStringEntryCounter++).array(); 653 return Base64.encodeToString(idByteArray, 654 Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE); 655 } 656 } 657