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