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