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