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