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