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