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