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