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