ImsCall.java revision 725ad373383798c1516348475b1f6304484e031e
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.HashMap; 23import java.util.Iterator; 24import java.util.Map.Entry; 25import java.util.Set; 26 27import android.content.Context; 28import android.os.Bundle; 29import android.os.Message; 30import android.telephony.Rlog; 31 32import com.android.ims.internal.CallGroup; 33import com.android.ims.internal.CallGroupManager; 34import com.android.ims.internal.ICall; 35import com.android.ims.internal.ImsCallSession; 36import com.android.ims.internal.ImsStreamMediaSession; 37 38/** 39 * Handles an IMS voice / video call over LTE. You can instantiate this class with 40 * {@link ImsManager}. 41 * 42 * @hide 43 */ 44public 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 * Determines if the call is a multiparty call. 648 * 649 * @return {@code True} if the call is a multiparty call. 650 */ 651 public boolean isMultiparty() { 652 return mSession.isMultiparty(); 653 } 654 655 /** 656 * Sets the listener to listen to the IMS call events. 657 * The method calls {@link #setListener setListener(listener, false)}. 658 * 659 * @param listener to listen to the IMS call events of this object; null to remove listener 660 * @see #setListener(Listener, boolean) 661 */ 662 public void setListener(ImsCall.Listener listener) { 663 setListener(listener, false); 664 } 665 666 /** 667 * Sets the listener to listen to the IMS call events. 668 * A {@link ImsCall} can only hold one listener at a time. Subsequent calls 669 * to this method override the previous listener. 670 * 671 * @param listener to listen to the IMS call events of this object; null to remove listener 672 * @param callbackImmediately set to true if the caller wants to be called 673 * back immediately on the current state 674 */ 675 public void setListener(ImsCall.Listener listener, boolean callbackImmediately) { 676 boolean inCall; 677 boolean onHold; 678 int state; 679 ImsReasonInfo lastReasonInfo; 680 681 synchronized(mLockObj) { 682 mListener = listener; 683 684 if ((listener == null) || !callbackImmediately) { 685 return; 686 } 687 688 inCall = mInCall; 689 onHold = mHold; 690 state = getState(); 691 lastReasonInfo = mLastReasonInfo; 692 } 693 694 try { 695 if (lastReasonInfo != null) { 696 listener.onCallError(this, lastReasonInfo); 697 } else if (inCall) { 698 if (onHold) { 699 listener.onCallHeld(this); 700 } else { 701 listener.onCallStarted(this); 702 } 703 } else { 704 switch (state) { 705 case ImsCallSession.State.ESTABLISHING: 706 listener.onCallProgressing(this); 707 break; 708 case ImsCallSession.State.TERMINATED: 709 listener.onCallTerminated(this, lastReasonInfo); 710 break; 711 default: 712 // Ignore it. There is no action in the other state. 713 break; 714 } 715 } 716 } catch (Throwable t) { 717 loge("setListener()", t); 718 } 719 } 720 721 /** 722 * Mutes or unmutes the mic for the active call. 723 * 724 * @param muted true if the call is muted, false otherwise 725 */ 726 public void setMute(boolean muted) throws ImsException { 727 synchronized(mLockObj) { 728 if (mMute != muted) { 729 mMute = muted; 730 731 try { 732 mSession.setMute(muted); 733 } catch (Throwable t) { 734 loge("setMute :: ", t); 735 throwImsException(t, 0); 736 } 737 } 738 } 739 } 740 741 /** 742 * Attaches an incoming call to this call object. 743 * 744 * @param session the session that receives the incoming call 745 * @throws ImsException if the IMS service fails to attach this object to the session 746 */ 747 public void attachSession(ImsCallSession session) throws ImsException { 748 if (DBG) { 749 log("attachSession :: session=" + session); 750 } 751 752 synchronized(mLockObj) { 753 mSession = session; 754 755 try { 756 mSession.setListener(createCallSessionListener()); 757 } catch (Throwable t) { 758 loge("attachSession :: ", t); 759 throwImsException(t, 0); 760 } 761 } 762 } 763 764 /** 765 * Initiates an IMS call with the call profile which is provided 766 * when creating a {@link ImsCall}. 767 * 768 * @param session the {@link ImsCallSession} for carrying out the call 769 * @param callee callee information to initiate an IMS call 770 * @throws ImsException if the IMS service fails to initiate the call 771 */ 772 public void start(ImsCallSession session, String callee) 773 throws ImsException { 774 if (DBG) { 775 log("start(1) :: session=" + session + ", callee=" + callee); 776 } 777 778 synchronized(mLockObj) { 779 mSession = session; 780 781 try { 782 session.setListener(createCallSessionListener()); 783 session.start(callee, mCallProfile); 784 } catch (Throwable t) { 785 loge("start(1) :: ", t); 786 throw new ImsException("start(1)", t, 0); 787 } 788 } 789 } 790 791 /** 792 * Initiates an IMS conferenca call with the call profile which is provided 793 * when creating a {@link ImsCall}. 794 * 795 * @param session the {@link ImsCallSession} for carrying out the call 796 * @param participants participant list to initiate an IMS conference call 797 * @throws ImsException if the IMS service fails to initiate the call 798 */ 799 public void start(ImsCallSession session, String[] participants) 800 throws ImsException { 801 if (DBG) { 802 log("start(n) :: session=" + session + ", callee=" + participants); 803 } 804 805 synchronized(mLockObj) { 806 mSession = session; 807 808 try { 809 session.setListener(createCallSessionListener()); 810 session.start(participants, mCallProfile); 811 } catch (Throwable t) { 812 loge("start(n) :: ", t); 813 throw new ImsException("start(n)", t, 0); 814 } 815 } 816 } 817 818 /** 819 * Accepts a call. 820 * 821 * @see Listener#onCallStarted 822 * 823 * @param callType The call type the user agreed to for accepting the call. 824 * @throws ImsException if the IMS service fails to accept the call 825 */ 826 public void accept(int callType) throws ImsException { 827 if (DBG) { 828 log("accept :: session=" + mSession); 829 } 830 831 accept(callType, new ImsStreamMediaProfile()); 832 } 833 834 /** 835 * Accepts a call. 836 * 837 * @param callType call type to be answered in {@link ImsCallProfile} 838 * @param profile a media profile to be answered (audio/audio & video, direction, ...) 839 * @see Listener#onCallStarted 840 * @throws ImsException if the IMS service fails to accept the call 841 */ 842 public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException { 843 if (DBG) { 844 log("accept :: session=" + mSession 845 + ", callType=" + callType + ", profile=" + profile); 846 } 847 848 synchronized(mLockObj) { 849 if (mSession == null) { 850 throw new ImsException("No call to answer", 851 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 852 } 853 854 try { 855 mSession.accept(callType, profile); 856 } catch (Throwable t) { 857 loge("accept :: ", t); 858 throw new ImsException("accept()", t, 0); 859 } 860 861 if (mInCall && (mProposedCallProfile != null)) { 862 if (DBG) { 863 log("accept :: call profile will be updated"); 864 } 865 866 mCallProfile = mProposedCallProfile; 867 mProposedCallProfile = null; 868 } 869 870 // Other call update received 871 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) { 872 mUpdateRequest = UPDATE_NONE; 873 } 874 } 875 } 876 877 /** 878 * Rejects a call. 879 * 880 * @param reason reason code to reject an incoming call 881 * @see Listener#onCallStartFailed 882 * @throws ImsException if the IMS service fails to accept the call 883 */ 884 public void reject(int reason) throws ImsException { 885 if (DBG) { 886 log("reject :: session=" + mSession + ", reason=" + reason); 887 } 888 889 synchronized(mLockObj) { 890 if (mSession != null) { 891 mSession.reject(reason); 892 } 893 894 if (mInCall && (mProposedCallProfile != null)) { 895 if (DBG) { 896 log("reject :: call profile is not updated; destroy it..."); 897 } 898 899 mProposedCallProfile = null; 900 } 901 902 // Other call update received 903 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) { 904 mUpdateRequest = UPDATE_NONE; 905 } 906 } 907 } 908 909 /** 910 * Terminates an IMS call. 911 * 912 * @param reason reason code to terminate a call 913 * @throws ImsException if the IMS service fails to terminate the call 914 */ 915 public void terminate(int reason) throws ImsException { 916 if (DBG) { 917 log("terminate :: session=" + mSession + ", reason=" + reason); 918 } 919 920 synchronized(mLockObj) { 921 mHold = false; 922 mInCall = false; 923 CallGroup callGroup = getCallGroup(); 924 925 if (mSession != null) { 926 if (callGroup != null && !callGroup.isOwner(ImsCall.this)) { 927 log("terminate owner of the call group"); 928 ImsCall owner = (ImsCall) callGroup.getOwner(); 929 if (owner != null) { 930 owner.terminate(reason); 931 return; 932 } 933 } 934 mSession.terminate(reason); 935 } 936 } 937 } 938 939 940 /** 941 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called. 942 * 943 * @see Listener#onCallHeld, Listener#onCallHoldFailed 944 * @throws ImsException if the IMS service fails to hold the call 945 */ 946 public void hold() throws ImsException { 947 if (DBG) { 948 log("hold :: session=" + mSession); 949 } 950 951 // perform operation on owner before doing any local checks: local 952 // call may not have its status updated 953 synchronized (mLockObj) { 954 CallGroup callGroup = mCallGroup; 955 if (callGroup != null && !callGroup.isOwner(ImsCall.this)) { 956 log("hold owner of the call group"); 957 ImsCall owner = (ImsCall) callGroup.getOwner(); 958 if (owner != null) { 959 owner.hold(); 960 return; 961 } 962 } 963 } 964 965 if (isOnHold()) { 966 if (DBG) { 967 log("hold :: call is already on hold"); 968 } 969 return; 970 } 971 972 synchronized(mLockObj) { 973 if (mUpdateRequest != UPDATE_NONE) { 974 loge("hold :: update is in progress; request=" + mUpdateRequest); 975 throw new ImsException("Call update is in progress", 976 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 977 } 978 979 if (mSession == null) { 980 loge("hold :: "); 981 throw new ImsException("No call session", 982 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 983 } 984 985 mSession.hold(createHoldMediaProfile()); 986 // FIXME: update the state on the callback? 987 mHold = true; 988 mUpdateRequest = UPDATE_HOLD; 989 } 990 } 991 992 /** 993 * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called. 994 * 995 * @see Listener#onCallResumed, Listener#onCallResumeFailed 996 * @throws ImsException if the IMS service fails to resume the call 997 */ 998 public void resume() throws ImsException { 999 if (DBG) { 1000 log("resume :: session=" + mSession); 1001 } 1002 1003 // perform operation on owner before doing any local checks: local 1004 // call may not have its status updated 1005 synchronized (mLockObj) { 1006 CallGroup callGroup = mCallGroup; 1007 if (callGroup != null && !callGroup.isOwner(ImsCall.this)) { 1008 log("resume owner of the call group"); 1009 ImsCall owner = (ImsCall) callGroup.getOwner(); 1010 if (owner != null) { 1011 owner.resume(); 1012 return; 1013 } 1014 } 1015 } 1016 1017 if (!isOnHold()) { 1018 if (DBG) { 1019 log("resume :: call is in conversation"); 1020 } 1021 return; 1022 } 1023 1024 synchronized(mLockObj) { 1025 if (mUpdateRequest != UPDATE_NONE) { 1026 loge("resume :: update is in progress; request=" + mUpdateRequest); 1027 throw new ImsException("Call update is in progress", 1028 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1029 } 1030 1031 if (mSession == null) { 1032 loge("resume :: "); 1033 throw new ImsException("No call session", 1034 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1035 } 1036 1037 mSession.resume(createResumeMediaProfile()); 1038 // FIXME: update the state on the callback? 1039 mHold = false; 1040 mUpdateRequest = UPDATE_RESUME; 1041 } 1042 } 1043 1044 /** 1045 * Merges the active & hold call. 1046 * 1047 * @see Listener#onCallMerged, Listener#onCallMergeFailed 1048 * @throws ImsException if the IMS service fails to merge the call 1049 */ 1050 public void merge() throws ImsException { 1051 if (DBG) { 1052 log("merge :: session=" + mSession); 1053 } 1054 1055 synchronized(mLockObj) { 1056 if (mUpdateRequest != UPDATE_NONE) { 1057 loge("merge :: update is in progress; request=" + mUpdateRequest); 1058 throw new ImsException("Call update is in progress", 1059 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1060 } 1061 1062 if (mSession == null) { 1063 loge("merge :: "); 1064 throw new ImsException("No call session", 1065 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1066 } 1067 1068 // if skipHoldBeforeMerge = true, IMS service implementation will 1069 // merge without explicitly holding the call. 1070 if (mHold || (mContext.getResources().getBoolean( 1071 com.android.internal.R.bool.skipHoldBeforeMerge))) { 1072 mSession.merge(); 1073 mUpdateRequest = UPDATE_MERGE; 1074 } else { 1075 mSession.hold(createHoldMediaProfile()); 1076 // FIXME: ? 1077 mHold = true; 1078 mUpdateRequest = UPDATE_HOLD_MERGE; 1079 } 1080 } 1081 } 1082 1083 /** 1084 * Merges the active & hold call. 1085 * 1086 * @param bgCall the background (holding) call 1087 * @see Listener#onCallMerged, Listener#onCallMergeFailed 1088 * @throws ImsException if the IMS service fails to merge the call 1089 */ 1090 public void merge(ImsCall bgCall) throws ImsException { 1091 if (DBG) { 1092 log("merge(1) :: session=" + mSession); 1093 } 1094 1095 if (bgCall == null) { 1096 throw new ImsException("No background call", 1097 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 1098 } 1099 1100 synchronized(mLockObj) { 1101 createCallGroup(bgCall); 1102 } 1103 1104 merge(); 1105 } 1106 1107 /** 1108 * Updates the current call's properties (ex. call mode change: video upgrade / downgrade). 1109 */ 1110 public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException { 1111 if (DBG) { 1112 log("update :: session=" + mSession); 1113 } 1114 1115 if (isOnHold()) { 1116 if (DBG) { 1117 log("update :: call is on hold"); 1118 } 1119 throw new ImsException("Not in a call to update call", 1120 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1121 } 1122 1123 synchronized(mLockObj) { 1124 if (mUpdateRequest != UPDATE_NONE) { 1125 if (DBG) { 1126 log("update :: update is in progress; request=" + mUpdateRequest); 1127 } 1128 throw new ImsException("Call update is in progress", 1129 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1130 } 1131 1132 if (mSession == null) { 1133 loge("update :: "); 1134 throw new ImsException("No call session", 1135 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1136 } 1137 1138 mSession.update(callType, mediaProfile); 1139 mUpdateRequest = UPDATE_UNSPECIFIED; 1140 } 1141 } 1142 1143 /** 1144 * Extends this call (1-to-1 call) to the conference call 1145 * inviting the specified participants to. 1146 * 1147 */ 1148 public void extendToConference(String[] participants) throws ImsException { 1149 if (DBG) { 1150 log("extendToConference :: session=" + mSession); 1151 } 1152 1153 if (isOnHold()) { 1154 if (DBG) { 1155 log("extendToConference :: call is on hold"); 1156 } 1157 throw new ImsException("Not in a call to extend a call to conference", 1158 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1159 } 1160 1161 synchronized(mLockObj) { 1162 if (mUpdateRequest != UPDATE_NONE) { 1163 if (DBG) { 1164 log("extendToConference :: update is in progress; request=" + mUpdateRequest); 1165 } 1166 throw new ImsException("Call update is in progress", 1167 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1168 } 1169 1170 if (mSession == null) { 1171 loge("extendToConference :: "); 1172 throw new ImsException("No call session", 1173 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1174 } 1175 1176 mSession.extendToConference(participants); 1177 mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE; 1178 } 1179 } 1180 1181 /** 1182 * Requests the conference server to invite an additional participants to the conference. 1183 * 1184 */ 1185 public void inviteParticipants(String[] participants) throws ImsException { 1186 if (DBG) { 1187 log("inviteParticipants :: session=" + mSession); 1188 } 1189 1190 synchronized(mLockObj) { 1191 if (mSession == null) { 1192 loge("inviteParticipants :: "); 1193 throw new ImsException("No call session", 1194 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1195 } 1196 1197 mSession.inviteParticipants(participants); 1198 } 1199 } 1200 1201 /** 1202 * Requests the conference server to remove the specified participants from the conference. 1203 * 1204 */ 1205 public void removeParticipants(String[] participants) throws ImsException { 1206 if (DBG) { 1207 log("removeParticipants :: session=" + mSession); 1208 } 1209 1210 synchronized(mLockObj) { 1211 if (mSession == null) { 1212 loge("removeParticipants :: "); 1213 throw new ImsException("No call session", 1214 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1215 } 1216 1217 mSession.removeParticipants(participants); 1218 } 1219 } 1220 1221 1222 /** 1223 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, 1224 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, 1225 * and event flash to 16. Currently, event flash is not supported. 1226 * 1227 * @param char that represents the DTMF digit to send. 1228 */ 1229 public void sendDtmf(char c) { 1230 sendDtmf(c, null); 1231 } 1232 1233 /** 1234 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, 1235 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, 1236 * and event flash to 16. Currently, event flash is not supported. 1237 * 1238 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. 1239 * @param result the result message to send when done. 1240 */ 1241 public void sendDtmf(char c, Message result) { 1242 if (DBG) { 1243 log("sendDtmf :: session=" + mSession + ", code=" + c); 1244 } 1245 1246 synchronized(mLockObj) { 1247 if (mSession != null) { 1248 mSession.sendDtmf(c); 1249 } 1250 } 1251 1252 if (result != null) { 1253 result.sendToTarget(); 1254 } 1255 } 1256 1257 /** 1258 * Sends an USSD message. 1259 * 1260 * @param ussdMessage USSD message to send 1261 */ 1262 public void sendUssd(String ussdMessage) throws ImsException { 1263 if (DBG) { 1264 log("sendUssd :: session=" + mSession + ", ussdMessage=" + ussdMessage); 1265 } 1266 1267 synchronized(mLockObj) { 1268 if (mSession == null) { 1269 loge("sendUssd :: "); 1270 throw new ImsException("No call session", 1271 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1272 } 1273 1274 mSession.sendUssd(ussdMessage); 1275 } 1276 } 1277 1278 private void clear(ImsReasonInfo lastReasonInfo) { 1279 mInCall = false; 1280 mHold = false; 1281 mUpdateRequest = UPDATE_NONE; 1282 mLastReasonInfo = lastReasonInfo; 1283 destroyCallGroup(); 1284 } 1285 1286 private void createCallGroup(ImsCall neutralReferrer) { 1287 CallGroup referrerCallGroup = neutralReferrer.getCallGroup(); 1288 1289 if (mCallGroup == null) { 1290 if (referrerCallGroup == null) { 1291 mCallGroup = CallGroupManager.getInstance().createCallGroup(new ImsCallGroup()); 1292 } else { 1293 mCallGroup = referrerCallGroup; 1294 } 1295 1296 if (mCallGroup != null) { 1297 mCallGroup.setNeutralReferrer(neutralReferrer); 1298 } 1299 } else { 1300 mCallGroup.setNeutralReferrer(neutralReferrer); 1301 1302 if ((referrerCallGroup != null) 1303 && (mCallGroup != referrerCallGroup)) { 1304 loge("fatal :: call group is mismatched; call is corrupted..."); 1305 } 1306 } 1307 } 1308 1309 private void updateCallGroup(ImsCall owner) { 1310 if (mCallGroup == null) { 1311 return; 1312 } 1313 1314 ImsCall neutralReferrer = (ImsCall)mCallGroup.getNeutralReferrer(); 1315 1316 if (owner == null) { 1317 // Maintain the call group if the current call has been merged in the past. 1318 if (!mCallGroup.hasReferrer()) { 1319 CallGroupManager.getInstance().destroyCallGroup(mCallGroup); 1320 mCallGroup = null; 1321 } 1322 } else { 1323 mCallGroup.addReferrer(this); 1324 1325 if (neutralReferrer != null) { 1326 if (neutralReferrer.getCallGroup() == null) { 1327 neutralReferrer.setCallGroup(mCallGroup); 1328 mCallGroup.addReferrer(neutralReferrer); 1329 } 1330 1331 neutralReferrer.enforceConversationMode(); 1332 } 1333 1334 // Close the existing owner call if present 1335 ImsCall exOwner = (ImsCall)mCallGroup.getOwner(); 1336 1337 mCallGroup.setOwner(owner); 1338 1339 if (exOwner != null) { 1340 exOwner.close(); 1341 } 1342 } 1343 } 1344 1345 private void destroyCallGroup() { 1346 if (mCallGroup == null) { 1347 return; 1348 } 1349 1350 mCallGroup.removeReferrer(this); 1351 1352 if (!mCallGroup.hasReferrer()) { 1353 CallGroupManager.getInstance().destroyCallGroup(mCallGroup); 1354 } 1355 1356 mCallGroup = null; 1357 } 1358 1359 public CallGroup getCallGroup() { 1360 synchronized(mLockObj) { 1361 return mCallGroup; 1362 } 1363 } 1364 1365 private void setCallGroup(CallGroup callGroup) { 1366 synchronized(mLockObj) { 1367 mCallGroup = callGroup; 1368 } 1369 } 1370 1371 /** 1372 * Creates an IMS call session listener. 1373 */ 1374 private ImsCallSession.Listener createCallSessionListener() { 1375 return new ImsCallSessionListenerProxy(); 1376 } 1377 1378 private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) { 1379 ImsCall call = new ImsCall(mContext, profile); 1380 1381 try { 1382 call.attachSession(session); 1383 } catch (ImsException e) { 1384 if (call != null) { 1385 call.close(); 1386 call = null; 1387 } 1388 } 1389 1390 // Do additional operations... 1391 1392 return call; 1393 } 1394 1395 private ImsStreamMediaProfile createHoldMediaProfile() { 1396 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(); 1397 1398 if (mCallProfile == null) { 1399 return mediaProfile; 1400 } 1401 1402 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality; 1403 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality; 1404 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND; 1405 1406 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) { 1407 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND; 1408 } 1409 1410 return mediaProfile; 1411 } 1412 1413 private ImsStreamMediaProfile createResumeMediaProfile() { 1414 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(); 1415 1416 if (mCallProfile == null) { 1417 return mediaProfile; 1418 } 1419 1420 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality; 1421 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality; 1422 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; 1423 1424 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) { 1425 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; 1426 } 1427 1428 return mediaProfile; 1429 } 1430 1431 private void enforceConversationMode() { 1432 if (mInCall) { 1433 mHold = false; 1434 mUpdateRequest = UPDATE_NONE; 1435 } 1436 } 1437 1438 private void mergeInternal() { 1439 if (DBG) { 1440 log("mergeInternal :: session=" + mSession); 1441 } 1442 1443 mSession.merge(); 1444 mUpdateRequest = UPDATE_MERGE; 1445 } 1446 1447 private void notifyCallStateChanged() { 1448 int state = 0; 1449 1450 if (mInCall && (mUpdateRequest == UPDATE_HOLD_MERGE)) { 1451 state = CALL_STATE_ACTIVE_TO_HOLD; 1452 mHold = true; 1453 } else if (mInCall && ((mUpdateRequest == UPDATE_MERGE) 1454 || (mUpdateRequest == UPDATE_EXTEND_TO_CONFERENCE))) { 1455 state = CALL_STATE_HOLD_TO_ACTIVE; 1456 mHold = false; 1457 mMute = false; 1458 } 1459 1460 if (state != 0) { 1461 if (mListener != null) { 1462 try { 1463 mListener.onCallStateChanged(ImsCall.this, state); 1464 } catch (Throwable t) { 1465 loge("notifyCallStateChanged :: ", t); 1466 } 1467 } 1468 } 1469 } 1470 1471 private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) { 1472 ImsCall.Listener listener; 1473 1474 if (mCallGroup.isOwner(ImsCall.this)) { 1475 log("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size()); 1476 while (mCallGroup.hasReferrer()) { 1477 ImsCall call = (ImsCall) mCallGroup.getReferrers().get(0); 1478 log("onCallTerminated to be called for the call:: " + call); 1479 1480 if (call == null) { 1481 continue; 1482 } 1483 1484 listener = call.mListener; 1485 call.clear(reasonInfo); 1486 1487 if (listener != null) { 1488 try { 1489 listener.onCallTerminated(call, reasonInfo); 1490 } catch (Throwable t) { 1491 loge("notifyConferenceSessionTerminated :: ", t); 1492 } 1493 } 1494 } 1495 } else if (!mCallGroup.isReferrer(ImsCall.this)) { 1496 return; 1497 } 1498 1499 listener = mListener; 1500 clear(reasonInfo); 1501 1502 if (listener != null) { 1503 try { 1504 listener.onCallTerminated(this, reasonInfo); 1505 } catch (Throwable t) { 1506 loge("notifyConferenceSessionTerminated :: ", t); 1507 } 1508 } 1509 } 1510 1511 private void notifyConferenceStateUpdatedThroughGroupOwner(int update) { 1512 ImsCall.Listener listener; 1513 1514 if (mCallGroup.isOwner(ImsCall.this)) { 1515 log("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size()); 1516 for (ICall icall : mCallGroup.getReferrers()) { 1517 ImsCall call = (ImsCall) icall; 1518 log("notifyConferenceStateUpdatedThroughGroupOwner to be called for the call:: " + call); 1519 1520 if (call == null) { 1521 continue; 1522 } 1523 1524 listener = call.mListener; 1525 1526 if (listener != null) { 1527 try { 1528 switch (update) { 1529 case UPDATE_HOLD: 1530 listener.onCallHeld(call); 1531 break; 1532 case UPDATE_RESUME: 1533 listener.onCallResumed(call); 1534 break; 1535 default: 1536 loge("notifyConferenceStateUpdatedThroughGroupOwner :: not handled update " 1537 + update); 1538 } 1539 } catch (Throwable t) { 1540 loge("notifyConferenceStateUpdatedThroughGroupOwner :: ", t); 1541 } 1542 } 1543 } 1544 } 1545 } 1546 1547 private void notifyConferenceStateUpdated(ImsConferenceState state) { 1548 Set<Entry<String, Bundle>> paticipants = state.mParticipants.entrySet(); 1549 1550 if (paticipants == null) { 1551 return; 1552 } 1553 1554 Iterator<Entry<String, Bundle>> iterator = paticipants.iterator(); 1555 1556 while (iterator.hasNext()) { 1557 Entry<String, Bundle> entry = iterator.next(); 1558 1559 String key = entry.getKey(); 1560 Bundle confInfo = entry.getValue(); 1561 String status = confInfo.getString(ImsConferenceState.STATUS); 1562 String user = confInfo.getString(ImsConferenceState.USER); 1563 String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT); 1564 1565 if (DBG) { 1566 log("notifyConferenceStateUpdated :: key=" + key + 1567 ", status=" + status + 1568 ", user=" + user + 1569 ", endpoint=" + endpoint); 1570 } 1571 1572 if ((mCallGroup != null) && (!mCallGroup.isOwner(ImsCall.this))) { 1573 continue; 1574 } 1575 1576 ImsCall referrer = (ImsCall)mCallGroup.getReferrer(endpoint); 1577 1578 if (referrer == null) { 1579 continue; 1580 } 1581 1582 if (referrer.mListener == null) { 1583 continue; 1584 } 1585 1586 try { 1587 if (status.equals(ImsConferenceState.STATUS_ALERTING)) { 1588 referrer.mListener.onCallProgressing(referrer); 1589 } 1590 else if (status.equals(ImsConferenceState.STATUS_CONNECT_FAIL)) { 1591 referrer.mListener.onCallStartFailed(referrer, new ImsReasonInfo()); 1592 } 1593 else if (status.equals(ImsConferenceState.STATUS_ON_HOLD)) { 1594 referrer.mListener.onCallHoldReceived(referrer); 1595 } 1596 else if (status.equals(ImsConferenceState.STATUS_CONNECTED)) { 1597 referrer.mListener.onCallStarted(referrer); 1598 } 1599 else if (status.equals(ImsConferenceState.STATUS_DISCONNECTED)) { 1600 referrer.clear(new ImsReasonInfo()); 1601 referrer.mListener.onCallTerminated(referrer, referrer.mLastReasonInfo); 1602 } 1603 } catch (Throwable t) { 1604 loge("notifyConferenceStateUpdated :: ", t); 1605 } 1606 } 1607 } 1608 1609 private void notifyError(int reason, int statusCode, String message) { 1610 } 1611 1612 private void throwImsException(Throwable t, int code) throws ImsException { 1613 if (t instanceof ImsException) { 1614 throw (ImsException) t; 1615 } else { 1616 throw new ImsException(String.valueOf(code), t, code); 1617 } 1618 } 1619 1620 private void log(String s) { 1621 Rlog.d(TAG, s); 1622 } 1623 1624 private void loge(String s) { 1625 Rlog.e(TAG, s); 1626 } 1627 1628 private void loge(String s, Throwable t) { 1629 Rlog.e(TAG, s, t); 1630 } 1631 1632 private class ImsCallSessionListenerProxy extends ImsCallSession.Listener { 1633 @Override 1634 public void callSessionProgressing(ImsCallSession session, 1635 ImsStreamMediaProfile profile) { 1636 if (DBG) { 1637 log("callSessionProgressing :: session=" + session + ", profile=" + profile); 1638 } 1639 1640 ImsCall.Listener listener; 1641 1642 synchronized(ImsCall.this) { 1643 listener = mListener; 1644 mCallProfile.mMediaProfile.copyFrom(profile); 1645 } 1646 1647 if (listener != null) { 1648 try { 1649 listener.onCallProgressing(ImsCall.this); 1650 } catch (Throwable t) { 1651 loge("callSessionProgressing :: ", t); 1652 } 1653 } 1654 } 1655 1656 @Override 1657 public void callSessionStarted(ImsCallSession session, 1658 ImsCallProfile profile) { 1659 if (DBG) { 1660 log("callSessionStarted :: session=" + session + ", profile=" + profile); 1661 } 1662 1663 ImsCall.Listener listener; 1664 1665 synchronized(ImsCall.this) { 1666 listener = mListener; 1667 mCallProfile = profile; 1668 } 1669 1670 if (listener != null) { 1671 try { 1672 listener.onCallStarted(ImsCall.this); 1673 } catch (Throwable t) { 1674 loge("callSessionStarted :: ", t); 1675 } 1676 } 1677 } 1678 1679 @Override 1680 public void callSessionStartFailed(ImsCallSession session, 1681 ImsReasonInfo reasonInfo) { 1682 if (DBG) { 1683 log("callSessionStartFailed :: session=" + session + 1684 ", reasonInfo=" + reasonInfo); 1685 } 1686 1687 ImsCall.Listener listener; 1688 1689 synchronized(ImsCall.this) { 1690 listener = mListener; 1691 mLastReasonInfo = reasonInfo; 1692 } 1693 1694 if (listener != null) { 1695 try { 1696 listener.onCallStartFailed(ImsCall.this, reasonInfo); 1697 } catch (Throwable t) { 1698 loge("callSessionStarted :: ", t); 1699 } 1700 } 1701 } 1702 1703 @Override 1704 public void callSessionTerminated(ImsCallSession session, 1705 ImsReasonInfo reasonInfo) { 1706 if (DBG) { 1707 log("callSessionTerminated :: session=" + session + 1708 ", reasonInfo=" + reasonInfo); 1709 } 1710 1711 ImsCall.Listener listener = null; 1712 1713 synchronized(ImsCall.this) { 1714 if (mCallGroup != null) { 1715 notifyConferenceSessionTerminated(reasonInfo); 1716 } else { 1717 listener = mListener; 1718 clear(reasonInfo); 1719 } 1720 } 1721 1722 if (listener != null) { 1723 try { 1724 listener.onCallTerminated(ImsCall.this, reasonInfo); 1725 } catch (Throwable t) { 1726 loge("callSessionTerminated :: ", t); 1727 } 1728 } 1729 } 1730 1731 @Override 1732 public void callSessionHeld(ImsCallSession session, 1733 ImsCallProfile profile) { 1734 if (DBG) { 1735 log("callSessionHeld :: session=" + session + ", profile=" + profile); 1736 } 1737 1738 ImsCall.Listener listener; 1739 1740 synchronized(ImsCall.this) { 1741 mCallProfile = profile; 1742 1743 if (mUpdateRequest == UPDATE_HOLD_MERGE) { 1744 mergeInternal(); 1745 return; 1746 } 1747 1748 mUpdateRequest = UPDATE_NONE; 1749 listener = mListener; 1750 } 1751 1752 if (listener != null) { 1753 try { 1754 listener.onCallHeld(ImsCall.this); 1755 } catch (Throwable t) { 1756 loge("callSessionHeld :: ", t); 1757 } 1758 } 1759 1760 if (mCallGroup != null) { 1761 notifyConferenceStateUpdatedThroughGroupOwner(UPDATE_HOLD); 1762 } 1763 } 1764 1765 @Override 1766 public void callSessionHoldFailed(ImsCallSession session, 1767 ImsReasonInfo reasonInfo) { 1768 if (DBG) { 1769 log("callSessionHoldFailed :: session=" + session + 1770 ", reasonInfo=" + reasonInfo); 1771 } 1772 1773 boolean isHoldForMerge = false; 1774 ImsCall.Listener listener; 1775 1776 synchronized(ImsCall.this) { 1777 if (mUpdateRequest == UPDATE_HOLD_MERGE) { 1778 isHoldForMerge = true; 1779 } 1780 1781 mUpdateRequest = UPDATE_NONE; 1782 listener = mListener; 1783 } 1784 1785 if (isHoldForMerge) { 1786 callSessionMergeFailed(session, reasonInfo); 1787 return; 1788 } 1789 1790 if (listener != null) { 1791 try { 1792 listener.onCallHoldFailed(ImsCall.this, reasonInfo); 1793 } catch (Throwable t) { 1794 loge("callSessionHoldFailed :: ", t); 1795 } 1796 } 1797 } 1798 1799 @Override 1800 public void callSessionHoldReceived(ImsCallSession session, 1801 ImsCallProfile profile) { 1802 if (DBG) { 1803 log("callSessionHoldReceived :: session=" + session + ", profile=" + profile); 1804 } 1805 1806 ImsCall.Listener listener; 1807 1808 synchronized(ImsCall.this) { 1809 listener = mListener; 1810 mCallProfile = profile; 1811 } 1812 1813 if (listener != null) { 1814 try { 1815 listener.onCallHoldReceived(ImsCall.this); 1816 } catch (Throwable t) { 1817 loge("callSessionHoldReceived :: ", t); 1818 } 1819 } 1820 } 1821 1822 @Override 1823 public void callSessionResumed(ImsCallSession session, 1824 ImsCallProfile profile) { 1825 if (DBG) { 1826 log("callSessionResumed :: session=" + session + ", profile=" + profile); 1827 } 1828 1829 ImsCall.Listener listener; 1830 1831 synchronized(ImsCall.this) { 1832 listener = mListener; 1833 mCallProfile = profile; 1834 mUpdateRequest = UPDATE_NONE; 1835 } 1836 1837 if (listener != null) { 1838 try { 1839 listener.onCallResumed(ImsCall.this); 1840 } catch (Throwable t) { 1841 loge("callSessionResumed :: ", t); 1842 } 1843 } 1844 1845 if (mCallGroup != null) { 1846 notifyConferenceStateUpdatedThroughGroupOwner(UPDATE_RESUME); 1847 } 1848 } 1849 1850 @Override 1851 public void callSessionResumeFailed(ImsCallSession session, 1852 ImsReasonInfo reasonInfo) { 1853 if (DBG) { 1854 log("callSessionResumeFailed :: session=" + session + 1855 ", reasonInfo=" + reasonInfo); 1856 } 1857 1858 ImsCall.Listener listener; 1859 1860 synchronized(ImsCall.this) { 1861 listener = mListener; 1862 mUpdateRequest = UPDATE_NONE; 1863 } 1864 1865 if (listener != null) { 1866 try { 1867 listener.onCallResumeFailed(ImsCall.this, reasonInfo); 1868 } catch (Throwable t) { 1869 loge("callSessionResumeFailed :: ", t); 1870 } 1871 } 1872 } 1873 1874 @Override 1875 public void callSessionResumeReceived(ImsCallSession session, 1876 ImsCallProfile profile) { 1877 if (DBG) { 1878 log("callSessionResumeReceived :: session=" + session + 1879 ", profile=" + profile); 1880 } 1881 1882 ImsCall.Listener listener; 1883 1884 synchronized(ImsCall.this) { 1885 listener = mListener; 1886 mCallProfile = profile; 1887 } 1888 1889 if (listener != null) { 1890 try { 1891 listener.onCallResumeReceived(ImsCall.this); 1892 } catch (Throwable t) { 1893 loge("callSessionResumeReceived :: ", t); 1894 } 1895 } 1896 } 1897 1898 @Override 1899 public void callSessionMerged(ImsCallSession session, 1900 ImsCallSession newSession, ImsCallProfile profile) { 1901 if (DBG) { 1902 log("callSessionMerged :: session=" + session 1903 + ", newSession=" + newSession + ", profile=" + profile); 1904 } 1905 1906 ImsCall newCall = createNewCall(newSession, profile); 1907 1908 if (newCall == null) { 1909 callSessionMergeFailed(session, new ImsReasonInfo()); 1910 return; 1911 } 1912 1913 ImsCall.Listener listener; 1914 1915 synchronized(ImsCall.this) { 1916 listener = mListener; 1917 updateCallGroup(newCall); 1918 newCall.setListener(mListener); 1919 newCall.setCallGroup(mCallGroup); 1920 mUpdateRequest = UPDATE_NONE; 1921 } 1922 1923 if (listener != null) { 1924 try { 1925 listener.onCallMerged(ImsCall.this, newCall); 1926 } catch (Throwable t) { 1927 loge("callSessionMerged :: ", t); 1928 } 1929 } 1930 } 1931 1932 @Override 1933 public void callSessionMergeFailed(ImsCallSession session, 1934 ImsReasonInfo reasonInfo) { 1935 if (DBG) { 1936 log("callSessionMergeFailed :: session=" + session + 1937 ", reasonInfo=" + reasonInfo); 1938 } 1939 1940 ImsCall.Listener listener; 1941 1942 synchronized(ImsCall.this) { 1943 listener = mListener; 1944 updateCallGroup(null); 1945 mUpdateRequest = UPDATE_NONE; 1946 } 1947 1948 if (listener != null) { 1949 try { 1950 listener.onCallMergeFailed(ImsCall.this, reasonInfo); 1951 } catch (Throwable t) { 1952 loge("callSessionMergeFailed :: ", t); 1953 } 1954 } 1955 } 1956 1957 @Override 1958 public void callSessionUpdated(ImsCallSession session, 1959 ImsCallProfile profile) { 1960 if (DBG) { 1961 log("callSessionUpdated :: session=" + session + ", profile=" + profile); 1962 } 1963 1964 ImsCall.Listener listener; 1965 1966 synchronized(ImsCall.this) { 1967 listener = mListener; 1968 mCallProfile = profile; 1969 mUpdateRequest = UPDATE_NONE; 1970 } 1971 1972 if (listener != null) { 1973 try { 1974 listener.onCallUpdated(ImsCall.this); 1975 } catch (Throwable t) { 1976 loge("callSessionUpdated :: ", t); 1977 } 1978 } 1979 } 1980 1981 @Override 1982 public void callSessionUpdateFailed(ImsCallSession session, 1983 ImsReasonInfo reasonInfo) { 1984 if (DBG) { 1985 log("callSessionUpdateFailed :: session=" + session + 1986 ", reasonInfo=" + reasonInfo); 1987 } 1988 1989 ImsCall.Listener listener; 1990 1991 synchronized(ImsCall.this) { 1992 listener = mListener; 1993 mUpdateRequest = UPDATE_NONE; 1994 } 1995 1996 if (listener != null) { 1997 try { 1998 listener.onCallUpdateFailed(ImsCall.this, reasonInfo); 1999 } catch (Throwable t) { 2000 loge("callSessionUpdateFailed :: ", t); 2001 } 2002 } 2003 } 2004 2005 @Override 2006 public void callSessionUpdateReceived(ImsCallSession session, 2007 ImsCallProfile profile) { 2008 if (DBG) { 2009 log("callSessionUpdateReceived :: session=" + session + 2010 ", profile=" + profile); 2011 } 2012 2013 ImsCall.Listener listener; 2014 2015 synchronized(ImsCall.this) { 2016 listener = mListener; 2017 mProposedCallProfile = profile; 2018 mUpdateRequest = UPDATE_UNSPECIFIED; 2019 } 2020 2021 if (listener != null) { 2022 try { 2023 listener.onCallUpdateReceived(ImsCall.this); 2024 } catch (Throwable t) { 2025 loge("callSessionUpdateReceived :: ", t); 2026 } 2027 } 2028 } 2029 2030 @Override 2031 public void callSessionConferenceExtended(ImsCallSession session, 2032 ImsCallSession newSession, ImsCallProfile profile) { 2033 if (DBG) { 2034 log("callSessionConferenceExtended :: session=" + session 2035 + ", newSession=" + newSession + ", profile=" + profile); 2036 } 2037 2038 ImsCall newCall = createNewCall(newSession, profile); 2039 2040 if (newCall == null) { 2041 callSessionConferenceExtendFailed(session, new ImsReasonInfo()); 2042 return; 2043 } 2044 2045 ImsCall.Listener listener; 2046 2047 synchronized(ImsCall.this) { 2048 listener = mListener; 2049 mUpdateRequest = UPDATE_NONE; 2050 } 2051 2052 if (listener != null) { 2053 try { 2054 listener.onCallConferenceExtended(ImsCall.this, newCall); 2055 } catch (Throwable t) { 2056 loge("callSessionConferenceExtended :: ", t); 2057 } 2058 } 2059 } 2060 2061 @Override 2062 public void callSessionConferenceExtendFailed(ImsCallSession session, 2063 ImsReasonInfo reasonInfo) { 2064 if (DBG) { 2065 log("callSessionConferenceExtendFailed :: session=" + session + 2066 ", reasonInfo=" + reasonInfo); 2067 } 2068 2069 ImsCall.Listener listener; 2070 2071 synchronized(ImsCall.this) { 2072 listener = mListener; 2073 mUpdateRequest = UPDATE_NONE; 2074 } 2075 2076 if (listener != null) { 2077 try { 2078 listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo); 2079 } catch (Throwable t) { 2080 loge("callSessionConferenceExtendFailed :: ", t); 2081 } 2082 } 2083 } 2084 2085 @Override 2086 public void callSessionConferenceExtendReceived(ImsCallSession session, 2087 ImsCallSession newSession, ImsCallProfile profile) { 2088 if (DBG) { 2089 log("callSessionConferenceExtendReceived :: session=" + session 2090 + ", newSession=" + newSession + ", profile=" + profile); 2091 } 2092 2093 ImsCall newCall = createNewCall(newSession, profile); 2094 2095 if (newCall == null) { 2096 // Should all the calls be terminated...??? 2097 return; 2098 } 2099 2100 ImsCall.Listener listener; 2101 2102 synchronized(ImsCall.this) { 2103 listener = mListener; 2104 } 2105 2106 if (listener != null) { 2107 try { 2108 listener.onCallConferenceExtendReceived(ImsCall.this, newCall); 2109 } catch (Throwable t) { 2110 loge("callSessionConferenceExtendReceived :: ", t); 2111 } 2112 } 2113 } 2114 2115 @Override 2116 public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) { 2117 if (DBG) { 2118 log("callSessionInviteParticipantsRequestDelivered :: session=" + session); 2119 } 2120 2121 ImsCall.Listener listener; 2122 2123 synchronized(ImsCall.this) { 2124 listener = mListener; 2125 } 2126 2127 if (listener != null) { 2128 try { 2129 listener.onCallInviteParticipantsRequestDelivered(ImsCall.this); 2130 } catch (Throwable t) { 2131 loge("callSessionInviteParticipantsRequestDelivered :: ", t); 2132 } 2133 } 2134 } 2135 2136 @Override 2137 public void callSessionInviteParticipantsRequestFailed(ImsCallSession session, 2138 ImsReasonInfo reasonInfo) { 2139 if (DBG) { 2140 log("callSessionInviteParticipantsRequestFailed :: session=" + session 2141 + ", reasonInfo=" + reasonInfo); 2142 } 2143 2144 ImsCall.Listener listener; 2145 2146 synchronized(ImsCall.this) { 2147 listener = mListener; 2148 } 2149 2150 if (listener != null) { 2151 try { 2152 listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo); 2153 } catch (Throwable t) { 2154 loge("callSessionInviteParticipantsRequestFailed :: ", t); 2155 } 2156 } 2157 } 2158 2159 @Override 2160 public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) { 2161 if (DBG) { 2162 log("callSessionRemoveParticipantsRequestDelivered :: session=" + session); 2163 } 2164 2165 ImsCall.Listener listener; 2166 2167 synchronized(ImsCall.this) { 2168 listener = mListener; 2169 } 2170 2171 if (listener != null) { 2172 try { 2173 listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this); 2174 } catch (Throwable t) { 2175 loge("callSessionRemoveParticipantsRequestDelivered :: ", t); 2176 } 2177 } 2178 } 2179 2180 @Override 2181 public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session, 2182 ImsReasonInfo reasonInfo) { 2183 if (DBG) { 2184 log("callSessionRemoveParticipantsRequestFailed :: session=" + session 2185 + ", reasonInfo=" + reasonInfo); 2186 } 2187 2188 ImsCall.Listener listener; 2189 2190 synchronized(ImsCall.this) { 2191 listener = mListener; 2192 } 2193 2194 if (listener != null) { 2195 try { 2196 listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo); 2197 } catch (Throwable t) { 2198 loge("callSessionRemoveParticipantsRequestFailed :: ", t); 2199 } 2200 } 2201 } 2202 2203 @Override 2204 public void callSessionConferenceStateUpdated(ImsCallSession session, 2205 ImsConferenceState state) { 2206 if (DBG) { 2207 log("callSessionConferenceStateUpdated :: session=" + session 2208 + ", state=" + state); 2209 } 2210 2211 ImsCall.Listener listener; 2212 2213 synchronized(ImsCall.this) { 2214 notifyConferenceStateUpdated(state); 2215 listener = mListener; 2216 } 2217 2218 if (listener != null) { 2219 try { 2220 listener.onCallConferenceStateUpdated(ImsCall.this, state); 2221 } catch (Throwable t) { 2222 loge("callSessionConferenceStateUpdated :: ", t); 2223 } 2224 } 2225 } 2226 2227 @Override 2228 public void callSessionUssdMessageReceived(ImsCallSession session, 2229 int mode, String ussdMessage) { 2230 if (DBG) { 2231 log("callSessionUssdMessageReceived :: session=" + session 2232 + ", mode=" + mode + ", ussdMessage=" + ussdMessage); 2233 } 2234 2235 ImsCall.Listener listener; 2236 2237 synchronized(ImsCall.this) { 2238 listener = mListener; 2239 } 2240 2241 if (listener != null) { 2242 try { 2243 listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage); 2244 } catch (Throwable t) { 2245 loge("callSessionUssdMessageReceived :: ", t); 2246 } 2247 } 2248 } 2249 } 2250} 2251