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