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