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