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