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