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