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