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 android.telephony.ims.cts; 18 19 import static org.junit.Assert.fail; 20 21 import android.os.Bundle; 22 import android.os.DeadObjectException; 23 import android.os.Handler; 24 import android.os.HandlerThread; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.telephony.ims.ImsCallProfile; 28 import android.telephony.ims.ImsCallSessionListener; 29 import android.telephony.ims.ImsConferenceState; 30 import android.telephony.ims.ImsReasonInfo; 31 import android.telephony.ims.ImsStreamMediaProfile; 32 import android.telephony.ims.stub.ImsCallSessionImplBase; 33 import android.util.Log; 34 35 import java.util.concurrent.Executor; 36 37 public class TestImsCallSessionImpl extends ImsCallSessionImplBase { 38 39 private static final String LOG_TAG = "CtsTestImsCallSessionImpl"; 40 41 // The timeout to wait in current state in milliseconds 42 protected static final int WAIT_IN_CURRENT_STATE = 200; 43 44 private final String mCallId = String.valueOf(this.hashCode()); 45 private final Object mLock = new Object(); 46 47 private int mState = ImsCallSessionImplBase.State.IDLE; 48 private ImsCallProfile mCallProfile; 49 private ImsCallProfile mLocalCallProfile; 50 private ImsCallSessionListener mListener; 51 52 private final MessageExecutor mCallExecutor = new MessageExecutor("CallExecutor"); 53 private final MessageExecutor mCallBackExecutor = new MessageExecutor("CallBackExecutor"); 54 55 public static final int TEST_TYPE_NONE = 0; 56 public static final int TEST_TYPE_MO_ANSWER = 1 << 0; 57 public static final int TEST_TYPE_MO_FAILED = 1 << 1; 58 public static final int TEST_TYPE_HOLD_FAILED = 1 << 2; 59 public static final int TEST_TYPE_RESUME_FAILED = 1 << 3; 60 public static final int TEST_TYPE_CONFERENCE_FAILED = 1 << 4; 61 public static final int TEST_TYPE_HOLD_NO_RESPONSE = 1 << 5; 62 public static final int TEST_TYPE_CONFERENCE_FAILED_REMOTE_TERMINATED = 1 << 6; 63 public static final int TEST_TYPE_JOIN_EXIST_CONFERENCE = 1 << 7; 64 public static final int TEST_TYPE_JOIN_EXIST_CONFERENCE_AFTER_SWAP = 1 << 8; 65 public static final int TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP = 1 << 9; 66 public static final int TEST_TYPE_TRANSFERRED = 1 << 10; 67 public static final int TEST_TYPE_TRANSFER_FAILED = 1 << 11; 68 private int mTestType = TEST_TYPE_NONE; 69 private boolean mIsOnHold = false; 70 private boolean mIsTransferResultNotified = false; 71 private int[] mAnbrValues = new int[3]; 72 73 private TestImsCallSessionImpl mConfSession = null; 74 private ImsCallProfile mConfCallProfile = null; 75 private ConferenceHelper mConferenceHelper = null; 76 private String mCallee = null; 77 TestImsCallSessionImpl(ImsCallProfile profile)78 public TestImsCallSessionImpl(ImsCallProfile profile) { 79 mCallProfile = profile; 80 } 81 82 @Override getCallId()83 public String getCallId() { 84 return mCallId; 85 } 86 87 @Override getCallProfile()88 public ImsCallProfile getCallProfile() { 89 return mCallProfile; 90 } 91 92 @Override getLocalCallProfile()93 public ImsCallProfile getLocalCallProfile() { 94 return mLocalCallProfile; 95 } 96 97 @Override getState()98 public int getState() { 99 return mState; 100 } 101 102 @Override isInCall()103 public boolean isInCall() { 104 return (mState == ImsCallSessionImplBase.State.ESTABLISHED) ? true : false; 105 } 106 107 @Override setListener(ImsCallSessionListener listener)108 public void setListener(ImsCallSessionListener listener) { 109 mListener = listener; 110 } 111 112 @Override isMultiparty()113 public boolean isMultiparty() { 114 boolean isMultiparty = (mCallProfile != null) 115 ? mCallProfile.getCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE) : false; 116 return isMultiparty; 117 } 118 119 @Override start(String callee, ImsCallProfile profile)120 public void start(String callee, ImsCallProfile profile) { 121 mCallee = callee; 122 mLocalCallProfile = profile; 123 int state = getState(); 124 125 if ((state != ImsCallSessionImplBase.State.IDLE) 126 && (state != ImsCallSessionImplBase.State.INITIATED)) { 127 Log.d(LOG_TAG, "start :: Illegal state; callId = " + getCallId() 128 + ", state=" + getState()); 129 } 130 131 mCallExecutor.execute(() -> { 132 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 133 134 if (isTestType(TEST_TYPE_MO_FAILED)) { 135 startFailed(); 136 } else { 137 startInternal(); 138 } 139 }); 140 } 141 startInternal()142 void startInternal() { 143 postAndRunTask(() -> { 144 try { 145 if (mListener == null) { 146 return; 147 } 148 Log.d(LOG_TAG, "invokeInitiating mCallId = " + mCallId); 149 mListener.callSessionInitiating(mCallProfile); 150 } catch (Throwable t) { 151 Throwable cause = t.getCause(); 152 if (t instanceof DeadObjectException 153 || (cause != null && cause instanceof DeadObjectException)) { 154 fail("starting cause Throwable to be thrown: " + t); 155 } 156 } 157 }); 158 setState(ImsCallSessionImplBase.State.INITIATED); 159 160 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile( 161 ImsStreamMediaProfile.AUDIO_QUALITY_AMR, 162 ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE, 163 ImsStreamMediaProfile.VIDEO_QUALITY_NONE, 164 ImsStreamMediaProfile.DIRECTION_INVALID, 165 ImsStreamMediaProfile.RTT_MODE_DISABLED); 166 167 ImsCallProfile profile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL, 168 ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile); 169 mCallProfile.updateMediaProfile(profile); 170 171 postAndRunTask(() -> { 172 try { 173 if (mListener == null) { 174 return; 175 } 176 Log.d(LOG_TAG, "invokeProgressing mCallId = " + mCallId); 177 mListener.callSessionProgressing(mCallProfile.getMediaProfile()); 178 } catch (Throwable t) { 179 Throwable cause = t.getCause(); 180 if (t instanceof DeadObjectException 181 || (cause != null && cause instanceof DeadObjectException)) { 182 fail("starting cause Throwable to be thrown: " + t); 183 } 184 } 185 }); 186 setState(ImsCallSessionImplBase.State.ESTABLISHING); 187 188 postAndRunTask(() -> { 189 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 190 try { 191 if (mListener == null) { 192 return; 193 } 194 Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId); 195 mListener.callSessionInitiated(mCallProfile); 196 setState(ImsCallSessionImplBase.State.ESTABLISHED); 197 } catch (Throwable t) { 198 Throwable cause = t.getCause(); 199 if (t instanceof DeadObjectException 200 || (cause != null && cause instanceof DeadObjectException)) { 201 fail("starting cause Throwable to be thrown: " + t); 202 } 203 } 204 }); 205 } 206 startFailed()207 void startFailed() { 208 postAndRunTask(() -> { 209 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 210 try { 211 if (mListener == null) { 212 return; 213 } 214 Log.d(LOG_TAG, "invokestartFailed mCallId = " + mCallId); 215 mListener.callSessionInitiatingFailed(getReasonInfo( 216 ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR, ImsReasonInfo.CODE_UNSPECIFIED)); 217 } catch (Throwable t) { 218 Throwable cause = t.getCause(); 219 if (t instanceof DeadObjectException 220 || (cause != null && cause instanceof DeadObjectException)) { 221 fail("starting cause Throwable to be thrown: " + t); 222 } 223 } 224 }); 225 setState(ImsCallSessionImplBase.State.TERMINATED); 226 } 227 228 @Override accept(int callType, ImsStreamMediaProfile profile)229 public void accept(int callType, ImsStreamMediaProfile profile) { 230 Log.i(LOG_TAG, "Accept Call"); 231 postAndRunTask(() -> { 232 try { 233 if (mListener == null) { 234 return; 235 } 236 Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId); 237 mListener.callSessionInitiated(mCallProfile); 238 } catch (Throwable t) { 239 Throwable cause = t.getCause(); 240 if (t instanceof DeadObjectException 241 || (cause != null && cause instanceof DeadObjectException)) { 242 fail("starting cause Throwable to be thrown: " + t); 243 } 244 } 245 }); 246 setState(ImsCallSessionImplBase.State.ESTABLISHED); 247 } 248 249 @Override reject(int reason)250 public void reject(int reason) { 251 postAndRunTask(() -> { 252 try { 253 if (mListener == null) { 254 return; 255 } 256 Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId); 257 mListener.callSessionTerminated(getReasonInfo( 258 ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, ImsReasonInfo.CODE_UNSPECIFIED)); 259 } catch (Throwable t) { 260 Throwable cause = t.getCause(); 261 if (t instanceof DeadObjectException 262 || (cause != null && cause instanceof DeadObjectException)) { 263 fail("starting cause Throwable to be thrown: " + t); 264 } 265 } 266 }); 267 setState(ImsCallSessionImplBase.State.TERMINATED); 268 } 269 270 @Override update(int callType, ImsStreamMediaProfile mediaProfile)271 public void update(int callType, ImsStreamMediaProfile mediaProfile) { 272 ImsCallProfile callProfile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL, 273 callType, new Bundle(), mediaProfile); 274 mCallProfile.updateMediaProfile(callProfile); 275 276 postAndRunTask(() -> { 277 try { 278 if (mListener == null) { 279 return; 280 } 281 Log.d(LOG_TAG, "callSessionUpdated mCallId = " + mCallId); 282 mListener.callSessionUpdated(callProfile); 283 setState(ImsCallSessionImplBase.State.ESTABLISHED); 284 } catch (Throwable t) { 285 Throwable cause = t.getCause(); 286 if (t instanceof DeadObjectException 287 || (cause != null && cause instanceof DeadObjectException)) { 288 fail("update cause Throwable to be thrown: " + t); 289 } 290 } 291 }); 292 } 293 294 @Override terminate(int reason)295 public void terminate(int reason) { 296 postAndRunTask(() -> { 297 try { 298 if (mListener == null) { 299 return; 300 } 301 Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId); 302 mListener.callSessionTerminated(getReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 303 ImsReasonInfo.CODE_UNSPECIFIED)); 304 } catch (Throwable t) { 305 Throwable cause = t.getCause(); 306 if (t instanceof DeadObjectException 307 || (cause != null && cause instanceof DeadObjectException)) { 308 fail("starting cause Throwable to be thrown: " + t); 309 } 310 } 311 }); 312 setState(ImsCallSessionImplBase.State.TERMINATED); 313 } 314 315 // End the Incoming Call terminateIncomingCall()316 public void terminateIncomingCall() { 317 postAndRunTask(() -> { 318 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 319 try { 320 if (mListener == null) { 321 return; 322 } 323 Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId); 324 mListener.callSessionTerminated(getReasonInfo( 325 ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 326 ImsReasonInfo.CODE_UNSPECIFIED)); 327 } catch (Throwable t) { 328 Throwable cause = t.getCause(); 329 if (t instanceof DeadObjectException 330 || (cause != null && cause instanceof DeadObjectException)) { 331 fail("starting cause Throwable to be thrown: " + t); 332 } 333 } 334 }); 335 setState(ImsCallSessionImplBase.State.TERMINATED); 336 } 337 338 @Override transfer(ImsCallSessionImplBase otherSession)339 public void transfer(ImsCallSessionImplBase otherSession) { 340 if (isTestType(TEST_TYPE_TRANSFER_FAILED)) { 341 transferFailed(); 342 } else if (isTestType(TEST_TYPE_TRANSFERRED)) { 343 postAndRunTask(() -> { 344 try { 345 if (mListener == null) { 346 return; 347 } 348 Log.d(LOG_TAG, "invokeTransferred"); 349 mListener.callSessionTransferred(); 350 mIsTransferResultNotified = true; 351 } catch (Throwable t) { 352 Throwable cause = t.getCause(); 353 if (t instanceof DeadObjectException 354 || (cause != null && cause instanceof DeadObjectException)) { 355 fail("starting cause Throwable to be thrown: " + t); 356 } 357 } 358 }); 359 } else { 360 fail("unknown transfer test type"); 361 } 362 } 363 transferFailed()364 private void transferFailed() { 365 postAndRunTask(() -> { 366 try { 367 if (mListener == null) { 368 return; 369 } 370 Log.d(LOG_TAG, "invokeTransferFailed"); 371 mListener.callSessionTransferFailed(getReasonInfo( 372 ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR, ImsReasonInfo.CODE_UNSPECIFIED)); 373 mIsTransferResultNotified = true; 374 } catch (Throwable t) { 375 Throwable cause = t.getCause(); 376 if (t instanceof DeadObjectException 377 || (cause != null && cause instanceof DeadObjectException)) { 378 fail("starting cause Throwable to be thrown: " + t); 379 } 380 } 381 }); 382 } 383 384 @Override hold(ImsStreamMediaProfile profile)385 public void hold(ImsStreamMediaProfile profile) { 386 if (isTestType(TEST_TYPE_HOLD_FAILED)) { 387 holdFailed(profile); 388 } else { 389 int audioDirection = profile.getAudioDirection(); 390 if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) { 391 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile( 392 ImsStreamMediaProfile.AUDIO_QUALITY_AMR, 393 ImsStreamMediaProfile.DIRECTION_RECEIVE, 394 ImsStreamMediaProfile.VIDEO_QUALITY_NONE, 395 ImsStreamMediaProfile.DIRECTION_INVALID, 396 ImsStreamMediaProfile.RTT_MODE_DISABLED); 397 ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL, 398 ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile); 399 mCallProfile.updateMediaProfile(mprofile); 400 } 401 setState(ImsCallSessionImplBase.State.RENEGOTIATING); 402 403 if (!isTestType(TEST_TYPE_HOLD_NO_RESPONSE)) sendHoldResponse(); 404 } 405 } 406 407 @Override resume(ImsStreamMediaProfile profile)408 public void resume(ImsStreamMediaProfile profile) { 409 if (isTestType(TEST_TYPE_RESUME_FAILED)) { 410 resumeFailed(profile); 411 } else { 412 int audioDirection = profile.getAudioDirection(); 413 if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) { 414 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile( 415 ImsStreamMediaProfile.AUDIO_QUALITY_AMR, 416 ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE, 417 ImsStreamMediaProfile.VIDEO_QUALITY_NONE, 418 ImsStreamMediaProfile.DIRECTION_INVALID, 419 ImsStreamMediaProfile.RTT_MODE_DISABLED); 420 ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL, 421 ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile); 422 mCallProfile.updateMediaProfile(mprofile); 423 } 424 setState(ImsCallSessionImplBase.State.RENEGOTIATING); 425 426 postAndRunTask(() -> { 427 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 428 try { 429 if (mListener == null) { 430 return; 431 } 432 Log.d(LOG_TAG, "invokeResume mCallId = " + mCallId); 433 mListener.callSessionResumed(mCallProfile); 434 mIsOnHold = false; 435 } catch (Throwable t) { 436 Throwable cause = t.getCause(); 437 if (t instanceof DeadObjectException 438 || (cause != null && cause instanceof DeadObjectException)) { 439 fail("starting cause Throwable to be thrown: " + t); 440 } 441 } 442 }); 443 setState(ImsCallSessionImplBase.State.ESTABLISHED); 444 } 445 } 446 holdFailed(ImsStreamMediaProfile profile)447 private void holdFailed(ImsStreamMediaProfile profile) { 448 int audioDirection = profile.getAudioDirection(); 449 if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) { 450 postAndRunTask(() -> { 451 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 452 try { 453 if (mListener == null) { 454 return; 455 } 456 Log.d(LOG_TAG, "invokeHoldFailed mCallId = " + mCallId); 457 mListener.callSessionHoldFailed(getReasonInfo(ImsReasonInfo 458 .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED)); 459 } catch (Throwable t) { 460 Throwable cause = t.getCause(); 461 if (t instanceof DeadObjectException 462 || (cause != null && cause instanceof DeadObjectException)) { 463 fail("starting cause Throwable to be thrown: " + t); 464 } 465 } 466 }); 467 setState(ImsCallSessionImplBase.State.ESTABLISHED); 468 } 469 } 470 resumeFailed(ImsStreamMediaProfile profile)471 private void resumeFailed(ImsStreamMediaProfile profile) { 472 int audioDirection = profile.getAudioDirection(); 473 if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) { 474 postAndRunTask(() -> { 475 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 476 try { 477 if (mListener == null) { 478 return; 479 } 480 Log.d(LOG_TAG, "invokeResumeFailed mCallId = " + mCallId); 481 mListener.callSessionResumeFailed(getReasonInfo(ImsReasonInfo 482 .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED)); 483 } catch (Throwable t) { 484 Throwable cause = t.getCause(); 485 if (t instanceof DeadObjectException 486 || (cause != null && cause instanceof DeadObjectException)) { 487 fail("starting cause Throwable to be thrown: " + t); 488 } 489 } 490 }); 491 setState(ImsCallSessionImplBase.State.ESTABLISHED); 492 } 493 } 494 495 @Override merge()496 public void merge() { 497 if (isTestType(TEST_TYPE_CONFERENCE_FAILED) 498 || isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP) 499 || isTestType(TEST_TYPE_CONFERENCE_FAILED_REMOTE_TERMINATED)) { 500 mergeFailed(); 501 } else if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE) 502 || isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_AFTER_SWAP)) { 503 mergeExistConference(); 504 } else { 505 createConferenceSession(); 506 mConfSession.setState(ImsCallSessionImplBase.State.ESTABLISHED); 507 508 postAndRunTask(() -> { 509 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 510 try { 511 if (mListener == null) { 512 return; 513 } 514 mConferenceHelper.getBackGroundSession().invokeSessionTerminated(); 515 Log.d(LOG_TAG, "invokeCallSessionMergeComplete"); 516 mListener.callSessionMergeComplete(mConfSession); 517 } catch (Throwable t) { 518 Throwable cause = t.getCause(); 519 if (t instanceof DeadObjectException 520 || (cause != null && cause instanceof DeadObjectException)) { 521 fail("starting cause Throwable to be thrown: " + t); 522 } 523 } 524 }); 525 526 postAndRunTask(() -> { 527 try { 528 if (mListener == null) { 529 return; 530 } 531 // after the conference call setup, the participant is two. 532 mConfSession.sendConferenceStateUpdated("connected", 2); 533 } catch (Throwable t) { 534 Throwable cause = t.getCause(); 535 if (t instanceof DeadObjectException 536 || (cause != null && cause instanceof DeadObjectException)) { 537 fail("starting cause Throwable to be thrown: " + t); 538 } 539 } 540 }); 541 } 542 } 543 mergeFailed()544 private void mergeFailed() { 545 if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP)) { 546 addExistConferenceSession(); 547 } else { 548 createConferenceSession(); 549 } 550 551 postAndRunTask(() -> { 552 try { 553 if (mListener == null) { 554 return; 555 } 556 557 TestImsCallSessionImpl confCallSession = mConfSession; 558 if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP)) { 559 confCallSession = null; 560 } 561 Log.d(LOG_TAG, "invokeCallSessionMergeStarted"); 562 mListener.callSessionMergeStarted(confCallSession, mConfCallProfile); 563 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 564 if (isTestType(TEST_TYPE_CONFERENCE_FAILED_REMOTE_TERMINATED)) { 565 return; 566 } 567 Log.d(LOG_TAG, "invokeCallSessionMergeFailed"); 568 mListener.callSessionMergeFailed(getReasonInfo( 569 ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL, 570 ImsReasonInfo.CODE_UNSPECIFIED)); 571 } catch (Throwable t) { 572 Throwable cause = t.getCause(); 573 if (t instanceof DeadObjectException 574 || (cause != null && cause instanceof DeadObjectException)) { 575 fail("starting cause Throwable to be thrown: " + t); 576 } 577 } 578 }); 579 } 580 mergeExistConference()581 private void mergeExistConference() { 582 addExistConferenceSession(); 583 mConfSession.setState(ImsCallSessionImplBase.State.ESTABLISHED); 584 585 postAndRunTask(() -> { 586 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 587 try { 588 if (mListener == null) { 589 return; 590 } 591 592 Log.d(LOG_TAG, "invokeMergeComplete into an existing conference call"); 593 TestImsCallSessionImpl newSession = null; 594 mListener.callSessionMergeComplete(newSession); 595 596 if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_AFTER_SWAP)) { 597 mConferenceHelper.getBackGroundSession().setState(State.TERMINATED); 598 mConferenceHelper.getBackGroundSession().invokeTerminatedByRemote(); 599 } else { 600 mConferenceHelper.getForeGroundSession().setState(State.TERMINATED); 601 mConferenceHelper.getForeGroundSession().invokeTerminatedByRemote(); 602 } 603 } catch (Throwable t) { 604 Throwable cause = t.getCause(); 605 if (t instanceof DeadObjectException 606 || (cause != null && cause instanceof DeadObjectException)) { 607 fail("starting cause Throwable to be thrown: " + t); 608 } 609 } 610 }); 611 612 postAndRunTask(() -> { 613 try { 614 if (mListener == null) { 615 return; 616 } 617 // after joining an existing conference call, the participants are three. 618 mConfSession.sendConferenceStateUpdated("connected", 3); 619 } catch (Throwable t) { 620 Throwable cause = t.getCause(); 621 if (t instanceof DeadObjectException 622 || (cause != null && cause instanceof DeadObjectException)) { 623 fail("starting cause Throwable to be thrown: " + t); 624 } 625 } 626 }); 627 } 628 createConferenceSession()629 private void createConferenceSession() { 630 mConferenceHelper.setForeGroundSession(this); 631 mConferenceHelper.setBackGroundSession(mConferenceHelper.getHoldSession()); 632 633 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile( 634 ImsStreamMediaProfile.AUDIO_QUALITY_AMR, 635 ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE, 636 ImsStreamMediaProfile.VIDEO_QUALITY_NONE, 637 ImsStreamMediaProfile.DIRECTION_INVALID, 638 ImsStreamMediaProfile.RTT_MODE_DISABLED); 639 640 mConfCallProfile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL, 641 ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile); 642 mConfCallProfile.setCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE, true); 643 644 mConfSession = new TestImsCallSessionImpl(mConfCallProfile); 645 mConfSession.setConferenceHelper(mConferenceHelper); 646 mConferenceHelper.addSession(mConfSession); 647 mConferenceHelper.setConferenceSession(mConfSession); 648 } 649 addExistConferenceSession()650 private void addExistConferenceSession() { 651 mConferenceHelper.setForeGroundSession(this); 652 mConferenceHelper.setBackGroundSession(mConferenceHelper.getHoldSession()); 653 654 if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_AFTER_SWAP) 655 || isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP)) { 656 mConfSession = this; 657 } else { 658 mConfSession = mConferenceHelper.getHoldSession(); 659 } 660 661 mConferenceHelper.setConferenceSession(mConfSession); 662 } 663 invokeSessionTerminated()664 private void invokeSessionTerminated() { 665 Log.d(LOG_TAG, "invokeCallSessionTerminated"); 666 mListener.callSessionTerminated(getReasonInfo( 667 ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE, 668 ImsReasonInfo.CODE_UNSPECIFIED)); 669 } 670 invokeTerminatedByRemote()671 private void invokeTerminatedByRemote() { 672 Log.d(LOG_TAG, "invokeCallSessionTerminated by remote"); 673 mListener.callSessionTerminated(getReasonInfo( 674 ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 675 ImsReasonInfo.CODE_UNSPECIFIED)); 676 } 677 sendConferenceStateUpdated(String state, int count)678 private void sendConferenceStateUpdated(String state, int count) { 679 ImsConferenceState confState = new ImsConferenceState(); 680 int counter = 5553639; 681 for (int i = 0; i < count; ++i) { 682 confState.mParticipants.put((String.valueOf(++counter)), 683 createConferenceParticipant(("tel:" + String.valueOf(++counter)), 684 ("tel:" + String.valueOf(++counter)), (String.valueOf(++counter)), state, 200)); 685 } 686 687 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE); 688 Log.d(LOG_TAG, "invokeCallSessionConferenceStateUpdated"); 689 mListener.callSessionConferenceStateUpdated(confState); 690 } 691 692 /** 693 * Send a hold response for this listener. 694 */ sendHoldResponse()695 public void sendHoldResponse() { 696 postAndRunTask(() -> { 697 try { 698 if (mListener == null) { 699 return; 700 } 701 Log.d(LOG_TAG, "invokeHeld mCallId = " + mCallId); 702 mListener.callSessionHeld(mCallProfile); 703 mIsOnHold = true; 704 } catch (Throwable t) { 705 Throwable cause = t.getCause(); 706 if (t instanceof DeadObjectException 707 || (cause != null && cause instanceof DeadObjectException)) { 708 fail("starting cause Throwable to be thrown: " + t); 709 } 710 } 711 }); 712 setState(ImsCallSessionImplBase.State.ESTABLISHED); 713 } 714 sendHoldFailRemoteTerminated()715 public void sendHoldFailRemoteTerminated() { 716 postAndRunTask(() -> { 717 try { 718 if (mListener == null) { 719 return; 720 } 721 Log.d(LOG_TAG, "invokeHoldFailed mCallId = " + mCallId); 722 mListener.callSessionHoldFailed(getReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED, 723 ImsReasonInfo.CODE_UNSPECIFIED)); 724 } catch (Throwable t) { 725 Throwable cause = t.getCause(); 726 if (t instanceof DeadObjectException 727 || (cause != null && cause instanceof DeadObjectException)) { 728 fail("starting cause Throwable to be thrown: " + t); 729 } 730 } 731 }); 732 setState(ImsCallSessionImplBase.State.ESTABLISHED); 733 734 sendTerminatedByRemote(); 735 } 736 sendTerminatedByRemote()737 public void sendTerminatedByRemote() { 738 postAndRunTask(() -> { 739 try { 740 if (mListener == null) { 741 return; 742 } 743 invokeTerminatedByRemote(); 744 setState(ImsCallSessionImplBase.State.TERMINATED); 745 } catch (Throwable t) { 746 Throwable cause = t.getCause(); 747 if (t instanceof DeadObjectException 748 || (cause != null && cause instanceof DeadObjectException)) { 749 fail("starting cause Throwable to be thrown: " + t); 750 } 751 } 752 }); 753 } 754 sendMergedFailed()755 public void sendMergedFailed() { 756 postAndRunTask(() -> { 757 try { 758 if (mListener == null) { 759 return; 760 } 761 Log.d(LOG_TAG, "invokeMergedFailed mCallId = " + mCallId); 762 mListener.callSessionMergeFailed(getReasonInfo( 763 ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL, 764 ImsReasonInfo.CODE_UNSPECIFIED)); 765 } catch (Throwable t) { 766 Throwable cause = t.getCause(); 767 if (t instanceof DeadObjectException 768 || (cause != null && cause instanceof DeadObjectException)) { 769 fail("starting cause Throwable to be thrown: " + t); 770 } 771 } 772 }); 773 } 774 sendHoldReceived()775 public void sendHoldReceived() { 776 postAndRunTask(() -> { 777 try { 778 if (mListener == null) { 779 return; 780 } 781 Log.d(LOG_TAG, "invokeHoldReceived mCallId = " + mCallId); 782 mListener.callSessionHoldReceived(mCallProfile); 783 } catch (Throwable t) { 784 Throwable cause = t.getCause(); 785 if (t instanceof DeadObjectException 786 || (cause != null && cause instanceof DeadObjectException)) { 787 fail("starting cause Throwable to be thrown: " + t); 788 } 789 } 790 }); 791 setState(ImsCallSessionImplBase.State.ESTABLISHED); 792 } 793 sendResumeReceived()794 public void sendResumeReceived() { 795 postAndRunTask(() -> { 796 try { 797 if (mListener == null) { 798 return; 799 } 800 Log.d(LOG_TAG, "invokeResumeReceived mCallId = " + mCallId); 801 mListener.callSessionResumeReceived(mCallProfile); 802 } catch (Throwable t) { 803 Throwable cause = t.getCause(); 804 if (t instanceof DeadObjectException 805 || (cause != null && cause instanceof DeadObjectException)) { 806 fail("starting cause Throwable to be thrown: " + t); 807 } 808 } 809 }); 810 setState(ImsCallSessionImplBase.State.ESTABLISHED); 811 } 812 createConferenceParticipant(String user, String endpoint, String displayText, String status, int sipStatusCode)813 public Bundle createConferenceParticipant(String user, String endpoint, 814 String displayText, String status, int sipStatusCode) { 815 Bundle participant = new Bundle(); 816 817 participant.putString(ImsConferenceState.STATUS, status); 818 participant.putString(ImsConferenceState.USER, user); 819 participant.putString(ImsConferenceState.ENDPOINT, endpoint); 820 participant.putString(ImsConferenceState.DISPLAY_TEXT, displayText); 821 participant.putInt(ImsConferenceState.SIP_STATUS_CODE, sipStatusCode); 822 return participant; 823 } 824 setConferenceHelper(ConferenceHelper confHelper)825 public void setConferenceHelper(ConferenceHelper confHelper) { 826 mConferenceHelper = confHelper; 827 } 828 isSessionOnHold()829 public boolean isSessionOnHold() { 830 return mIsOnHold; 831 } 832 setState(int state)833 private void setState(int state) { 834 if (mState != state) { 835 Log.d(LOG_TAG, "ImsCallSession :: " + mState + " >> " + state); 836 mState = state; 837 } 838 } 839 isInTerminated()840 public boolean isInTerminated() { 841 return (mState == ImsCallSessionImplBase.State.TERMINATED) ? true : false; 842 } 843 isRenegotiating()844 public boolean isRenegotiating() { 845 return (mState == State.RENEGOTIATING) ? true : false; 846 } 847 getReasonInfo(int code, int extraCode)848 private ImsReasonInfo getReasonInfo(int code, int extraCode) { 849 ImsReasonInfo reasonInfo = new ImsReasonInfo(code, extraCode, ""); 850 return reasonInfo; 851 } 852 addTestType(int type)853 public void addTestType(int type) { 854 mTestType |= type; 855 } 856 removeTestType(int type)857 public void removeTestType(int type) { 858 mTestType &= ~type; 859 } 860 isTestType(int type)861 public boolean isTestType(int type) { 862 return ((mTestType & type) == type); 863 } 864 getExecutor()865 public Executor getExecutor() { 866 return mCallBackExecutor; 867 } 868 postAndRunTask(Runnable task)869 private void postAndRunTask(Runnable task) { 870 mCallBackExecutor.execute(task); 871 } 872 createLooper(String name)873 private static Looper createLooper(String name) { 874 HandlerThread thread = new HandlerThread(name); 875 thread.start(); 876 877 Looper looper = thread.getLooper(); 878 879 if (looper == null) { 880 return Looper.getMainLooper(); 881 } 882 return looper; 883 } 884 /** 885 * Executes the tasks in the other thread rather than the calling thread. 886 */ 887 public class MessageExecutor extends Handler implements Executor { MessageExecutor(String name)888 public MessageExecutor(String name) { 889 super(createLooper(name)); 890 } 891 892 @Override execute(Runnable r)893 public void execute(Runnable r) { 894 Message m = Message.obtain(this, 0 /* don't care */, r); 895 m.sendToTarget(); 896 } 897 898 @Override handleMessage(Message msg)899 public void handleMessage(Message msg) { 900 if (msg.obj instanceof Runnable) { 901 executeInternal((Runnable) msg.obj); 902 } else { 903 Log.d(LOG_TAG, "[MessageExecutor] handleMessage :: " 904 + "Not runnable object; ignore the msg=" + msg); 905 } 906 } 907 executeInternal(Runnable r)908 private void executeInternal(Runnable r) { 909 try { 910 r.run(); 911 } catch (Throwable t) { 912 Log.d(LOG_TAG, "[MessageExecutor] executeInternal :: run task=" + r); 913 t.printStackTrace(); 914 } 915 } 916 } 917 918 /** 919 * ANBR Query received. 920 */ callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond)921 public void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) { 922 if (mListener != null) { 923 mListener.callSessionSendAnbrQuery(mediaType, direction, bitsPerSecond); 924 } else { 925 Log.d(LOG_TAG, "callSessionSendAnbrQuery - listener is null"); 926 } 927 } 928 929 /** 930 * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer. 931 */ callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond)932 public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) { 933 mAnbrValues[0] = mediaType; 934 mAnbrValues[1] = direction; 935 mAnbrValues[2] = bitsPerSecond; 936 } 937 938 /** 939 * Returns the Anbr values received from NW. 940 */ getAnbrValues()941 public int[] getAnbrValues() { 942 if (mAnbrValues[0] > 0 && mAnbrValues[1] > 0 && mAnbrValues[2] >= 0) { 943 return mAnbrValues; 944 } else { 945 Log.d(LOG_TAG, "getAnbrValues - invalid values"); 946 return null; 947 } 948 } 949 950 /** 951 * Clears the Anbr values. 952 */ resetAnbrValues()953 public void resetAnbrValues() { 954 mAnbrValues[0] = -1; 955 mAnbrValues[1] = -1; 956 mAnbrValues[2] = -1; 957 } 958 959 /** 960 * Returns whether IMS call session transfer result is notified. 961 */ isTransferResultNotified()962 public boolean isTransferResultNotified() { 963 return mIsTransferResultNotified; 964 } 965 } 966