ImsCall.java revision 7b72dc2e9dacdb51e998775e8505744a574afb12
1/* 2 * Copyright (c) 2013 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 17package com.android.ims; 18 19import com.android.internal.R; 20 21import java.util.ArrayList; 22import java.util.Iterator; 23import java.util.List; 24import java.util.Map.Entry; 25import java.util.Set; 26 27import android.content.Context; 28import android.net.Uri; 29import android.os.Bundle; 30import android.os.Message; 31import android.telecom.ConferenceParticipant; 32import android.telephony.Rlog; 33import android.util.Log; 34 35import com.android.ims.internal.ICall; 36import com.android.ims.internal.ImsCallSession; 37import com.android.ims.internal.ImsStreamMediaSession; 38import com.android.internal.annotations.VisibleForTesting; 39 40/** 41 * Handles an IMS voice / video call over LTE. You can instantiate this class with 42 * {@link ImsManager}. 43 * 44 * @hide 45 */ 46public class ImsCall implements ICall { 47 // Mode of USSD message 48 public static final int USSD_MODE_NOTIFY = 0; 49 public static final int USSD_MODE_REQUEST = 1; 50 51 private static final String TAG = "ImsCall"; 52 private static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */ 53 private static final boolean DBG = FORCE_DEBUG || Rlog.isLoggable(TAG, Log.DEBUG); 54 private static final boolean VDBG = FORCE_DEBUG || Rlog.isLoggable(TAG, Log.VERBOSE); 55 56 /** 57 * Listener for events relating to an IMS call, such as when a call is being 58 * received ("on ringing") or a call is outgoing ("on calling"). 59 * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p> 60 */ 61 public static class Listener { 62 /** 63 * Called when a request is sent out to initiate a new call 64 * and 1xx response is received from the network. 65 * The default implementation calls {@link #onCallStateChanged}. 66 * 67 * @param call the call object that carries out the IMS call 68 */ 69 public void onCallProgressing(ImsCall call) { 70 onCallStateChanged(call); 71 } 72 73 /** 74 * Called when the call is established. 75 * The default implementation calls {@link #onCallStateChanged}. 76 * 77 * @param call the call object that carries out the IMS call 78 */ 79 public void onCallStarted(ImsCall call) { 80 onCallStateChanged(call); 81 } 82 83 /** 84 * Called when the call setup is failed. 85 * The default implementation calls {@link #onCallError}. 86 * 87 * @param call the call object that carries out the IMS call 88 * @param reasonInfo detailed reason of the call setup failure 89 */ 90 public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) { 91 onCallError(call, reasonInfo); 92 } 93 94 /** 95 * Called when the call is terminated. 96 * The default implementation calls {@link #onCallStateChanged}. 97 * 98 * @param call the call object that carries out the IMS call 99 * @param reasonInfo detailed reason of the call termination 100 */ 101 public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) { 102 // Store the call termination reason 103 104 onCallStateChanged(call); 105 } 106 107 /** 108 * Called when the call is in hold. 109 * The default implementation calls {@link #onCallStateChanged}. 110 * 111 * @param call the call object that carries out the IMS call 112 */ 113 public void onCallHeld(ImsCall call) { 114 onCallStateChanged(call); 115 } 116 117 /** 118 * Called when the call hold is failed. 119 * The default implementation calls {@link #onCallError}. 120 * 121 * @param call the call object that carries out the IMS call 122 * @param reasonInfo detailed reason of the call hold failure 123 */ 124 public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) { 125 onCallError(call, reasonInfo); 126 } 127 128 /** 129 * Called when the call hold is received from the remote user. 130 * The default implementation calls {@link #onCallStateChanged}. 131 * 132 * @param call the call object that carries out the IMS call 133 */ 134 public void onCallHoldReceived(ImsCall call) { 135 onCallStateChanged(call); 136 } 137 138 /** 139 * Called when the call is in call. 140 * The default implementation calls {@link #onCallStateChanged}. 141 * 142 * @param call the call object that carries out the IMS call 143 */ 144 public void onCallResumed(ImsCall call) { 145 onCallStateChanged(call); 146 } 147 148 /** 149 * Called when the call resume is failed. 150 * The default implementation calls {@link #onCallError}. 151 * 152 * @param call the call object that carries out the IMS call 153 * @param reasonInfo detailed reason of the call resume failure 154 */ 155 public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) { 156 onCallError(call, reasonInfo); 157 } 158 159 /** 160 * Called when the call resume is received from the remote user. 161 * The default implementation calls {@link #onCallStateChanged}. 162 * 163 * @param call the call object that carries out the IMS call 164 */ 165 public void onCallResumeReceived(ImsCall call) { 166 onCallStateChanged(call); 167 } 168 169 /** 170 * Called when the call is in call. 171 * The default implementation calls {@link #onCallStateChanged}. 172 * 173 * @param call the call object that carries out the IMS call 174 * @param swapCalls {@code true} if the foreground and background calls should be swapped 175 * now that the merge has completed. 176 */ 177 public void onCallMerged(ImsCall call, boolean swapCalls) { 178 onCallStateChanged(call); 179 } 180 181 /** 182 * Called when the call merge is failed. 183 * The default implementation calls {@link #onCallError}. 184 * 185 * @param call the call object that carries out the IMS call 186 * @param reasonInfo detailed reason of the call merge failure 187 */ 188 public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) { 189 onCallError(call, reasonInfo); 190 } 191 192 /** 193 * Called when the call is updated (except for hold/unhold). 194 * The default implementation calls {@link #onCallStateChanged}. 195 * 196 * @param call the call object that carries out the IMS call 197 */ 198 public void onCallUpdated(ImsCall call) { 199 onCallStateChanged(call); 200 } 201 202 /** 203 * Called when the call update is failed. 204 * The default implementation calls {@link #onCallError}. 205 * 206 * @param call the call object that carries out the IMS call 207 * @param reasonInfo detailed reason of the call update failure 208 */ 209 public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) { 210 onCallError(call, reasonInfo); 211 } 212 213 /** 214 * Called when the call update is received from the remote user. 215 * 216 * @param call the call object that carries out the IMS call 217 */ 218 public void onCallUpdateReceived(ImsCall call) { 219 // no-op 220 } 221 222 /** 223 * Called when the call is extended to the conference call. 224 * The default implementation calls {@link #onCallStateChanged}. 225 * 226 * @param call the call object that carries out the IMS call 227 * @param newCall the call object that is extended to the conference from the active call 228 */ 229 public void onCallConferenceExtended(ImsCall call, ImsCall newCall) { 230 onCallStateChanged(call); 231 } 232 233 /** 234 * Called when the conference extension is failed. 235 * The default implementation calls {@link #onCallError}. 236 * 237 * @param call the call object that carries out the IMS call 238 * @param reasonInfo detailed reason of the conference extension failure 239 */ 240 public void onCallConferenceExtendFailed(ImsCall call, 241 ImsReasonInfo reasonInfo) { 242 onCallError(call, reasonInfo); 243 } 244 245 /** 246 * Called when the conference extension is received from the remote user. 247 * 248 * @param call the call object that carries out the IMS call 249 * @param newCall the call object that is extended to the conference from the active call 250 */ 251 public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) { 252 onCallStateChanged(call); 253 } 254 255 /** 256 * Called when the invitation request of the participants is delivered to 257 * the conference server. 258 * 259 * @param call the call object that carries out the IMS call 260 */ 261 public void onCallInviteParticipantsRequestDelivered(ImsCall call) { 262 // no-op 263 } 264 265 /** 266 * Called when the invitation request of the participants is failed. 267 * 268 * @param call the call object that carries out the IMS call 269 * @param reasonInfo detailed reason of the conference invitation failure 270 */ 271 public void onCallInviteParticipantsRequestFailed(ImsCall call, 272 ImsReasonInfo reasonInfo) { 273 // no-op 274 } 275 276 /** 277 * Called when the removal request of the participants is delivered to 278 * the conference server. 279 * 280 * @param call the call object that carries out the IMS call 281 */ 282 public void onCallRemoveParticipantsRequestDelivered(ImsCall call) { 283 // no-op 284 } 285 286 /** 287 * Called when the removal request of the participants is failed. 288 * 289 * @param call the call object that carries out the IMS call 290 * @param reasonInfo detailed reason of the conference removal failure 291 */ 292 public void onCallRemoveParticipantsRequestFailed(ImsCall call, 293 ImsReasonInfo reasonInfo) { 294 // no-op 295 } 296 297 /** 298 * Called when the conference state is updated. 299 * 300 * @param call the call object that carries out the IMS call 301 * @param state state of the participant who is participated in the conference call 302 */ 303 public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) { 304 // no-op 305 } 306 307 /** 308 * Called when the state of IMS conference participant(s) has changed. 309 * 310 * @param call the call object that carries out the IMS call. 311 * @param participants the participant(s) and their new state information. 312 */ 313 public void onConferenceParticipantsStateChanged(ImsCall call, 314 List<ConferenceParticipant> participants) { 315 // no-op 316 } 317 318 /** 319 * Called when the USSD message is received from the network. 320 * 321 * @param mode mode of the USSD message (REQUEST / NOTIFY) 322 * @param ussdMessage USSD message 323 */ 324 public void onCallUssdMessageReceived(ImsCall call, 325 int mode, String ussdMessage) { 326 // no-op 327 } 328 329 /** 330 * Called when an error occurs. The default implementation is no op. 331 * overridden. The default implementation is no op. Error events are 332 * not re-directed to this callback and are handled in {@link #onCallError}. 333 * 334 * @param call the call object that carries out the IMS call 335 * @param reasonInfo detailed reason of this error 336 * @see ImsReasonInfo 337 */ 338 public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) { 339 // no-op 340 } 341 342 /** 343 * Called when an event occurs and the corresponding callback is not 344 * overridden. The default implementation is no op. Error events are 345 * not re-directed to this callback and are handled in {@link #onCallError}. 346 * 347 * @param call the call object that carries out the IMS call 348 */ 349 public void onCallStateChanged(ImsCall call) { 350 // no-op 351 } 352 353 /** 354 * Called when the call moves the hold state to the conversation state. 355 * For example, when merging the active & hold call, the state of all the hold call 356 * will be changed from hold state to conversation state. 357 * This callback method can be invoked even though the application does not trigger 358 * any operations. 359 * 360 * @param call the call object that carries out the IMS call 361 * @param state the detailed state of call state changes; 362 * Refer to CALL_STATE_* in {@link ImsCall} 363 */ 364 public void onCallStateChanged(ImsCall call, int state) { 365 // no-op 366 } 367 368 /** 369 * Called when TTY mode of remote party changed 370 * 371 * @param call the call object that carries out the IMS call 372 * @param mode TTY mode of remote party 373 */ 374 public void onCallSessionTtyModeReceived(ImsCall call, int mode) { 375 // no-op 376 } 377 378 /** 379 * Called when handover occurs from one access technology to another. 380 * 381 * @param session IMS session object 382 * @param srcAccessTech original access technology 383 * @param targetAccessTech new access technology 384 * @param reasonInfo 385 */ 386 public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech, 387 ImsReasonInfo reasonInfo) { 388 } 389 390 /** 391 * Called when handover from one access technology to another fails. 392 * 393 * @param session IMS session object 394 * @param srcAccessTech original access technology 395 * @param targetAccessTech new access technology 396 * @param reasonInfo 397 */ 398 public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech, 399 ImsReasonInfo reasonInfo) { 400 } 401 } 402 403 404 405 // List of update operation for IMS call control 406 private static final int UPDATE_NONE = 0; 407 private static final int UPDATE_HOLD = 1; 408 private static final int UPDATE_HOLD_MERGE = 2; 409 private static final int UPDATE_RESUME = 3; 410 private static final int UPDATE_MERGE = 4; 411 private static final int UPDATE_EXTEND_TO_CONFERENCE = 5; 412 private static final int UPDATE_UNSPECIFIED = 6; 413 414 // For synchronization of private variables 415 private Object mLockObj = new Object(); 416 private Context mContext; 417 418 // true if the call is established & in the conversation state 419 private boolean mInCall = false; 420 // true if the call is on hold 421 // If it is triggered by the local, mute the call. Otherwise, play local hold tone 422 // or network generated media. 423 private boolean mHold = false; 424 // true if the call is on mute 425 private boolean mMute = false; 426 // It contains the exclusive call update request. Refer to UPDATE_*. 427 private int mUpdateRequest = UPDATE_NONE; 428 429 private ImsCall.Listener mListener = null; 430 431 // When merging two calls together, the "peer" call that will merge into this call. 432 private ImsCall mMergePeer = null; 433 // When merging two calls together, the "host" call we are merging into. 434 private ImsCall mMergeHost = null; 435 436 // Wrapper call session to interworking the IMS service (server). 437 private ImsCallSession mSession = null; 438 // Call profile of the current session. 439 // It can be changed at anytime when the call is updated. 440 private ImsCallProfile mCallProfile = null; 441 // Call profile to be updated after the application's action (accept/reject) 442 // to the call update. After the application's action (accept/reject) is done, 443 // it will be set to null. 444 private ImsCallProfile mProposedCallProfile = null; 445 private ImsReasonInfo mLastReasonInfo = null; 446 447 // Media session to control media (audio/video) operations for an IMS call 448 private ImsStreamMediaSession mMediaSession = null; 449 450 // The temporary ImsCallSession that could represent the merged call once 451 // we receive notification that the merge was successful. 452 private ImsCallSession mTransientConferenceSession = null; 453 // While a merge is progressing, we bury any session termination requests 454 // made on the original ImsCallSession until we have closure on the merge request 455 // If the request ultimately fails, we need to act on the termination request 456 // that we buried temporarily. We do this because we feel that timing issues could 457 // cause the termination request to occur just because the merge is succeeding. 458 private boolean mSessionEndDuringMerge = false; 459 // Just like mSessionEndDuringMerge, we need to keep track of the reason why the 460 // termination request was made on the original session in case we need to act 461 // on it in the case of a merge failure. 462 private ImsReasonInfo mSessionEndDuringMergeReasonInfo = null; 463 // This flag is used to indicate if this ImsCall was merged into a conference 464 // or not. It is used primarily to determine if a disconnect sound should 465 // be heard when the call is terminated. 466 private boolean mIsMerged = false; 467 // If true, this flag means that this ImsCall is in the process of merging 468 // into a conference but it does not yet have closure on if it was 469 // actually added to the conference or not. false implies that it either 470 // is not part of a merging conference or already knows if it was 471 // successfully added. 472 private boolean mCallSessionMergePending = false; 473 474 /** 475 * Create an IMS call object. 476 * 477 * @param context the context for accessing system services 478 * @param profile the call profile to make/take a call 479 */ 480 public ImsCall(Context context, ImsCallProfile profile) { 481 mContext = context; 482 mCallProfile = profile; 483 } 484 485 /** 486 * Closes this object. This object is not usable after being closed. 487 */ 488 @Override 489 public void close() { 490 synchronized(mLockObj) { 491 if (mSession != null) { 492 mSession.close(); 493 mSession = null; 494 } 495 496 mCallProfile = null; 497 mProposedCallProfile = null; 498 mLastReasonInfo = null; 499 mMediaSession = null; 500 } 501 } 502 503 /** 504 * Checks if the call has a same remote user identity or not. 505 * 506 * @param userId the remote user identity 507 * @return true if the remote user identity is equal; otherwise, false 508 */ 509 @Override 510 public boolean checkIfRemoteUserIsSame(String userId) { 511 if (userId == null) { 512 return false; 513 } 514 515 return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, "")); 516 } 517 518 /** 519 * Checks if the call is equal or not. 520 * 521 * @param call the call to be compared 522 * @return true if the call is equal; otherwise, false 523 */ 524 @Override 525 public boolean equalsTo(ICall call) { 526 if (call == null) { 527 return false; 528 } 529 530 if (call instanceof ImsCall) { 531 return this.equals(call); 532 } 533 534 return false; 535 } 536 537 public static boolean isSessionAlive(ImsCallSession session) { 538 return session != null && session.isAlive(); 539 } 540 541 /** 542 * Gets the negotiated (local & remote) call profile. 543 * 544 * @return a {@link ImsCallProfile} object that has the negotiated call profile 545 */ 546 public ImsCallProfile getCallProfile() { 547 synchronized(mLockObj) { 548 return mCallProfile; 549 } 550 } 551 552 /** 553 * Gets the local call profile (local capabilities). 554 * 555 * @return a {@link ImsCallProfile} object that has the local call profile 556 */ 557 public ImsCallProfile getLocalCallProfile() throws ImsException { 558 synchronized(mLockObj) { 559 if (mSession == null) { 560 throw new ImsException("No call session", 561 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 562 } 563 564 try { 565 return mSession.getLocalCallProfile(); 566 } catch (Throwable t) { 567 loge("getLocalCallProfile :: ", t); 568 throw new ImsException("getLocalCallProfile()", t, 0); 569 } 570 } 571 } 572 573 /** 574 * Gets the remote call profile (remote capabilities). 575 * 576 * @return a {@link ImsCallProfile} object that has the remote call profile 577 */ 578 public ImsCallProfile getRemoteCallProfile() throws ImsException { 579 synchronized(mLockObj) { 580 if (mSession == null) { 581 throw new ImsException("No call session", 582 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 583 } 584 585 try { 586 return mSession.getRemoteCallProfile(); 587 } catch (Throwable t) { 588 loge("getRemoteCallProfile :: ", t); 589 throw new ImsException("getRemoteCallProfile()", t, 0); 590 } 591 } 592 } 593 594 /** 595 * Gets the call profile proposed by the local/remote user. 596 * 597 * @return a {@link ImsCallProfile} object that has the proposed call profile 598 */ 599 public ImsCallProfile getProposedCallProfile() { 600 synchronized(mLockObj) { 601 if (!isInCall()) { 602 return null; 603 } 604 605 return mProposedCallProfile; 606 } 607 } 608 609 /** 610 * Gets the state of the {@link ImsCallSession} that carries this call. 611 * The value returned must be one of the states in {@link ImsCallSession#State}. 612 * 613 * @return the session state 614 */ 615 public int getState() { 616 synchronized(mLockObj) { 617 if (mSession == null) { 618 return ImsCallSession.State.IDLE; 619 } 620 621 return mSession.getState(); 622 } 623 } 624 625 /** 626 * Gets the {@link ImsCallSession} that carries this call. 627 * 628 * @return the session object that carries this call 629 * @hide 630 */ 631 public ImsCallSession getCallSession() { 632 synchronized(mLockObj) { 633 return mSession; 634 } 635 } 636 637 /** 638 * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call. 639 * Almost interface APIs are for the VT (Video Telephony). 640 * 641 * @return the media session object that handles the media operation of this call 642 * @hide 643 */ 644 public ImsStreamMediaSession getMediaSession() { 645 synchronized(mLockObj) { 646 return mMediaSession; 647 } 648 } 649 650 /** 651 * Gets the specified property of this call. 652 * 653 * @param name key to get the extra call information defined in {@link ImsCallProfile} 654 * @return the extra call information as string 655 */ 656 public String getCallExtra(String name) throws ImsException { 657 // Lookup the cache 658 659 synchronized(mLockObj) { 660 // If not found, try to get the property from the remote 661 if (mSession == null) { 662 throw new ImsException("No call session", 663 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 664 } 665 666 try { 667 return mSession.getProperty(name); 668 } catch (Throwable t) { 669 loge("getCallExtra :: ", t); 670 throw new ImsException("getCallExtra()", t, 0); 671 } 672 } 673 } 674 675 /** 676 * Gets the last reason information when the call is not established, cancelled or terminated. 677 * 678 * @return the last reason information 679 */ 680 public ImsReasonInfo getLastReasonInfo() { 681 synchronized(mLockObj) { 682 return mLastReasonInfo; 683 } 684 } 685 686 /** 687 * Checks if the call has a pending update operation. 688 * 689 * @return true if the call has a pending update operation 690 */ 691 public boolean hasPendingUpdate() { 692 synchronized(mLockObj) { 693 return (mUpdateRequest != UPDATE_NONE); 694 } 695 } 696 697 /** 698 * Checks if the call is established. 699 * 700 * @return true if the call is established 701 */ 702 public boolean isInCall() { 703 synchronized(mLockObj) { 704 return mInCall; 705 } 706 } 707 708 /** 709 * Checks if the call is muted. 710 * 711 * @return true if the call is muted 712 */ 713 public boolean isMuted() { 714 synchronized(mLockObj) { 715 return mMute; 716 } 717 } 718 719 /** 720 * Checks if the call is on hold. 721 * 722 * @return true if the call is on hold 723 */ 724 public boolean isOnHold() { 725 synchronized(mLockObj) { 726 return mHold; 727 } 728 } 729 730 /** 731 * Determines if the call is a multiparty call. 732 * 733 * @return {@code True} if the call is a multiparty call. 734 */ 735 public boolean isMultiparty() { 736 synchronized(mLockObj) { 737 if (mSession == null) { 738 return false; 739 } 740 741 return mSession.isMultiparty(); 742 } 743 } 744 745 /** 746 * Marks whether an IMS call is merged. This should be set {@code true} when the call merges 747 * into a conference. 748 * 749 * @param isMerged Whether the call is merged. 750 */ 751 public void setIsMerged(boolean isMerged) { 752 mIsMerged = isMerged; 753 } 754 755 /** 756 * @return {@code true} if the call recently merged into a conference call. 757 */ 758 public boolean isMerged() { 759 return mIsMerged; 760 } 761 762 /** 763 * Sets the listener to listen to the IMS call events. 764 * The method calls {@link #setListener setListener(listener, false)}. 765 * 766 * @param listener to listen to the IMS call events of this object; null to remove listener 767 * @see #setListener(Listener, boolean) 768 */ 769 public void setListener(ImsCall.Listener listener) { 770 setListener(listener, false); 771 } 772 773 /** 774 * Sets the listener to listen to the IMS call events. 775 * A {@link ImsCall} can only hold one listener at a time. Subsequent calls 776 * to this method override the previous listener. 777 * 778 * @param listener to listen to the IMS call events of this object; null to remove listener 779 * @param callbackImmediately set to true if the caller wants to be called 780 * back immediately on the current state 781 */ 782 public void setListener(ImsCall.Listener listener, boolean callbackImmediately) { 783 boolean inCall; 784 boolean onHold; 785 int state; 786 ImsReasonInfo lastReasonInfo; 787 788 synchronized(mLockObj) { 789 mListener = listener; 790 791 if ((listener == null) || !callbackImmediately) { 792 return; 793 } 794 795 inCall = mInCall; 796 onHold = mHold; 797 state = getState(); 798 lastReasonInfo = mLastReasonInfo; 799 } 800 801 try { 802 if (lastReasonInfo != null) { 803 listener.onCallError(this, lastReasonInfo); 804 } else if (inCall) { 805 if (onHold) { 806 listener.onCallHeld(this); 807 } else { 808 listener.onCallStarted(this); 809 } 810 } else { 811 switch (state) { 812 case ImsCallSession.State.ESTABLISHING: 813 listener.onCallProgressing(this); 814 break; 815 case ImsCallSession.State.TERMINATED: 816 listener.onCallTerminated(this, lastReasonInfo); 817 break; 818 default: 819 // Ignore it. There is no action in the other state. 820 break; 821 } 822 } 823 } catch (Throwable t) { 824 loge("setListener()", t); 825 } 826 } 827 828 /** 829 * Mutes or unmutes the mic for the active call. 830 * 831 * @param muted true if the call is muted, false otherwise 832 */ 833 public void setMute(boolean muted) throws ImsException { 834 synchronized(mLockObj) { 835 if (mMute != muted) { 836 mMute = muted; 837 838 try { 839 mSession.setMute(muted); 840 } catch (Throwable t) { 841 loge("setMute :: ", t); 842 throwImsException(t, 0); 843 } 844 } 845 } 846 } 847 848 /** 849 * Attaches an incoming call to this call object. 850 * 851 * @param session the session that receives the incoming call 852 * @throws ImsException if the IMS service fails to attach this object to the session 853 */ 854 public void attachSession(ImsCallSession session) throws ImsException { 855 if (DBG) { 856 log("attachSession :: session=" + session); 857 } 858 859 synchronized(mLockObj) { 860 mSession = session; 861 862 try { 863 mSession.setListener(createCallSessionListener()); 864 } catch (Throwable t) { 865 loge("attachSession :: ", t); 866 throwImsException(t, 0); 867 } 868 } 869 } 870 871 /** 872 * Initiates an IMS call with the call profile which is provided 873 * when creating a {@link ImsCall}. 874 * 875 * @param session the {@link ImsCallSession} for carrying out the call 876 * @param callee callee information to initiate an IMS call 877 * @throws ImsException if the IMS service fails to initiate the call 878 */ 879 public void start(ImsCallSession session, String callee) 880 throws ImsException { 881 if (DBG) { 882 log("start(1) :: session=" + session + ", callee=" + callee); 883 } 884 885 synchronized(mLockObj) { 886 mSession = session; 887 888 try { 889 session.setListener(createCallSessionListener()); 890 session.start(callee, mCallProfile); 891 } catch (Throwable t) { 892 loge("start(1) :: ", t); 893 throw new ImsException("start(1)", t, 0); 894 } 895 } 896 } 897 898 /** 899 * Initiates an IMS conferenca call with the call profile which is provided 900 * when creating a {@link ImsCall}. 901 * 902 * @param session the {@link ImsCallSession} for carrying out the call 903 * @param participants participant list to initiate an IMS conference call 904 * @throws ImsException if the IMS service fails to initiate the call 905 */ 906 public void start(ImsCallSession session, String[] participants) 907 throws ImsException { 908 if (DBG) { 909 log("start(n) :: session=" + session + ", callee=" + participants); 910 } 911 912 synchronized(mLockObj) { 913 mSession = session; 914 915 try { 916 session.setListener(createCallSessionListener()); 917 session.start(participants, mCallProfile); 918 } catch (Throwable t) { 919 loge("start(n) :: ", t); 920 throw new ImsException("start(n)", t, 0); 921 } 922 } 923 } 924 925 /** 926 * Accepts a call. 927 * 928 * @see Listener#onCallStarted 929 * 930 * @param callType The call type the user agreed to for accepting the call. 931 * @throws ImsException if the IMS service fails to accept the call 932 */ 933 public void accept(int callType) throws ImsException { 934 if (VDBG) { 935 log("accept ::"); 936 } 937 938 accept(callType, new ImsStreamMediaProfile()); 939 } 940 941 /** 942 * Accepts a call. 943 * 944 * @param callType call type to be answered in {@link ImsCallProfile} 945 * @param profile a media profile to be answered (audio/audio & video, direction, ...) 946 * @see Listener#onCallStarted 947 * @throws ImsException if the IMS service fails to accept the call 948 */ 949 public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException { 950 if (VDBG) { 951 log("accept :: callType=" + callType + ", profile=" + profile); 952 } 953 954 synchronized(mLockObj) { 955 if (mSession == null) { 956 throw new ImsException("No call to answer", 957 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 958 } 959 960 try { 961 mSession.accept(callType, profile); 962 } catch (Throwable t) { 963 loge("accept :: ", t); 964 throw new ImsException("accept()", t, 0); 965 } 966 967 if (mInCall && (mProposedCallProfile != null)) { 968 if (DBG) { 969 log("accept :: call profile will be updated"); 970 } 971 972 mCallProfile = mProposedCallProfile; 973 mProposedCallProfile = null; 974 } 975 976 // Other call update received 977 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) { 978 mUpdateRequest = UPDATE_NONE; 979 } 980 } 981 } 982 983 /** 984 * Rejects a call. 985 * 986 * @param reason reason code to reject an incoming call 987 * @see Listener#onCallStartFailed 988 * @throws ImsException if the IMS service fails to accept the call 989 */ 990 public void reject(int reason) throws ImsException { 991 if (VDBG) { 992 log("reject :: reason=" + reason); 993 } 994 995 synchronized(mLockObj) { 996 if (mSession != null) { 997 mSession.reject(reason); 998 } 999 1000 if (mInCall && (mProposedCallProfile != null)) { 1001 if (DBG) { 1002 log("reject :: call profile is not updated; destroy it..."); 1003 } 1004 1005 mProposedCallProfile = null; 1006 } 1007 1008 // Other call update received 1009 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) { 1010 mUpdateRequest = UPDATE_NONE; 1011 } 1012 } 1013 } 1014 1015 /** 1016 * Terminates an IMS call. 1017 * 1018 * @param reason reason code to terminate a call 1019 * @throws ImsException if the IMS service fails to terminate the call 1020 */ 1021 public void terminate(int reason) throws ImsException { 1022 if (VDBG) { 1023 log("terminate :: ImsCall=" + this +" reason=" + reason); 1024 } 1025 1026 synchronized(mLockObj) { 1027 mHold = false; 1028 mInCall = false; 1029 1030 if (mSession != null) { 1031 // TODO: Fix the fact that user invoked call terminations during 1032 // the process of establishing a conference call needs to be handled 1033 // as a special case. 1034 // Currently, any terminations (both invoked by the user or 1035 // by the network results in a callSessionTerminated() callback 1036 // from the network. When establishing a conference call we bury 1037 // these callbacks until we get closure on all participants of the 1038 // conference. In some situations, we will throw away the callback 1039 // (when the underlying session of the host of the new conference 1040 // is terminated) or will will unbury it when the conference has been 1041 // established, like when the peer of the new conference goes away 1042 // after the conference has been created. The UI relies on the callback 1043 // to reflect the fact that the call is gone. 1044 // So if a user decides to terminated a call while it is merging, it 1045 // could take a long time to reflect in the UI due to the conference 1046 // processing but we should probably cancel that and just terminate 1047 // the call immediately and clean up. This is not a huge issue right 1048 // now because we have not seen instances where establishing a 1049 // conference takes a long time (more than a second or two). 1050 mSession.terminate(reason); 1051 } 1052 } 1053 } 1054 1055 1056 /** 1057 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called. 1058 * 1059 * @see Listener#onCallHeld, Listener#onCallHoldFailed 1060 * @throws ImsException if the IMS service fails to hold the call 1061 */ 1062 public void hold() throws ImsException { 1063 if (VDBG) { 1064 log("hold :: ImsCall=" + this); 1065 } 1066 1067 if (isOnHold()) { 1068 if (DBG) { 1069 log("hold :: call is already on hold"); 1070 } 1071 return; 1072 } 1073 1074 synchronized(mLockObj) { 1075 if (mUpdateRequest != UPDATE_NONE) { 1076 loge("hold :: update is in progress; request=" + 1077 updateRequestToString(mUpdateRequest)); 1078 throw new ImsException("Call update is in progress", 1079 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1080 } 1081 1082 if (mSession == null) { 1083 loge("hold :: "); 1084 throw new ImsException("No call session", 1085 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1086 } 1087 1088 mSession.hold(createHoldMediaProfile()); 1089 // FIXME: update the state on the callback? 1090 mHold = true; 1091 mUpdateRequest = UPDATE_HOLD; 1092 } 1093 } 1094 1095 /** 1096 * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called. 1097 * 1098 * @see Listener#onCallResumed, Listener#onCallResumeFailed 1099 * @throws ImsException if the IMS service fails to resume the call 1100 */ 1101 public void resume() throws ImsException { 1102 if (VDBG) { 1103 log("resume :: ImsCall=" + this); 1104 } 1105 1106 if (!isOnHold()) { 1107 if (DBG) { 1108 log("resume :: call is in conversation"); 1109 } 1110 return; 1111 } 1112 1113 synchronized(mLockObj) { 1114 if (mUpdateRequest != UPDATE_NONE) { 1115 loge("resume :: update is in progress; request=" + 1116 updateRequestToString(mUpdateRequest)); 1117 throw new ImsException("Call update is in progress", 1118 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1119 } 1120 1121 if (mSession == null) { 1122 loge("resume :: "); 1123 throw new ImsException("No call session", 1124 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1125 } 1126 1127 // mHold is set to false in confirmation callback that the 1128 // ImsCall was resumed. 1129 mUpdateRequest = UPDATE_RESUME; 1130 mSession.resume(createResumeMediaProfile()); 1131 } 1132 } 1133 1134 /** 1135 * Merges the active & hold call. 1136 * 1137 * @see Listener#onCallMerged, Listener#onCallMergeFailed 1138 * @throws ImsException if the IMS service fails to merge the call 1139 */ 1140 private void merge() throws ImsException { 1141 if (VDBG) { 1142 log("merge :: ImsCall=" + this); 1143 } 1144 1145 synchronized(mLockObj) { 1146 if (mUpdateRequest != UPDATE_NONE) { 1147 loge("merge :: update is in progress; request=" + 1148 updateRequestToString(mUpdateRequest)); 1149 throw new ImsException("Call update is in progress", 1150 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1151 } 1152 1153 if (mSession == null) { 1154 loge("merge :: no call session"); 1155 throw new ImsException("No call session", 1156 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1157 } 1158 1159 // if skipHoldBeforeMerge = true, IMS service implementation will 1160 // merge without explicitly holding the call. 1161 if (mHold || (mContext.getResources().getBoolean( 1162 com.android.internal.R.bool.skipHoldBeforeMerge))) { 1163 1164 if (mMergePeer != null && !mMergePeer.isMultiparty() && !isMultiparty()) { 1165 // We only set UPDATE_MERGE when we are adding the first 1166 // calls to the Conference. If there is already a conference 1167 // no special handling is needed. The existing conference 1168 // session will just go active and any other sessions will be terminated 1169 // if needed. There will be no merge failed callback. 1170 // Mark both the host and peer UPDATE_MERGE to ensure both are aware that a 1171 // merge is pending. 1172 mUpdateRequest = UPDATE_MERGE; 1173 mMergePeer.mUpdateRequest = UPDATE_MERGE; 1174 } 1175 1176 mSession.merge(); 1177 } else { 1178 // This code basically says, we need to explicitly hold before requesting a merge 1179 // when we get the callback that the hold was successful (or failed), we should 1180 // automatically request a merge. 1181 mSession.hold(createHoldMediaProfile()); 1182 mHold = true; 1183 mUpdateRequest = UPDATE_HOLD_MERGE; 1184 } 1185 } 1186 } 1187 1188 /** 1189 * Merges the active & hold call. 1190 * 1191 * @param bgCall the background (holding) call 1192 * @see Listener#onCallMerged, Listener#onCallMergeFailed 1193 * @throws ImsException if the IMS service fails to merge the call 1194 */ 1195 public void merge(ImsCall bgCall) throws ImsException { 1196 if (VDBG) { 1197 log("merge(1) :: bgImsCall=" + bgCall); 1198 } 1199 1200 if (bgCall == null) { 1201 throw new ImsException("No background call", 1202 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 1203 } 1204 1205 synchronized(mLockObj) { 1206 // Mark both sessions as pending merge. 1207 this.setCallSessionMergePending(true); 1208 bgCall.setCallSessionMergePending(true); 1209 1210 if ((!isMultiparty() && !bgCall.isMultiparty()) || isMultiparty()) { 1211 // If neither call is multiparty, the current call is the merge host and the bg call 1212 // is the merge peer (ie we're starting a new conference). 1213 // OR 1214 // If this call is multiparty, it is the merge host and the other call is the merge 1215 // peer. 1216 setMergePeer(bgCall); 1217 } else { 1218 // If the bg call is multiparty, it is the merge host. 1219 setMergeHost(bgCall); 1220 } 1221 } 1222 merge(); 1223 } 1224 1225 /** 1226 * Updates the current call's properties (ex. call mode change: video upgrade / downgrade). 1227 */ 1228 public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException { 1229 if (VDBG) { 1230 log("update ::"); 1231 } 1232 1233 if (isOnHold()) { 1234 if (DBG) { 1235 log("update :: call is on hold"); 1236 } 1237 throw new ImsException("Not in a call to update call", 1238 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1239 } 1240 1241 synchronized(mLockObj) { 1242 if (mUpdateRequest != UPDATE_NONE) { 1243 if (DBG) { 1244 log("update :: update is in progress; request=" + 1245 updateRequestToString(mUpdateRequest)); 1246 } 1247 throw new ImsException("Call update is in progress", 1248 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1249 } 1250 1251 if (mSession == null) { 1252 loge("update :: "); 1253 throw new ImsException("No call session", 1254 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1255 } 1256 1257 mSession.update(callType, mediaProfile); 1258 mUpdateRequest = UPDATE_UNSPECIFIED; 1259 } 1260 } 1261 1262 /** 1263 * Extends this call (1-to-1 call) to the conference call 1264 * inviting the specified participants to. 1265 * 1266 */ 1267 public void extendToConference(String[] participants) throws ImsException { 1268 if (VDBG) { 1269 log("extendToConference ::"); 1270 } 1271 1272 if (isOnHold()) { 1273 if (DBG) { 1274 log("extendToConference :: call is on hold"); 1275 } 1276 throw new ImsException("Not in a call to extend a call to conference", 1277 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1278 } 1279 1280 synchronized(mLockObj) { 1281 if (mUpdateRequest != UPDATE_NONE) { 1282 if (DBG) { 1283 log("extendToConference :: update is in progress; request=" + 1284 updateRequestToString(mUpdateRequest)); 1285 } 1286 throw new ImsException("Call update is in progress", 1287 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1288 } 1289 1290 if (mSession == null) { 1291 loge("extendToConference :: "); 1292 throw new ImsException("No call session", 1293 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1294 } 1295 1296 mSession.extendToConference(participants); 1297 mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE; 1298 } 1299 } 1300 1301 /** 1302 * Requests the conference server to invite an additional participants to the conference. 1303 * 1304 */ 1305 public void inviteParticipants(String[] participants) throws ImsException { 1306 if (VDBG) { 1307 log("inviteParticipants ::"); 1308 } 1309 1310 synchronized(mLockObj) { 1311 if (mSession == null) { 1312 loge("inviteParticipants :: "); 1313 throw new ImsException("No call session", 1314 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1315 } 1316 1317 mSession.inviteParticipants(participants); 1318 } 1319 } 1320 1321 /** 1322 * Requests the conference server to remove the specified participants from the conference. 1323 * 1324 */ 1325 public void removeParticipants(String[] participants) throws ImsException { 1326 if (DBG) { 1327 log("removeParticipants ::"); 1328 } 1329 1330 synchronized(mLockObj) { 1331 if (mSession == null) { 1332 loge("removeParticipants :: "); 1333 throw new ImsException("No call session", 1334 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1335 } 1336 1337 mSession.removeParticipants(participants); 1338 } 1339 } 1340 1341 /** 1342 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, 1343 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, 1344 * and event flash to 16. Currently, event flash is not supported. 1345 * 1346 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. 1347 * @param result the result message to send when done. 1348 */ 1349 public void sendDtmf(char c, Message result) { 1350 if (VDBG) { 1351 log("sendDtmf :: code=" + c); 1352 } 1353 1354 synchronized(mLockObj) { 1355 if (mSession != null) { 1356 mSession.sendDtmf(c, result); 1357 } 1358 } 1359 } 1360 1361 /** 1362 * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, 1363 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, 1364 * and event flash to 16. Currently, event flash is not supported. 1365 * 1366 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. 1367 */ 1368 public void startDtmf(char c) { 1369 if (DBG) { 1370 log("startDtmf :: session=" + mSession + ", code=" + c); 1371 } 1372 1373 synchronized(mLockObj) { 1374 if (mSession != null) { 1375 mSession.startDtmf(c); 1376 } 1377 } 1378 } 1379 1380 /** 1381 * Stop a DTMF code. 1382 */ 1383 public void stopDtmf() { 1384 if (DBG) { 1385 log("stopDtmf :: session=" + mSession); 1386 } 1387 1388 synchronized(mLockObj) { 1389 if (mSession != null) { 1390 mSession.stopDtmf(); 1391 } 1392 } 1393 } 1394 1395 /** 1396 * Sends an USSD message. 1397 * 1398 * @param ussdMessage USSD message to send 1399 */ 1400 public void sendUssd(String ussdMessage) throws ImsException { 1401 if (VDBG) { 1402 log("sendUssd :: ussdMessage=" + ussdMessage); 1403 } 1404 1405 synchronized(mLockObj) { 1406 if (mSession == null) { 1407 loge("sendUssd :: "); 1408 throw new ImsException("No call session", 1409 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1410 } 1411 1412 mSession.sendUssd(ussdMessage); 1413 } 1414 } 1415 1416 private void clear(ImsReasonInfo lastReasonInfo) { 1417 mInCall = false; 1418 mHold = false; 1419 mUpdateRequest = UPDATE_NONE; 1420 mLastReasonInfo = lastReasonInfo; 1421 } 1422 1423 /** 1424 * Creates an IMS call session listener. 1425 */ 1426 private ImsCallSession.Listener createCallSessionListener() { 1427 return new ImsCallSessionListenerProxy(); 1428 } 1429 1430 private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) { 1431 ImsCall call = new ImsCall(mContext, profile); 1432 1433 try { 1434 call.attachSession(session); 1435 } catch (ImsException e) { 1436 if (call != null) { 1437 call.close(); 1438 call = null; 1439 } 1440 } 1441 1442 // Do additional operations... 1443 1444 return call; 1445 } 1446 1447 private ImsStreamMediaProfile createHoldMediaProfile() { 1448 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(); 1449 1450 if (mCallProfile == null) { 1451 return mediaProfile; 1452 } 1453 1454 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality; 1455 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality; 1456 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND; 1457 1458 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) { 1459 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND; 1460 } 1461 1462 return mediaProfile; 1463 } 1464 1465 private ImsStreamMediaProfile createResumeMediaProfile() { 1466 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(); 1467 1468 if (mCallProfile == null) { 1469 return mediaProfile; 1470 } 1471 1472 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality; 1473 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality; 1474 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; 1475 1476 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) { 1477 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; 1478 } 1479 1480 return mediaProfile; 1481 } 1482 1483 private void enforceConversationMode() { 1484 if (mInCall) { 1485 mHold = false; 1486 mUpdateRequest = UPDATE_NONE; 1487 } 1488 } 1489 1490 private void mergeInternal() { 1491 if (VDBG) { 1492 log("mergeInternal :: ImsCall=" + this); 1493 } 1494 1495 mSession.merge(); 1496 mUpdateRequest = UPDATE_MERGE; 1497 } 1498 1499 private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) { 1500 ImsCall.Listener listener = mListener; 1501 clear(reasonInfo); 1502 1503 if (listener != null) { 1504 try { 1505 listener.onCallTerminated(this, reasonInfo); 1506 } catch (Throwable t) { 1507 loge("notifyConferenceSessionTerminated :: ", t); 1508 } 1509 } 1510 } 1511 1512 private void notifyConferenceStateUpdated(ImsConferenceState state) { 1513 Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet(); 1514 1515 if (participants == null) { 1516 return; 1517 } 1518 1519 Iterator<Entry<String, Bundle>> iterator = participants.iterator(); 1520 List<ConferenceParticipant> conferenceParticipants = new ArrayList<>(participants.size()); 1521 while (iterator.hasNext()) { 1522 Entry<String, Bundle> entry = iterator.next(); 1523 1524 String key = entry.getKey(); 1525 Bundle confInfo = entry.getValue(); 1526 String status = confInfo.getString(ImsConferenceState.STATUS); 1527 String user = confInfo.getString(ImsConferenceState.USER); 1528 String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT); 1529 String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT); 1530 1531 if (DBG) { 1532 log("notifyConferenceStateUpdated :: key=" + key + 1533 ", status=" + status + 1534 ", user=" + user + 1535 ", displayName= " + displayName + 1536 ", endpoint=" + endpoint); 1537 } 1538 1539 Uri handle = Uri.parse(user); 1540 Uri endpointUri = Uri.parse(endpoint); 1541 int connectionState = ImsConferenceState.getConnectionStateForStatus(status); 1542 1543 ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle, 1544 displayName, endpointUri, connectionState); 1545 conferenceParticipants.add(conferenceParticipant); 1546 } 1547 1548 if (!conferenceParticipants.isEmpty() && mListener != null) { 1549 try { 1550 mListener.onConferenceParticipantsStateChanged(this, conferenceParticipants); 1551 } catch (Throwable t) { 1552 loge("notifyConferenceStateUpdated :: ", t); 1553 } 1554 } 1555 } 1556 1557 /** 1558 * Perform all cleanup and notification around the termination of a session. 1559 * Note that there are 2 distinct modes of operation. The first is when 1560 * we receive a session termination on the primary session when we are 1561 * in the processing of merging. The second is when we are not merging anything 1562 * and the call is terminated. 1563 * 1564 * @param reasonInfo The reason for the session termination 1565 */ 1566 private void processCallTerminated(ImsReasonInfo reasonInfo) { 1567 if (VDBG) { 1568 String reasonString = reasonInfo != null ? reasonInfo.toString() : "null"; 1569 log("processCallTerminated :: ImsCall=" + this + " reason=" + reasonString); 1570 } 1571 1572 ImsCall.Listener listener = null; 1573 1574 synchronized(ImsCall.this) { 1575 // If we are in the midst of establishing a conference, we will bury the termination 1576 // until the merge has completed. If necessary we can surface the termination at this 1577 // point. 1578 if (isCallSessionMergePending()) { 1579 // Since we are in the process of a merge, this trigger means something 1580 // else because it is probably due to the merge happening vs. the 1581 // session is really terminated. Let's flag this and revisit if 1582 // the merge() ends up failing because we will need to take action on the 1583 // mSession in that case since the termination was not due to the merge 1584 // succeeding. 1585 if (DBG) { 1586 log("processCallTerminated :: burying termination during ongoing merge."); 1587 } 1588 mSessionEndDuringMerge = true; 1589 mSessionEndDuringMergeReasonInfo = reasonInfo; 1590 return; 1591 } 1592 1593 // If we are terminating the conference call, notify using conference listeners. 1594 if (isMultiparty()) { 1595 notifyConferenceSessionTerminated(reasonInfo); 1596 return; 1597 } else { 1598 listener = mListener; 1599 clear(reasonInfo); 1600 } 1601 } 1602 1603 if (listener != null) { 1604 try { 1605 listener.onCallTerminated(ImsCall.this, reasonInfo); 1606 } catch (Throwable t) { 1607 loge("processCallTerminated :: ", t); 1608 } 1609 } 1610 } 1611 1612 /** 1613 * This function determines if the ImsCallSession is our actual ImsCallSession or if is 1614 * the transient session used in the process of creating a conference. This function should only 1615 * be called within callbacks that are not directly related to conference merging but might 1616 * potentially still be called on the transient ImsCallSession sent to us from 1617 * callSessionMergeStarted() when we don't really care. In those situations, we probably don't 1618 * want to take any action so we need to know that we can return early. 1619 * 1620 * @param session - The {@link ImsCallSession} that the function needs to analyze 1621 * @return true if this is the transient {@link ImsCallSession}, false otherwise. 1622 */ 1623 private boolean isTransientConferenceSession(ImsCallSession session) { 1624 if (session != null && session != mSession && session == mTransientConferenceSession) { 1625 return true; 1626 } 1627 return false; 1628 } 1629 1630 private void setTransientSessionAsPrimary(ImsCallSession transientSession) { 1631 synchronized (ImsCall.this) { 1632 mSession.setListener(null); 1633 mSession = transientSession; 1634 mSession.setListener(createCallSessionListener()); 1635 } 1636 } 1637 1638 /** 1639 * This function will determine if there is a pending conference and if 1640 * we are ready to finalize processing it. 1641 */ 1642 private void tryProcessConferenceResult() { 1643 if (shouldProcessConferenceResult()) { 1644 if (isMergeHost()) { 1645 processMergeComplete(); 1646 } else if (mMergeHost != null) { 1647 mMergeHost.processMergeComplete(); 1648 } else { 1649 // There must be a merge host at this point. 1650 loge("tryProcessConferenceResult :: No merge host for this conference!"); 1651 } 1652 } 1653 } 1654 1655 /** 1656 * We have detected that a initial conference call has been fully configured. The internal 1657 * state of both {@code ImsCall} objects need to be cleaned up to reflect the new state. 1658 * This function should only be called in the context of the merge host to simplify logic 1659 * 1660 */ 1661 private void processMergeComplete() { 1662 if (VDBG) { 1663 log("processMergeComplete :: ImsCall=" + this); 1664 } 1665 1666 // The logic simplifies if we can assume that this function is only called on 1667 // the merge host. 1668 if (!isMergeHost()) { 1669 loge("processMergeComplete :: We are not the merge host!"); 1670 return; 1671 } 1672 1673 ImsCall.Listener listener; 1674 boolean swapRequired = false; 1675 synchronized(ImsCall.this) { 1676 ImsCall finalHostCall = this; 1677 ImsCall finalPeerCall = mMergePeer; 1678 1679 if (isMultiparty()) { 1680 // The only clean up that we need to do for a merge into an existing conference 1681 // is to deal with the disconnect of the peer if it was successfully added to 1682 // the conference. 1683 setIsMerged(false); 1684 if (!isSessionAlive(mMergePeer.mSession)) { 1685 // If the peer is dead, let's not play a disconnect sound for it when we 1686 // unbury the termination callback. 1687 mMergePeer.setIsMerged(true); 1688 } else { 1689 mMergePeer.setIsMerged(false); 1690 } 1691 } else { 1692 // If we are here, we are not trying to merge a new call into an existing 1693 // conference. That means that there is a transient session on the merge 1694 // host that represents the future conference once all the parties 1695 // have been added to it. So make sure that it exists or else something 1696 // very wrong is going on. 1697 if (mTransientConferenceSession == null) { 1698 loge("processMergeComplete :: No transient session!"); 1699 return; 1700 } 1701 if (mMergePeer == null) { 1702 loge("processMergeComplete :: No merge peer!"); 1703 return; 1704 } 1705 1706 // Since we are the host, we have the transient session attached to us. Let's detach 1707 // it and figure out where we need to set it for the final conference configuration. 1708 ImsCallSession transientConferenceSession = mTransientConferenceSession; 1709 mTransientConferenceSession = null; 1710 1711 // Clear the listener for this transient session, we'll create a new listener 1712 // when it is attached to the final ImsCall that it should live on. 1713 transientConferenceSession.setListener(null); 1714 1715 // Determine which call the transient session should be moved to. If the current 1716 // call session is still alive and the merge peer's session is not, we have a 1717 // situation where the current call failed to merge into the conference but the 1718 // merge peer did merge in to the conference. In this type of scenario the current 1719 // call will continue as a single party call, yet the background call will become 1720 // the conference. 1721 1722 if (isSessionAlive(mSession) && !isSessionAlive(mMergePeer.getCallSession())) { 1723 // I'm the host but we are moving the transient session to the peer since its 1724 // session was disconnected and my session is still alive. This signifies that 1725 // their session was properly added to the conference but mine was not because 1726 // it is probably in the held state as opposed to part of the final conference. 1727 // In this case, we need to set isMerged to false on both calls so the 1728 // disconnect sound is called when either call disconnects. 1729 // Note that this case is only valid if this is an initial conference being 1730 // brought up. 1731 finalHostCall = mMergePeer; 1732 finalPeerCall = this; 1733 swapRequired = true; 1734 setIsMerged(false); 1735 mMergePeer.setIsMerged(false); 1736 if (VDBG) { 1737 log("processMergeComplete :: transient will transfer to merge peer"); 1738 } 1739 } else if (!isSessionAlive(mSession) && isSessionAlive(mMergePeer.getCallSession())) { 1740 // The transient session stays with us and the disconnect sound should be played 1741 // when the merge peer eventually disconnects since it was not actually added to 1742 // the conference and is probably sitting in the held state. 1743 finalHostCall = this; 1744 finalPeerCall = mMergePeer; 1745 swapRequired = false; 1746 setIsMerged(false); 1747 mMergePeer.setIsMerged(false); // Play the disconnect sound 1748 if (VDBG) { 1749 log("processMergeComplete :: transient will stay with the merge host"); 1750 } 1751 } else { 1752 // The transient session stays with us and the disconnect sound should not be 1753 // played when we ripple up the disconnect for the merge peer because it was 1754 // only disconnected to be added to the conference. 1755 finalHostCall = this; 1756 finalPeerCall = mMergePeer; 1757 swapRequired = false; 1758 setIsMerged(false); 1759 mMergePeer.setIsMerged(true); 1760 if (VDBG) { 1761 log("processMergeComplete :: transient will stay with us (I'm the host)."); 1762 } 1763 } 1764 1765 if (VDBG) { 1766 log("processMergeComplete :: call=" + finalHostCall + " is the final host"); 1767 } 1768 1769 // Add the transient session to the ImsCall that ended up being the host for the 1770 // conference. 1771 finalHostCall.setTransientSessionAsPrimary(transientConferenceSession); 1772 } 1773 1774 listener = finalHostCall.mListener; 1775 1776 // Clear all the merge related flags. 1777 clearMergeInfo(); 1778 1779 // For the final peer...let's bubble up any possible disconnects that we had 1780 // during the merge process 1781 finalPeerCall.notifySessionTerminatedDuringMerge(); 1782 // For the final host, let's just bury the disconnects that we my have received 1783 // during the merge process since we are now the host of the conference call. 1784 finalHostCall.clearSessionTerminationFlags(); 1785 } 1786 if (listener != null) { 1787 try { 1788 listener.onCallMerged(ImsCall.this, swapRequired); 1789 } catch (Throwable t) { 1790 loge("processMergeComplete :: ", t); 1791 } 1792 } 1793 return; 1794 } 1795 1796 /** 1797 * Handles the case where the session has ended during a merge by reporting the termination 1798 * reason to listeners. 1799 */ 1800 private void notifySessionTerminatedDuringMerge() { 1801 ImsCall.Listener listener; 1802 boolean notifyFailure = false; 1803 ImsReasonInfo notifyFailureReasonInfo = null; 1804 1805 synchronized(ImsCall.this) { 1806 listener = mListener; 1807 if (mSessionEndDuringMerge) { 1808 // Set some local variables that will send out a notification about a 1809 // previously buried termination callback for our primary session now that 1810 // we know that this is not due to the conference call merging successfully. 1811 if (DBG) { 1812 log("notifySessionTerminatedDuringMerge ::reporting terminate during merge"); 1813 } 1814 notifyFailure = true; 1815 notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo; 1816 } 1817 clearSessionTerminationFlags(); 1818 } 1819 1820 if (listener != null && notifyFailure) { 1821 try { 1822 processCallTerminated(notifyFailureReasonInfo); 1823 } catch (Throwable t) { 1824 loge("notifySessionTerminatedDuringMerge :: ", t); 1825 } 1826 } 1827 } 1828 1829 private void clearSessionTerminationFlags() { 1830 mSessionEndDuringMerge = false; 1831 mSessionEndDuringMergeReasonInfo = null; 1832 } 1833 1834 /** 1835 * We received a callback from ImsCallSession that a merge failed. Clean up all 1836 * internal state to represent this state change. The calling function is a callback 1837 * and should have been called on the session that was in the foreground 1838 * when merge() was originally called. It is assumed that this function will be called 1839 * on the merge host. 1840 * 1841 * @param reasonInfo The {@link ImsReasonInfo} why the merge failed. 1842 */ 1843 private void processMergeFailed(ImsReasonInfo reasonInfo) { 1844 if (VDBG) { 1845 log("processMergeFailed :: this=" + this + "reason=" + reasonInfo); 1846 } 1847 1848 ImsCall.Listener listener; 1849 synchronized(ImsCall.this) { 1850 // The logic simplifies if we can assume that this function is only called on 1851 // the merge host. 1852 if (!isMergeHost()) { 1853 loge("processMergeFailed :: We are not the merge host!"); 1854 return; 1855 } 1856 1857 if (mMergePeer == null) { 1858 loge("processMergeFailed :: No merge peer!"); 1859 return; 1860 } 1861 1862 if (!isMultiparty()) { 1863 if (mTransientConferenceSession == null) { 1864 loge("processMergeFailed :: No transient session!"); 1865 return; 1866 } 1867 // Clean up any work that we performed on the transient session. 1868 mTransientConferenceSession.setListener(null); 1869 mTransientConferenceSession = null; 1870 } 1871 1872 // Ensure the calls being conferenced into the conference has isMerged = false. 1873 setIsMerged(false); 1874 mMergePeer.setIsMerged(false); 1875 1876 listener = mListener; 1877 1878 // Ensure any terminations are surfaced from this session. 1879 notifySessionTerminatedDuringMerge(); 1880 mMergePeer.notifySessionTerminatedDuringMerge(); 1881 1882 // Clear all the various flags around coordinating this merge. 1883 clearMergeInfo(); 1884 } 1885 if (listener != null) { 1886 try { 1887 listener.onCallMergeFailed(ImsCall.this, reasonInfo); 1888 } catch (Throwable t) { 1889 loge("processMergeFailed :: ", t); 1890 } 1891 } 1892 1893 return; 1894 } 1895 1896 private void notifyError(int reason, int statusCode, String message) { 1897 } 1898 1899 private void throwImsException(Throwable t, int code) throws ImsException { 1900 if (t instanceof ImsException) { 1901 throw (ImsException) t; 1902 } else { 1903 throw new ImsException(String.valueOf(code), t, code); 1904 } 1905 } 1906 1907 private void log(String s) { 1908 Rlog.d(TAG, s); 1909 } 1910 1911 /** 1912 * Logs the specified message, as well as the current instance of {@link ImsCall}. 1913 * 1914 * @param s The message to log. 1915 */ 1916 private void logv(String s) { 1917 StringBuilder sb = new StringBuilder(); 1918 sb.append(s); 1919 sb.append(" imsCall="); 1920 sb.append(ImsCall.this); 1921 Rlog.v(TAG, sb.toString()); 1922 } 1923 1924 private void loge(String s) { 1925 Rlog.e(TAG, s); 1926 } 1927 1928 private void loge(String s, Throwable t) { 1929 Rlog.e(TAG, s, t); 1930 } 1931 1932 private class ImsCallSessionListenerProxy extends ImsCallSession.Listener { 1933 @Override 1934 public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) { 1935 if (isTransientConferenceSession(session)) { 1936 // If it is a transient (conference) session, there is no action for this signal. 1937 log("callSessionProgressing :: not supported for transient conference session=" + 1938 session); 1939 return; 1940 } 1941 1942 if (VDBG) { 1943 log("callSessionProgressing :: session=" + session + " profile=" + profile); 1944 } 1945 1946 ImsCall.Listener listener; 1947 1948 synchronized(ImsCall.this) { 1949 listener = mListener; 1950 mCallProfile.mMediaProfile.copyFrom(profile); 1951 } 1952 1953 if (listener != null) { 1954 try { 1955 listener.onCallProgressing(ImsCall.this); 1956 } catch (Throwable t) { 1957 loge("callSessionProgressing :: ", t); 1958 } 1959 } 1960 } 1961 1962 @Override 1963 public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) { 1964 if (VDBG) { 1965 log("callSessionStarted :: session=" + session + " profile=" + profile); 1966 } 1967 1968 if (!isTransientConferenceSession(session)) { 1969 // In the case that we are in the middle of a merge (either host or peer), we have 1970 // closure as far as this call's primary session is concerned. If we are not 1971 // merging...its a NOOP. 1972 setCallSessionMergePending(false); 1973 } else { 1974 if (VDBG) { 1975 log("callSessionStarted :: on transient session=" + session); 1976 } 1977 return; 1978 } 1979 1980 // Check if there is an ongoing conference merge which has completed. If there is 1981 // we can process the merge completion now. 1982 tryProcessConferenceResult(); 1983 1984 if (isTransientConferenceSession(session)) { 1985 // No further processing is needed if this is the transient session. 1986 return; 1987 } 1988 1989 ImsCall.Listener listener; 1990 1991 synchronized(ImsCall.this) { 1992 listener = mListener; 1993 mCallProfile = profile; 1994 } 1995 1996 if (listener != null) { 1997 try { 1998 listener.onCallStarted(ImsCall.this); 1999 } catch (Throwable t) { 2000 loge("callSessionStarted :: ", t); 2001 } 2002 } 2003 } 2004 2005 @Override 2006 public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2007 if (isTransientConferenceSession(session)) { 2008 // We should not get this callback for a transient session. 2009 log("callSessionStartFailed :: not supported for transient conference session=" + 2010 session); 2011 return; 2012 } 2013 2014 if (VDBG) { 2015 log("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo); 2016 } 2017 2018 ImsCall.Listener listener; 2019 2020 synchronized(ImsCall.this) { 2021 listener = mListener; 2022 mLastReasonInfo = reasonInfo; 2023 } 2024 2025 if (listener != null) { 2026 try { 2027 listener.onCallStartFailed(ImsCall.this, reasonInfo); 2028 } catch (Throwable t) { 2029 loge("callSessionStarted :: ", t); 2030 } 2031 } 2032 } 2033 2034 @Override 2035 public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) { 2036 if (isTransientConferenceSession(session)) { 2037 log("callSessionTerminated :: on transient session=" + session); 2038 // This is bad, it should be treated much a callSessionMergeFailed since the 2039 // transient session only exists when in the process of a merge and the 2040 // termination of this session is effectively the end of the merge. 2041 processMergeFailed(reasonInfo); 2042 return; 2043 } 2044 2045 if (VDBG) { 2046 log("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo); 2047 } 2048 2049 // Process the termination first. If we are in the midst of establishing a conference 2050 // call, we may bury this callback until we are done. If there so no conference 2051 // call, the code after this function will be a NOOP. 2052 processCallTerminated(reasonInfo); 2053 2054 // If session has terminated, it is no longer pending merge. 2055 setCallSessionMergePending(false); 2056 2057 // Check if there is an ongoing conference merge which has completed. If there is 2058 // we can process the merge completion now. 2059 tryProcessConferenceResult(); 2060 } 2061 2062 @Override 2063 public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) { 2064 if (isTransientConferenceSession(session)) { 2065 // We should not get this callback for a transient session. 2066 log("callSessionHeld :: not supported for transient conference session=" + session); 2067 return; 2068 } 2069 2070 if (VDBG) { 2071 log("callSessionHeld :: session=" + session + "profile=" + profile); 2072 } 2073 2074 ImsCall.Listener listener; 2075 2076 synchronized(ImsCall.this) { 2077 // If the session was held, it is no longer pending a merge -- this means it could 2078 // not be merged into the conference and was held instead. 2079 setCallSessionMergePending(false); 2080 2081 mCallProfile = profile; 2082 2083 if (mUpdateRequest == UPDATE_HOLD_MERGE) { 2084 // This hold request was made to set the stage for a merge. 2085 mergeInternal(); 2086 return; 2087 } 2088 2089 // Check if there is an ongoing conference merge which has completed. If there is 2090 // we can process the merge completion now. processMergeComplete needs to be 2091 // called on the merge host. 2092 tryProcessConferenceResult(); 2093 2094 mUpdateRequest = UPDATE_NONE; 2095 listener = mListener; 2096 } 2097 2098 if (listener != null) { 2099 try { 2100 listener.onCallHeld(ImsCall.this); 2101 } catch (Throwable t) { 2102 loge("callSessionHeld :: ", t); 2103 } 2104 } 2105 } 2106 2107 @Override 2108 public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2109 if (isTransientConferenceSession(session)) { 2110 // We should not get this callback for a transient session. 2111 log("callSessionHoldFailed :: not supported for transient conference session=" + 2112 session); 2113 return; 2114 } 2115 2116 if (VDBG) { 2117 log("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo); 2118 } 2119 2120 boolean isHoldForMerge = false; 2121 ImsCall.Listener listener; 2122 2123 synchronized(ImsCall.this) { 2124 if (mUpdateRequest == UPDATE_HOLD_MERGE) { 2125 isHoldForMerge = true; 2126 } 2127 2128 mUpdateRequest = UPDATE_NONE; 2129 listener = mListener; 2130 } 2131 2132 if (listener != null) { 2133 try { 2134 listener.onCallHoldFailed(ImsCall.this, reasonInfo); 2135 } catch (Throwable t) { 2136 loge("callSessionHoldFailed :: ", t); 2137 } 2138 } 2139 } 2140 2141 @Override 2142 public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) { 2143 if (isTransientConferenceSession(session)) { 2144 // We should not get this callback for a transient session. 2145 log("callSessionHoldReceived :: not supported for transient conference session=" + 2146 session); 2147 return; 2148 } 2149 2150 if (VDBG) { 2151 log("callSessionHoldReceived :: session=" + session + "profile=" + profile); 2152 } 2153 2154 ImsCall.Listener listener; 2155 2156 synchronized(ImsCall.this) { 2157 listener = mListener; 2158 mCallProfile = profile; 2159 } 2160 2161 if (listener != null) { 2162 try { 2163 listener.onCallHoldReceived(ImsCall.this); 2164 } catch (Throwable t) { 2165 loge("callSessionHoldReceived :: ", t); 2166 } 2167 } 2168 } 2169 2170 @Override 2171 public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) { 2172 if (isTransientConferenceSession(session)) { 2173 log("callSessionResumed :: not supported for transient conference session=" + 2174 session); 2175 return; 2176 } 2177 2178 if (VDBG) { 2179 log("callSessionResumed :: session=" + session + "profile=" + profile); 2180 } 2181 2182 // If this call was pending a merge, it is not anymore. This is the case when we 2183 // are merging in a new call into an existing conference. 2184 setCallSessionMergePending(false); 2185 2186 // Check if there is an ongoing conference merge which has completed. If there is 2187 // we can process the merge completion now. 2188 tryProcessConferenceResult(); 2189 2190 // TOOD: When we are merging a new call into an existing conference we are waiting 2191 // for 2 triggers to let us know that the conference has been established, the first 2192 // is a termination for the new calls (since it is added to the conference) the second 2193 // would be a resume on the existing conference. If the resume comes first, then 2194 // we will make the onCallResumed() callback and its unclear how this will behave if 2195 // the termination has not come yet. 2196 2197 ImsCall.Listener listener; 2198 synchronized(ImsCall.this) { 2199 listener = mListener; 2200 mCallProfile = profile; 2201 mUpdateRequest = UPDATE_NONE; 2202 mHold = false; 2203 } 2204 2205 if (listener != null) { 2206 try { 2207 listener.onCallResumed(ImsCall.this); 2208 } catch (Throwable t) { 2209 loge("callSessionResumed :: ", t); 2210 } 2211 } 2212 } 2213 2214 @Override 2215 public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2216 if (isTransientConferenceSession(session)) { 2217 log("callSessionResumeFailed :: not supported for transient conference session=" + 2218 session); 2219 return; 2220 } 2221 2222 if (VDBG) { 2223 log("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo); 2224 } 2225 2226 ImsCall.Listener listener; 2227 2228 synchronized(ImsCall.this) { 2229 listener = mListener; 2230 mUpdateRequest = UPDATE_NONE; 2231 } 2232 2233 if (listener != null) { 2234 try { 2235 listener.onCallResumeFailed(ImsCall.this, reasonInfo); 2236 } catch (Throwable t) { 2237 loge("callSessionResumeFailed :: ", t); 2238 } 2239 } 2240 } 2241 2242 @Override 2243 public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) { 2244 if (isTransientConferenceSession(session)) { 2245 log("callSessionResumeReceived :: not supported for transient conference session=" + 2246 session); 2247 return; 2248 } 2249 2250 if (VDBG) { 2251 log("callSessionResumeReceived :: session=" + session + "profile=" + profile); 2252 } 2253 2254 ImsCall.Listener listener; 2255 2256 synchronized(ImsCall.this) { 2257 listener = mListener; 2258 mCallProfile = profile; 2259 } 2260 2261 if (listener != null) { 2262 try { 2263 listener.onCallResumeReceived(ImsCall.this); 2264 } catch (Throwable t) { 2265 loge("callSessionResumeReceived :: ", t); 2266 } 2267 } 2268 } 2269 2270 @Override 2271 public void callSessionMergeStarted(ImsCallSession session, 2272 ImsCallSession newSession, ImsCallProfile profile) { 2273 if (VDBG) { 2274 log("callSessionMergeStarted :: session=" + session + " newSession=" + newSession + 2275 ", profile=" + profile); 2276 } 2277 2278 if (!isCallSessionMergePending()) { 2279 // Odd, we are not in the midst of merging anything. 2280 log("callSessionMergeStarted :: no merge in progress."); 2281 return; 2282 } 2283 2284 // There are 2 ways that we can go here. If the session that supplied the params 2285 // is not null, then it is the new session that represents the new conference 2286 // if the merge succeeds. If it is null, the merge is happening on our current 2287 // ImsCallSession. 2288 if (session == null) { 2289 // Everything is already set up and we just need to make sure 2290 // that we properly respond to all the future callbacks about 2291 // this merge. 2292 if (DBG) { 2293 log("callSessionMergeStarted :: merging into existing ImsCallSession"); 2294 } 2295 return; 2296 } 2297 2298 if (DBG) { 2299 log("callSessionMergeStarted :: setting our transient ImsCallSession"); 2300 } 2301 2302 // If we are here, this means that we are creating a new conference and 2303 // we need to do some extra work around managing a new ImsCallSession that 2304 // could represent our new ImsCallSession if the merge succeeds. 2305 synchronized(ImsCall.this) { 2306 // Keep track of this session for future callbacks to indicate success 2307 // or failure of this merge. 2308 mTransientConferenceSession = newSession; 2309 mTransientConferenceSession.setListener(createCallSessionListener()); 2310 } 2311 2312 return; 2313 } 2314 2315 @Override 2316 public void callSessionMergeComplete(ImsCallSession session) { 2317 if (VDBG) { 2318 log("callSessionMergeComplete :: session=" + session); 2319 } 2320 2321 setCallSessionMergePending(false); 2322 2323 // Check if there is an ongoing conference merge which has completed. If there is 2324 // we can process the merge completion now. 2325 tryProcessConferenceResult(); 2326 } 2327 2328 @Override 2329 public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2330 if (VDBG) { 2331 log("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo); 2332 } 2333 2334 // Its possible that there could be threading issues with the other thread handling 2335 // the other call. This could affect our state. 2336 synchronized (ImsCall.this) { 2337 if (!isCallSessionMergePending()) { 2338 // Odd, we are not in the midst of merging anything. 2339 log("callSessionMergeFailed :: no merge in progress."); 2340 return; 2341 } 2342 // Let's tell our parent ImsCall that the merge has failed and we need to clean 2343 // up any temporary, transient state. Note this only gets called for an initial 2344 // conference. If a merge into an existing conference fails, the two sessions will 2345 // just go back to their original state (ACTIVE or HELD). 2346 if (isMergeHost()) { 2347 processMergeFailed(reasonInfo); 2348 } else if (mMergeHost != null) { 2349 mMergeHost.processMergeFailed(reasonInfo); 2350 } else { 2351 loge("callSessionMergeFailed :: No merge host for this conference!"); 2352 } 2353 } 2354 } 2355 2356 @Override 2357 public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) { 2358 if (isTransientConferenceSession(session)) { 2359 log("callSessionUpdated :: not supported for transient conference session=" + 2360 session); 2361 return; 2362 } 2363 2364 if (VDBG) { 2365 log("callSessionUpdated :: session=" + session + " profile=" + profile); 2366 } 2367 2368 ImsCall.Listener listener; 2369 2370 synchronized(ImsCall.this) { 2371 listener = mListener; 2372 mCallProfile = profile; 2373 mUpdateRequest = UPDATE_NONE; 2374 } 2375 2376 if (listener != null) { 2377 try { 2378 listener.onCallUpdated(ImsCall.this); 2379 } catch (Throwable t) { 2380 loge("callSessionUpdated :: ", t); 2381 } 2382 } 2383 } 2384 2385 @Override 2386 public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2387 if (isTransientConferenceSession(session)) { 2388 log("callSessionUpdateFailed :: not supported for transient conference session=" + 2389 session); 2390 return; 2391 } 2392 2393 if (VDBG) { 2394 log("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo); 2395 } 2396 2397 ImsCall.Listener listener; 2398 2399 synchronized(ImsCall.this) { 2400 listener = mListener; 2401 mUpdateRequest = UPDATE_NONE; 2402 } 2403 2404 if (listener != null) { 2405 try { 2406 listener.onCallUpdateFailed(ImsCall.this, reasonInfo); 2407 } catch (Throwable t) { 2408 loge("callSessionUpdateFailed :: ", t); 2409 } 2410 } 2411 } 2412 2413 @Override 2414 public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) { 2415 if (isTransientConferenceSession(session)) { 2416 log("callSessionUpdateReceived :: not supported for transient conference " + 2417 "session=" + session); 2418 return; 2419 } 2420 2421 if (VDBG) { 2422 log("callSessionUpdateReceived :: session=" + session + " profile=" + profile); 2423 } 2424 2425 ImsCall.Listener listener; 2426 2427 synchronized(ImsCall.this) { 2428 listener = mListener; 2429 mProposedCallProfile = profile; 2430 mUpdateRequest = UPDATE_UNSPECIFIED; 2431 } 2432 2433 if (listener != null) { 2434 try { 2435 listener.onCallUpdateReceived(ImsCall.this); 2436 } catch (Throwable t) { 2437 loge("callSessionUpdateReceived :: ", t); 2438 } 2439 } 2440 } 2441 2442 @Override 2443 public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession, 2444 ImsCallProfile profile) { 2445 if (isTransientConferenceSession(session)) { 2446 log("callSessionConferenceExtended :: not supported for transient conference " + 2447 "session=" + session); 2448 return; 2449 } 2450 2451 if (VDBG) { 2452 log("callSessionConferenceExtended :: session=" + session + " newSession=" + 2453 newSession + ", profile=" + profile); 2454 } 2455 2456 ImsCall newCall = createNewCall(newSession, profile); 2457 2458 if (newCall == null) { 2459 callSessionConferenceExtendFailed(session, new ImsReasonInfo()); 2460 return; 2461 } 2462 2463 ImsCall.Listener listener; 2464 2465 synchronized(ImsCall.this) { 2466 listener = mListener; 2467 mUpdateRequest = UPDATE_NONE; 2468 } 2469 2470 if (listener != null) { 2471 try { 2472 listener.onCallConferenceExtended(ImsCall.this, newCall); 2473 } catch (Throwable t) { 2474 loge("callSessionConferenceExtended :: ", t); 2475 } 2476 } 2477 } 2478 2479 @Override 2480 public void callSessionConferenceExtendFailed(ImsCallSession session, 2481 ImsReasonInfo reasonInfo) { 2482 if (isTransientConferenceSession(session)) { 2483 log("callSessionConferenceExtendFailed :: not supported for transient " + 2484 "conference session=" + session); 2485 return; 2486 } 2487 2488 if (DBG) { 2489 log("callSessionConferenceExtendFailed :: imsCall=" + ImsCall.this + 2490 ", reasonInfo=" + reasonInfo); 2491 } 2492 2493 ImsCall.Listener listener; 2494 2495 synchronized(ImsCall.this) { 2496 listener = mListener; 2497 mUpdateRequest = UPDATE_NONE; 2498 } 2499 2500 if (listener != null) { 2501 try { 2502 listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo); 2503 } catch (Throwable t) { 2504 loge("callSessionConferenceExtendFailed :: ", t); 2505 } 2506 } 2507 } 2508 2509 @Override 2510 public void callSessionConferenceExtendReceived(ImsCallSession session, 2511 ImsCallSession newSession, ImsCallProfile profile) { 2512 if (isTransientConferenceSession(session)) { 2513 log("callSessionConferenceExtendReceived :: not supported for transient " + 2514 "conference session" + session); 2515 return; 2516 } 2517 2518 if (VDBG) { 2519 log("callSessionConferenceExtendReceived :: newSession=" + newSession + 2520 ", profile=" + profile); 2521 } 2522 2523 ImsCall newCall = createNewCall(newSession, profile); 2524 2525 if (newCall == null) { 2526 // Should all the calls be terminated...??? 2527 return; 2528 } 2529 2530 ImsCall.Listener listener; 2531 2532 synchronized(ImsCall.this) { 2533 listener = mListener; 2534 } 2535 2536 if (listener != null) { 2537 try { 2538 listener.onCallConferenceExtendReceived(ImsCall.this, newCall); 2539 } catch (Throwable t) { 2540 loge("callSessionConferenceExtendReceived :: ", t); 2541 } 2542 } 2543 } 2544 2545 @Override 2546 public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) { 2547 if (isTransientConferenceSession(session)) { 2548 log("callSessionInviteParticipantsRequestDelivered :: not supported for " + 2549 "conference session=" + session); 2550 return; 2551 } 2552 2553 if (VDBG) { 2554 log("callSessionInviteParticipantsRequestDelivered ::"); 2555 } 2556 2557 ImsCall.Listener listener; 2558 2559 synchronized(ImsCall.this) { 2560 listener = mListener; 2561 } 2562 2563 if (listener != null) { 2564 try { 2565 listener.onCallInviteParticipantsRequestDelivered(ImsCall.this); 2566 } catch (Throwable t) { 2567 loge("callSessionInviteParticipantsRequestDelivered :: ", t); 2568 } 2569 } 2570 } 2571 2572 @Override 2573 public void callSessionInviteParticipantsRequestFailed(ImsCallSession session, 2574 ImsReasonInfo reasonInfo) { 2575 if (isTransientConferenceSession(session)) { 2576 log("callSessionInviteParticipantsRequestFailed :: not supported for " + 2577 "conference session=" + session); 2578 return; 2579 } 2580 2581 if (VDBG) { 2582 log("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo); 2583 } 2584 2585 ImsCall.Listener listener; 2586 2587 synchronized(ImsCall.this) { 2588 listener = mListener; 2589 } 2590 2591 if (listener != null) { 2592 try { 2593 listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo); 2594 } catch (Throwable t) { 2595 loge("callSessionInviteParticipantsRequestFailed :: ", t); 2596 } 2597 } 2598 } 2599 2600 @Override 2601 public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) { 2602 if (isTransientConferenceSession(session)) { 2603 log("callSessionRemoveParticipantsRequestDelivered :: not supported for " + 2604 "conference session=" + session); 2605 return; 2606 } 2607 2608 if (VDBG) { 2609 log("callSessionRemoveParticipantsRequestDelivered ::"); 2610 } 2611 2612 ImsCall.Listener listener; 2613 2614 synchronized(ImsCall.this) { 2615 listener = mListener; 2616 } 2617 2618 if (listener != null) { 2619 try { 2620 listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this); 2621 } catch (Throwable t) { 2622 loge("callSessionRemoveParticipantsRequestDelivered :: ", t); 2623 } 2624 } 2625 } 2626 2627 @Override 2628 public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session, 2629 ImsReasonInfo reasonInfo) { 2630 if (isTransientConferenceSession(session)) { 2631 log("callSessionRemoveParticipantsRequestFailed :: not supported for " + 2632 "conference session=" + session); 2633 return; 2634 } 2635 2636 if (VDBG) { 2637 log("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo); 2638 } 2639 2640 ImsCall.Listener listener; 2641 2642 synchronized(ImsCall.this) { 2643 listener = mListener; 2644 } 2645 2646 if (listener != null) { 2647 try { 2648 listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo); 2649 } catch (Throwable t) { 2650 loge("callSessionRemoveParticipantsRequestFailed :: ", t); 2651 } 2652 } 2653 } 2654 2655 @Override 2656 public void callSessionConferenceStateUpdated(ImsCallSession session, 2657 ImsConferenceState state) { 2658 if (isTransientConferenceSession(session)) { 2659 log("callSessionConferenceStateUpdated :: not supported for transient " + 2660 "conference session=" + session); 2661 return; 2662 } 2663 2664 if (VDBG) { 2665 log("callSessionConferenceStateUpdated :: state=" + state); 2666 } 2667 2668 conferenceStateUpdated(state); 2669 } 2670 2671 @Override 2672 public void callSessionUssdMessageReceived(ImsCallSession session, int mode, 2673 String ussdMessage) { 2674 if (isTransientConferenceSession(session)) { 2675 log("callSessionUssdMessageReceived :: not supported for transient " + 2676 "conference session=" + session); 2677 return; 2678 } 2679 2680 if (VDBG) { 2681 log("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" + 2682 ussdMessage); 2683 } 2684 2685 ImsCall.Listener listener; 2686 2687 synchronized(ImsCall.this) { 2688 listener = mListener; 2689 } 2690 2691 if (listener != null) { 2692 try { 2693 listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage); 2694 } catch (Throwable t) { 2695 loge("callSessionUssdMessageReceived :: ", t); 2696 } 2697 } 2698 } 2699 2700 @Override 2701 public void callSessionTtyModeReceived(ImsCallSession session, int mode) { 2702 if (VDBG) { 2703 log("callSessionTtyModeReceived :: mode=" + mode); 2704 } 2705 2706 ImsCall.Listener listener; 2707 2708 synchronized(ImsCall.this) { 2709 listener = mListener; 2710 } 2711 2712 if (listener != null) { 2713 try { 2714 listener.onCallSessionTtyModeReceived(ImsCall.this, mode); 2715 } catch (Throwable t) { 2716 loge("callSessionTtyModeReceived :: ", t); 2717 } 2718 } 2719 } 2720 2721 public void callSessionHandover(ImsCallSession session, int srcAccessTech, 2722 int targetAccessTech, ImsReasonInfo reasonInfo) { 2723 if (DBG) { 2724 log("callSessionHandover :: session=" + session + ", srcAccessTech=" + 2725 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + 2726 reasonInfo); 2727 } 2728 2729 ImsCall.Listener listener; 2730 2731 synchronized(ImsCall.this) { 2732 listener = mListener; 2733 } 2734 2735 if (listener != null) { 2736 try { 2737 listener.onCallHandover(ImsCall.this, srcAccessTech, targetAccessTech, 2738 reasonInfo); 2739 } catch (Throwable t) { 2740 loge("callSessionHandover :: ", t); 2741 } 2742 } 2743 } 2744 2745 @Override 2746 public void callSessionHandoverFailed(ImsCallSession session, int srcAccessTech, 2747 int targetAccessTech, ImsReasonInfo reasonInfo) { 2748 if (DBG) { 2749 log("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" + 2750 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + 2751 reasonInfo); 2752 } 2753 2754 ImsCall.Listener listener; 2755 2756 synchronized(ImsCall.this) { 2757 listener = mListener; 2758 } 2759 2760 if (listener != null) { 2761 try { 2762 listener.onCallHandoverFailed(ImsCall.this, srcAccessTech, targetAccessTech, 2763 reasonInfo); 2764 } catch (Throwable t) { 2765 loge("callSessionHandoverFailed :: ", t); 2766 } 2767 } 2768 } 2769 } 2770 2771 /** 2772 * Report a new conference state to the current {@link ImsCall} and inform listeners of the 2773 * change. Marked as {@code VisibleForTesting} so that the 2774 * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference 2775 * event package into a regular ongoing IMS call. 2776 * 2777 * @param state The {@link ImsConferenceState}. 2778 */ 2779 @VisibleForTesting 2780 public void conferenceStateUpdated(ImsConferenceState state) { 2781 Listener listener; 2782 2783 synchronized(this) { 2784 notifyConferenceStateUpdated(state); 2785 listener = mListener; 2786 } 2787 2788 if (listener != null) { 2789 try { 2790 listener.onCallConferenceStateUpdated(this, state); 2791 } catch (Throwable t) { 2792 loge("callSessionConferenceStateUpdated :: ", t); 2793 } 2794 } 2795 } 2796 2797 /** 2798 * Provides a human-readable string representation of an update request. 2799 * 2800 * @param updateRequest The update request. 2801 * @return The string representation. 2802 */ 2803 private String updateRequestToString(int updateRequest) { 2804 switch (updateRequest) { 2805 case UPDATE_NONE: 2806 return "NONE"; 2807 case UPDATE_HOLD: 2808 return "HOLD"; 2809 case UPDATE_HOLD_MERGE: 2810 return "HOLD_MERGE"; 2811 case UPDATE_RESUME: 2812 return "RESUME"; 2813 case UPDATE_MERGE: 2814 return "MERGE"; 2815 case UPDATE_EXTEND_TO_CONFERENCE: 2816 return "EXTEND_TO_CONFERENCE"; 2817 case UPDATE_UNSPECIFIED: 2818 return "UNSPECIFIED"; 2819 default: 2820 return "UNKNOWN"; 2821 } 2822 } 2823 2824 /** 2825 * Clears the merge peer for this call, ensuring that the peer's connection to this call is also 2826 * severed at the same time. 2827 */ 2828 private void clearMergeInfo() { 2829 if (VDBG) { 2830 log("clearMergeInfo :: clearing all merge info"); 2831 } 2832 2833 // First clear out the merge partner then clear ourselves out. 2834 if (mMergeHost != null) { 2835 mMergeHost.mMergePeer = null; 2836 mMergeHost.mUpdateRequest = UPDATE_NONE; 2837 mMergeHost.mCallSessionMergePending = false; 2838 } 2839 if (mMergePeer != null) { 2840 mMergePeer.mMergeHost = null; 2841 mMergePeer.mUpdateRequest = UPDATE_NONE; 2842 mMergePeer.mCallSessionMergePending = false; 2843 } 2844 mMergeHost = null; 2845 mMergePeer = null; 2846 mUpdateRequest = UPDATE_NONE; 2847 mCallSessionMergePending = false; 2848 } 2849 2850 /** 2851 * Sets the merge peer for the current call. The merge peer is the background call that will be 2852 * merged into this call. On the merge peer, sets the merge host to be this call. 2853 * 2854 * @param mergePeer The peer call to be merged into this one. 2855 */ 2856 private void setMergePeer(ImsCall mergePeer) { 2857 mMergePeer = mergePeer; 2858 mMergeHost = null; 2859 2860 mergePeer.mMergeHost = ImsCall.this; 2861 mergePeer.mMergePeer = null; 2862 } 2863 2864 /** 2865 * Sets the merge hody for the current call. The merge host is the foreground call this call 2866 * will be merged into. On the merge host, sets the merge peer to be this call. 2867 * 2868 * @param mergeHost The merge host this call will be merged into. 2869 */ 2870 public void setMergeHost(ImsCall mergeHost) { 2871 mMergeHost = mergeHost; 2872 mMergePeer = null; 2873 2874 mergeHost.mMergeHost = null; 2875 mergeHost.mMergePeer = ImsCall.this; 2876 } 2877 2878 /** 2879 * Determines if the current call is in the process of merging with another call or conference. 2880 * 2881 * @return {@code true} if in the process of merging. 2882 */ 2883 private boolean isMerging() { 2884 return mMergePeer != null || mMergeHost != null; 2885 } 2886 2887 /** 2888 * Determines if the current call is the host of the merge. 2889 * 2890 * @return {@code true} if the call is the merge host. 2891 */ 2892 private boolean isMergeHost() { 2893 return mMergePeer != null && mMergeHost == null; 2894 } 2895 2896 /** 2897 * Determines if the current call is the peer of the merge. 2898 * 2899 * @return {@code true} if the call is the merge peer. 2900 */ 2901 private boolean isMergePeer() { 2902 return mMergePeer == null && mMergeHost != null; 2903 } 2904 2905 /** 2906 * Determines if the call session is pending merge into a conference or not. 2907 * 2908 * @return {@code true} if a merge into a conference is pending, {@code false} otherwise. 2909 */ 2910 private boolean isCallSessionMergePending() { 2911 return mCallSessionMergePending; 2912 } 2913 2914 /** 2915 * Sets flag indicating whether the call session is pending merge into a conference or not. 2916 * 2917 * @param callSessionMergePending {@code true} if a merge into the conference is pending, 2918 * {@code false} otherwise. 2919 */ 2920 private void setCallSessionMergePending(boolean callSessionMergePending) { 2921 mCallSessionMergePending = callSessionMergePending; 2922 } 2923 2924 /** 2925 * Determines if there is a conference merge in process. If there is a merge in process, 2926 * determines if both the merge host and peer sessions have completed the merge process. This 2927 * means that we have received terminate or hold signals for the sessions, indicating that they 2928 * are no longer in the process of being merged into the conference. 2929 * <p> 2930 * The sessions are considered to have merged if: both calls still have merge peer/host 2931 * relationships configured, both sessions are not waiting to be merged into the conference, 2932 * and the transient conference session is alive in the case of an initial conference. 2933 * 2934 * @return {@code true} where the host and peer sessions have finished merging into the 2935 * conference, {@code false} if the merge has not yet completed, and {@code false} if there 2936 * is no conference merge in progress. 2937 */ 2938 private boolean shouldProcessConferenceResult() { 2939 boolean areMergeTriggersDone = false; 2940 2941 synchronized (ImsCall.this) { 2942 // if there is a merge going on, then the merge host/peer relationships should have been 2943 // set up. This works for both the initial conference or merging a call into an 2944 // existing conference. 2945 if (!isMergeHost() && !isMergePeer()) { 2946 if (VDBG) { 2947 log("shouldProcessConferenceResult :: no merge in progress"); 2948 } 2949 return false; 2950 } 2951 2952 // There is a merge in progress, so check the sessions to ensure: 2953 // 1. Both calls have completed being merged (or failing to merge) into the conference. 2954 // 2. The transient conference session is alive. 2955 if (isMergeHost()) { 2956 if (VDBG) { 2957 log("shouldProcessConferenceResult :: We are a merge host=" + this); 2958 log("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer); 2959 } 2960 areMergeTriggersDone = !isCallSessionMergePending() && 2961 !mMergePeer.isCallSessionMergePending(); 2962 if (!isMultiparty()) { 2963 // Only check the transient session when there is no existing conference 2964 areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession); 2965 } 2966 } else if (isMergePeer()) { 2967 if (VDBG) { 2968 log("shouldProcessConferenceResult :: We are a merge peer=" + this); 2969 log("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost); 2970 } 2971 areMergeTriggersDone = !isCallSessionMergePending() && 2972 !mMergeHost.isCallSessionMergePending(); 2973 if (!mMergeHost.isMultiparty()) { 2974 // Only check the transient session when there is no existing conference 2975 areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession); 2976 } else { 2977 // This else block is a special case for Verizon to handle these steps 2978 // 1. Establish a conference call. 2979 // 2. Add a new call (conference in in BG) 2980 // 3. Swap (conference active on FG) 2981 // 4. Merge 2982 // What happens here is that the BG call gets a terminated callback 2983 // because it was added to the conference. I've seen where 2984 // the FG gets no callback at all because its already active. 2985 // So if we continue to wait for it to set its isCallSessionMerging 2986 // flag to false...we'll be waiting forever. 2987 areMergeTriggersDone = !isCallSessionMergePending(); 2988 } 2989 } else { 2990 // Realistically this shouldn't happen, but best to be safe. 2991 loge("shouldProcessConferenceResult : merge in progress but call is neither" + 2992 "host nor peer."); 2993 } 2994 if (VDBG) { 2995 log("shouldProcessConferenceResult :: returning:" + 2996 (areMergeTriggersDone ? "true" : "false")); 2997 } 2998 } 2999 return areMergeTriggersDone; 3000 } 3001 3002 /** 3003 * Provides a string representation of the {@link ImsCall}. Primarily intended for use in log 3004 * statements. 3005 * 3006 * @return String representation of call. 3007 */ 3008 @Override 3009 public String toString() { 3010 StringBuilder sb = new StringBuilder(); 3011 sb.append("[ImsCall objId:"); 3012 sb.append(System.identityHashCode(this)); 3013 sb.append(" onHold:"); 3014 sb.append(isOnHold() ? "Y" : "N"); 3015 sb.append(" mute:"); 3016 sb.append(isMuted() ? "Y" : "N"); 3017 sb.append(" updateRequest:"); 3018 sb.append(updateRequestToString(mUpdateRequest)); 3019 sb.append(" merging:"); 3020 sb.append(isMerging() ? "Y" : "N"); 3021 if (isMerging()) { 3022 if (isMergePeer()) { 3023 sb.append("P"); 3024 } else { 3025 sb.append("H"); 3026 } 3027 } 3028 sb.append(" merge action pending:"); 3029 sb.append(isCallSessionMergePending() ? "Y" : "N"); 3030 sb.append(" merged:"); 3031 sb.append(isMerged() ? "Y" : "N"); 3032 sb.append(" multiParty:"); 3033 sb.append(isMultiparty() ? "Y" : "N"); 3034 sb.append(" buried term:"); 3035 sb.append(mSessionEndDuringMerge ? "Y" : "N"); 3036 sb.append(" session:"); 3037 sb.append(mSession); 3038 sb.append(" transientSession:"); 3039 sb.append(mTransientConferenceSession); 3040 sb.append("]"); 3041 return sb.toString(); 3042 } 3043} 3044