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