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