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