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