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