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