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