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