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