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 17 package com.android.ims; 18 19 import com.android.internal.R; 20 21 import java.util.ArrayList; 22 import java.util.Iterator; 23 import java.util.List; 24 import java.util.Map.Entry; 25 import java.util.Set; 26 27 import android.content.Context; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.os.Message; 31 import android.telecom.ConferenceParticipant; 32 import android.telecom.Connection; 33 import java.util.Objects; 34 import java.util.concurrent.atomic.AtomicInteger; 35 36 import android.telephony.ServiceState; 37 import android.util.Log; 38 39 import com.android.ims.internal.ICall; 40 import com.android.ims.internal.ImsCallSession; 41 import com.android.ims.internal.ImsStreamMediaSession; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 /** 45 * Handles an IMS voice / video call over LTE. You can instantiate this class with 46 * {@link ImsManager}. 47 * 48 * @hide 49 */ 50 public class ImsCall implements ICall { 51 // Mode of USSD message 52 public static final int USSD_MODE_NOTIFY = 0; 53 public static final int USSD_MODE_REQUEST = 1; 54 55 private static final String TAG = "ImsCall"; 56 57 // This flag is meant to be used as a debugging tool to quickly see all logs 58 // regardless of the actual log level set on this component. 59 private static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */ 60 61 // We will log messages guarded by these flags at the info level. If logging is required 62 // to occur at (and only at) a particular log level, please use the logd, logv and loge 63 // functions as those will not be affected by the value of FORCE_DEBUG at all. 64 // Otherwise, anything guarded by these flags will be logged at the info level since that 65 // level allows those statements ot be logged by default which supports the workflow of 66 // setting FORCE_DEBUG and knowing these logs will show up regardless of the actual log 67 // level of this component. 68 private static final boolean DBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.DEBUG); 69 private static final boolean VDBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.VERBOSE); 70 // This is a special flag that is used only to highlight specific log around bringing 71 // up and tearing down conference calls. At times, these errors are transient and hard to 72 // reproduce so we need to capture this information the first time. 73 // TODO: Set this flag to FORCE_DEBUG once the new conference call logic gets more mileage 74 // across different IMS implementations. 75 private static final boolean CONF_DBG = true; 76 77 private List<ConferenceParticipant> mConferenceParticipants; 78 /** 79 * Listener for events relating to an IMS call, such as when a call is being 80 * received ("on ringing") or a call is outgoing ("on calling"). 81 * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p> 82 */ 83 public static class Listener { 84 /** 85 * Called when a request is sent out to initiate a new call 86 * and 1xx response is received from the network. 87 * The default implementation calls {@link #onCallStateChanged}. 88 * 89 * @param call the call object that carries out the IMS call 90 */ 91 public void onCallProgressing(ImsCall call) { 92 onCallStateChanged(call); 93 } 94 95 /** 96 * Called when the call is established. 97 * The default implementation calls {@link #onCallStateChanged}. 98 * 99 * @param call the call object that carries out the IMS call 100 */ 101 public void onCallStarted(ImsCall call) { 102 onCallStateChanged(call); 103 } 104 105 /** 106 * Called when the call setup is failed. 107 * The default implementation calls {@link #onCallError}. 108 * 109 * @param call the call object that carries out the IMS call 110 * @param reasonInfo detailed reason of the call setup failure 111 */ 112 public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) { 113 onCallError(call, reasonInfo); 114 } 115 116 /** 117 * Called when the call is terminated. 118 * The default implementation calls {@link #onCallStateChanged}. 119 * 120 * @param call the call object that carries out the IMS call 121 * @param reasonInfo detailed reason of the call termination 122 */ 123 public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) { 124 // Store the call termination reason 125 126 onCallStateChanged(call); 127 } 128 129 /** 130 * Called when the call is in hold. 131 * The default implementation calls {@link #onCallStateChanged}. 132 * 133 * @param call the call object that carries out the IMS call 134 */ 135 public void onCallHeld(ImsCall call) { 136 onCallStateChanged(call); 137 } 138 139 /** 140 * Called when the call hold is failed. 141 * The default implementation calls {@link #onCallError}. 142 * 143 * @param call the call object that carries out the IMS call 144 * @param reasonInfo detailed reason of the call hold failure 145 */ 146 public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) { 147 onCallError(call, reasonInfo); 148 } 149 150 /** 151 * Called when the call hold is received from the remote user. 152 * The default implementation calls {@link #onCallStateChanged}. 153 * 154 * @param call the call object that carries out the IMS call 155 */ 156 public void onCallHoldReceived(ImsCall call) { 157 onCallStateChanged(call); 158 } 159 160 /** 161 * Called when the call is in call. 162 * The default implementation calls {@link #onCallStateChanged}. 163 * 164 * @param call the call object that carries out the IMS call 165 */ 166 public void onCallResumed(ImsCall call) { 167 onCallStateChanged(call); 168 } 169 170 /** 171 * Called when the call resume is failed. 172 * The default implementation calls {@link #onCallError}. 173 * 174 * @param call the call object that carries out the IMS call 175 * @param reasonInfo detailed reason of the call resume failure 176 */ 177 public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) { 178 onCallError(call, reasonInfo); 179 } 180 181 /** 182 * Called when the call resume is received from the remote user. 183 * The default implementation calls {@link #onCallStateChanged}. 184 * 185 * @param call the call object that carries out the IMS call 186 */ 187 public void onCallResumeReceived(ImsCall call) { 188 onCallStateChanged(call); 189 } 190 191 /** 192 * Called when the call is in call. 193 * The default implementation calls {@link #onCallStateChanged}. 194 * 195 * @param call the call object that carries out the active IMS call 196 * @param peerCall the call object that carries out the held IMS call 197 * @param swapCalls {@code true} if the foreground and background calls should be swapped 198 * now that the merge has completed. 199 */ 200 public void onCallMerged(ImsCall call, ImsCall peerCall, boolean swapCalls) { 201 onCallStateChanged(call); 202 } 203 204 /** 205 * Called when the call merge is failed. 206 * The default implementation calls {@link #onCallError}. 207 * 208 * @param call the call object that carries out the IMS call 209 * @param reasonInfo detailed reason of the call merge failure 210 */ 211 public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) { 212 onCallError(call, reasonInfo); 213 } 214 215 /** 216 * Called when the call is updated (except for hold/unhold). 217 * The default implementation calls {@link #onCallStateChanged}. 218 * 219 * @param call the call object that carries out the IMS call 220 */ 221 public void onCallUpdated(ImsCall call) { 222 onCallStateChanged(call); 223 } 224 225 /** 226 * Called when the call update is failed. 227 * The default implementation calls {@link #onCallError}. 228 * 229 * @param call the call object that carries out the IMS call 230 * @param reasonInfo detailed reason of the call update failure 231 */ 232 public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) { 233 onCallError(call, reasonInfo); 234 } 235 236 /** 237 * Called when the call update is received from the remote user. 238 * 239 * @param call the call object that carries out the IMS call 240 */ 241 public void onCallUpdateReceived(ImsCall call) { 242 // no-op 243 } 244 245 /** 246 * Called when the call is extended to the conference call. 247 * The default implementation calls {@link #onCallStateChanged}. 248 * 249 * @param call the call object that carries out the IMS call 250 * @param newCall the call object that is extended to the conference from the active call 251 */ 252 public void onCallConferenceExtended(ImsCall call, ImsCall newCall) { 253 onCallStateChanged(call); 254 } 255 256 /** 257 * Called when the conference extension is failed. 258 * The default implementation calls {@link #onCallError}. 259 * 260 * @param call the call object that carries out the IMS call 261 * @param reasonInfo detailed reason of the conference extension failure 262 */ 263 public void onCallConferenceExtendFailed(ImsCall call, 264 ImsReasonInfo reasonInfo) { 265 onCallError(call, reasonInfo); 266 } 267 268 /** 269 * Called when the conference extension is received from the remote user. 270 * 271 * @param call the call object that carries out the IMS call 272 * @param newCall the call object that is extended to the conference from the active call 273 */ 274 public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) { 275 onCallStateChanged(call); 276 } 277 278 /** 279 * Called when the invitation request of the participants is delivered to 280 * the conference server. 281 * 282 * @param call the call object that carries out the IMS call 283 */ 284 public void onCallInviteParticipantsRequestDelivered(ImsCall call) { 285 // no-op 286 } 287 288 /** 289 * Called when the invitation request of the participants is failed. 290 * 291 * @param call the call object that carries out the IMS call 292 * @param reasonInfo detailed reason of the conference invitation failure 293 */ 294 public void onCallInviteParticipantsRequestFailed(ImsCall call, 295 ImsReasonInfo reasonInfo) { 296 // no-op 297 } 298 299 /** 300 * Called when the removal request of the participants is delivered to 301 * the conference server. 302 * 303 * @param call the call object that carries out the IMS call 304 */ 305 public void onCallRemoveParticipantsRequestDelivered(ImsCall call) { 306 // no-op 307 } 308 309 /** 310 * Called when the removal request of the participants is failed. 311 * 312 * @param call the call object that carries out the IMS call 313 * @param reasonInfo detailed reason of the conference removal failure 314 */ 315 public void onCallRemoveParticipantsRequestFailed(ImsCall call, 316 ImsReasonInfo reasonInfo) { 317 // no-op 318 } 319 320 /** 321 * Called when the conference state is updated. 322 * 323 * @param call the call object that carries out the IMS call 324 * @param state state of the participant who is participated in the conference call 325 */ 326 public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) { 327 // no-op 328 } 329 330 /** 331 * Called when the state of IMS conference participant(s) has changed. 332 * 333 * @param call the call object that carries out the IMS call. 334 * @param participants the participant(s) and their new state information. 335 */ 336 public void onConferenceParticipantsStateChanged(ImsCall call, 337 List<ConferenceParticipant> participants) { 338 // no-op 339 } 340 341 /** 342 * Called when the USSD message is received from the network. 343 * 344 * @param mode mode of the USSD message (REQUEST / NOTIFY) 345 * @param ussdMessage USSD message 346 */ 347 public void onCallUssdMessageReceived(ImsCall call, 348 int mode, String ussdMessage) { 349 // no-op 350 } 351 352 /** 353 * Called when an error occurs. The default implementation is no op. 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 reasonInfo detailed reason of this error 359 * @see ImsReasonInfo 360 */ 361 public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) { 362 // no-op 363 } 364 365 /** 366 * Called when an event occurs and the corresponding callback is not 367 * overridden. The default implementation is no op. Error events are 368 * not re-directed to this callback and are handled in {@link #onCallError}. 369 * 370 * @param call the call object that carries out the IMS call 371 */ 372 public void onCallStateChanged(ImsCall call) { 373 // no-op 374 } 375 376 /** 377 * Called when the call moves the hold state to the conversation state. 378 * For example, when merging the active & hold call, the state of all the hold call 379 * will be changed from hold state to conversation state. 380 * This callback method can be invoked even though the application does not trigger 381 * any operations. 382 * 383 * @param call the call object that carries out the IMS call 384 * @param state the detailed state of call state changes; 385 * Refer to CALL_STATE_* in {@link ImsCall} 386 */ 387 public void onCallStateChanged(ImsCall call, int state) { 388 // no-op 389 } 390 391 /** 392 * Called when the call supp service is received 393 * The default implementation calls {@link #onCallStateChanged}. 394 * 395 * @param call the call object that carries out the IMS call 396 */ 397 public void onCallSuppServiceReceived(ImsCall call, 398 ImsSuppServiceNotification suppServiceInfo) { 399 } 400 401 /** 402 * Called when TTY mode of remote party changed 403 * 404 * @param call the call object that carries out the IMS call 405 * @param mode TTY mode of remote party 406 */ 407 public void onCallSessionTtyModeReceived(ImsCall call, int mode) { 408 // no-op 409 } 410 411 /** 412 * Called when handover occurs from one access technology to another. 413 * 414 * @param imsCall ImsCall object 415 * @param srcAccessTech original access technology 416 * @param targetAccessTech new access technology 417 * @param reasonInfo 418 */ 419 public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech, 420 ImsReasonInfo reasonInfo) { 421 } 422 423 /** 424 * Called when handover from one access technology to another fails. 425 * 426 * @param imsCall call that failed the handover. 427 * @param srcAccessTech original access technology 428 * @param targetAccessTech new access technology 429 * @param reasonInfo 430 */ 431 public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech, 432 ImsReasonInfo reasonInfo) { 433 } 434 435 /** 436 * Notifies of a change to the multiparty state for this {@code ImsCall}. 437 * 438 * @param imsCall The IMS call. 439 * @param isMultiParty {@code true} if the call became multiparty, {@code false} 440 * otherwise. 441 */ 442 public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) { 443 } 444 } 445 446 // List of update operation for IMS call control 447 private static final int UPDATE_NONE = 0; 448 private static final int UPDATE_HOLD = 1; 449 private static final int UPDATE_HOLD_MERGE = 2; 450 private static final int UPDATE_RESUME = 3; 451 private static final int UPDATE_MERGE = 4; 452 private static final int UPDATE_EXTEND_TO_CONFERENCE = 5; 453 private static final int UPDATE_UNSPECIFIED = 6; 454 455 // For synchronization of private variables 456 private Object mLockObj = new Object(); 457 private Context mContext; 458 459 // true if the call is established & in the conversation state 460 private boolean mInCall = false; 461 // true if the call is on hold 462 // If it is triggered by the local, mute the call. Otherwise, play local hold tone 463 // or network generated media. 464 private boolean mHold = false; 465 // true if the call is on mute 466 private boolean mMute = false; 467 // It contains the exclusive call update request. Refer to UPDATE_*. 468 private int mUpdateRequest = UPDATE_NONE; 469 470 private ImsCall.Listener mListener = null; 471 472 // When merging two calls together, the "peer" call that will merge into this call. 473 private ImsCall mMergePeer = null; 474 // When merging two calls together, the "host" call we are merging into. 475 private ImsCall mMergeHost = null; 476 477 // True if Conference request was initiated by 478 // Foreground Conference call else it will be false 479 private boolean mMergeRequestedByConference = false; 480 // Wrapper call session to interworking the IMS service (server). 481 private ImsCallSession mSession = null; 482 // Call profile of the current session. 483 // It can be changed at anytime when the call is updated. 484 private ImsCallProfile mCallProfile = null; 485 // Call profile to be updated after the application's action (accept/reject) 486 // to the call update. After the application's action (accept/reject) is done, 487 // it will be set to null. 488 private ImsCallProfile mProposedCallProfile = null; 489 private ImsReasonInfo mLastReasonInfo = null; 490 491 // Media session to control media (audio/video) operations for an IMS call 492 private ImsStreamMediaSession mMediaSession = null; 493 494 // The temporary ImsCallSession that could represent the merged call once 495 // we receive notification that the merge was successful. 496 private ImsCallSession mTransientConferenceSession = null; 497 // While a merge is progressing, we bury any session termination requests 498 // made on the original ImsCallSession until we have closure on the merge request 499 // If the request ultimately fails, we need to act on the termination request 500 // that we buried temporarily. We do this because we feel that timing issues could 501 // cause the termination request to occur just because the merge is succeeding. 502 private boolean mSessionEndDuringMerge = false; 503 // Just like mSessionEndDuringMerge, we need to keep track of the reason why the 504 // termination request was made on the original session in case we need to act 505 // on it in the case of a merge failure. 506 private ImsReasonInfo mSessionEndDuringMergeReasonInfo = null; 507 // This flag is used to indicate if this ImsCall was merged into a conference 508 // or not. It is used primarily to determine if a disconnect sound should 509 // be heard when the call is terminated. 510 private boolean mIsMerged = false; 511 // If true, this flag means that this ImsCall is in the process of merging 512 // into a conference but it does not yet have closure on if it was 513 // actually added to the conference or not. false implies that it either 514 // is not part of a merging conference or already knows if it was 515 // successfully added. 516 private boolean mCallSessionMergePending = false; 517 518 /** 519 * If {@code true}, this flag indicates that a request to terminate the call was made by 520 * Telephony (could be from the user or some internal telephony logic) 521 * and that when we receive a {@link #processCallTerminated(ImsReasonInfo)} callback from the 522 * radio indicating that the call was terminated, we should override any burying of the 523 * termination due to an ongoing conference merge. 524 */ 525 private boolean mTerminationRequestPending = false; 526 527 /** 528 * For multi-party IMS calls (e.g. conferences), determines if this {@link ImsCall} is the one 529 * hosting the call. This is used to distinguish between a situation where an {@link ImsCall} 530 * is {@link #isMultiparty()} because calls were merged on the device, and a situation where 531 * an {@link ImsCall} is {@link #isMultiparty()} because it is a member of a conference started 532 * on another device. 533 * <p> 534 * When {@code true}, this {@link ImsCall} is is the origin of the conference call. 535 * When {@code false}, this {@link ImsCall} is a member of a conference started on another 536 * device. 537 */ 538 private boolean mIsConferenceHost = false; 539 540 /** 541 * Tracks whether this {@link ImsCall} has been a video call at any point in its lifetime. 542 * Some examples of calls which are/were video calls: 543 * 1. A call which has been a video call for its duration. 544 * 2. An audio call upgraded to video (and potentially downgraded to audio later). 545 * 3. A call answered as video which was downgraded to audio. 546 */ 547 private boolean mWasVideoCall = false; 548 549 /** 550 * Unique id generator used to generate call id. 551 */ 552 private static final AtomicInteger sUniqueIdGenerator = new AtomicInteger(); 553 554 /** 555 * Unique identifier. 556 */ 557 public final int uniqueId; 558 559 /** 560 * The current ImsCallSessionListenerProxy. 561 */ 562 private ImsCallSessionListenerProxy mImsCallSessionListenerProxy; 563 564 /** 565 * When calling {@link #terminate(int, int)}, an override for the termination reason which the 566 * modem returns. 567 * 568 * Necessary because passing in an unexpected {@link ImsReasonInfo} reason code to 569 * {@link #terminate(int)} will cause the modem to ignore the terminate request. 570 */ 571 private int mOverrideReason = ImsReasonInfo.CODE_UNSPECIFIED; 572 573 /** 574 * Create an IMS call object. 575 * 576 * @param context the context for accessing system services 577 * @param profile the call profile to make/take a call 578 */ 579 public ImsCall(Context context, ImsCallProfile profile) { 580 mContext = context; 581 setCallProfile(profile); 582 uniqueId = sUniqueIdGenerator.getAndIncrement(); 583 } 584 585 /** 586 * Closes this object. This object is not usable after being closed. 587 */ 588 @Override 589 public void close() { 590 synchronized(mLockObj) { 591 if (mSession != null) { 592 mSession.close(); 593 mSession = null; 594 } else { 595 logi("close :: Cannot close Null call session!"); 596 } 597 598 mCallProfile = null; 599 mProposedCallProfile = null; 600 mLastReasonInfo = null; 601 mMediaSession = null; 602 } 603 } 604 605 /** 606 * Checks if the call has a same remote user identity or not. 607 * 608 * @param userId the remote user identity 609 * @return true if the remote user identity is equal; otherwise, false 610 */ 611 @Override 612 public boolean checkIfRemoteUserIsSame(String userId) { 613 if (userId == null) { 614 return false; 615 } 616 617 return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, "")); 618 } 619 620 /** 621 * Checks if the call is equal or not. 622 * 623 * @param call the call to be compared 624 * @return true if the call is equal; otherwise, false 625 */ 626 @Override 627 public boolean equalsTo(ICall call) { 628 if (call == null) { 629 return false; 630 } 631 632 if (call instanceof ImsCall) { 633 return this.equals(call); 634 } 635 636 return false; 637 } 638 639 public static boolean isSessionAlive(ImsCallSession session) { 640 return session != null && session.isAlive(); 641 } 642 643 /** 644 * Gets the negotiated (local & remote) call profile. 645 * 646 * @return a {@link ImsCallProfile} object that has the negotiated call profile 647 */ 648 public ImsCallProfile getCallProfile() { 649 synchronized(mLockObj) { 650 return mCallProfile; 651 } 652 } 653 654 /** 655 * Replaces the current call profile with a new one, tracking whethere this was previously a 656 * video call or not. 657 * 658 * @param profile The new call profile. 659 */ 660 private void setCallProfile(ImsCallProfile profile) { 661 synchronized(mLockObj) { 662 mCallProfile = profile; 663 trackVideoStateHistory(mCallProfile); 664 } 665 } 666 667 /** 668 * Gets the local call profile (local capabilities). 669 * 670 * @return a {@link ImsCallProfile} object that has the local call profile 671 */ 672 public ImsCallProfile getLocalCallProfile() throws ImsException { 673 synchronized(mLockObj) { 674 if (mSession == null) { 675 throw new ImsException("No call session", 676 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 677 } 678 679 try { 680 return mSession.getLocalCallProfile(); 681 } catch (Throwable t) { 682 loge("getLocalCallProfile :: ", t); 683 throw new ImsException("getLocalCallProfile()", t, 0); 684 } 685 } 686 } 687 688 /** 689 * Gets the remote call profile (remote capabilities). 690 * 691 * @return a {@link ImsCallProfile} object that has the remote call profile 692 */ 693 public ImsCallProfile getRemoteCallProfile() throws ImsException { 694 synchronized(mLockObj) { 695 if (mSession == null) { 696 throw new ImsException("No call session", 697 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 698 } 699 700 try { 701 return mSession.getRemoteCallProfile(); 702 } catch (Throwable t) { 703 loge("getRemoteCallProfile :: ", t); 704 throw new ImsException("getRemoteCallProfile()", t, 0); 705 } 706 } 707 } 708 709 /** 710 * Gets the call profile proposed by the local/remote user. 711 * 712 * @return a {@link ImsCallProfile} object that has the proposed call profile 713 */ 714 public ImsCallProfile getProposedCallProfile() { 715 synchronized(mLockObj) { 716 if (!isInCall()) { 717 return null; 718 } 719 720 return mProposedCallProfile; 721 } 722 } 723 724 /** 725 * Gets the list of conference participants currently 726 * associated with this call. 727 * 728 * @return The list of conference participants. 729 */ 730 public List<ConferenceParticipant> getConferenceParticipants() { 731 synchronized(mLockObj) { 732 logi("getConferenceParticipants :: mConferenceParticipants" 733 + mConferenceParticipants); 734 return mConferenceParticipants; 735 } 736 } 737 738 /** 739 * Gets the state of the {@link ImsCallSession} that carries this call. 740 * The value returned must be one of the states in {@link ImsCallSession#State}. 741 * 742 * @return the session state 743 */ 744 public int getState() { 745 synchronized(mLockObj) { 746 if (mSession == null) { 747 return ImsCallSession.State.IDLE; 748 } 749 750 return mSession.getState(); 751 } 752 } 753 754 /** 755 * Gets the {@link ImsCallSession} that carries this call. 756 * 757 * @return the session object that carries this call 758 * @hide 759 */ 760 public ImsCallSession getCallSession() { 761 synchronized(mLockObj) { 762 return mSession; 763 } 764 } 765 766 /** 767 * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call. 768 * Almost interface APIs are for the VT (Video Telephony). 769 * 770 * @return the media session object that handles the media operation of this call 771 * @hide 772 */ 773 public ImsStreamMediaSession getMediaSession() { 774 synchronized(mLockObj) { 775 return mMediaSession; 776 } 777 } 778 779 /** 780 * Gets the specified property of this call. 781 * 782 * @param name key to get the extra call information defined in {@link ImsCallProfile} 783 * @return the extra call information as string 784 */ 785 public String getCallExtra(String name) throws ImsException { 786 // Lookup the cache 787 788 synchronized(mLockObj) { 789 // If not found, try to get the property from the remote 790 if (mSession == null) { 791 throw new ImsException("No call session", 792 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 793 } 794 795 try { 796 return mSession.getProperty(name); 797 } catch (Throwable t) { 798 loge("getCallExtra :: ", t); 799 throw new ImsException("getCallExtra()", t, 0); 800 } 801 } 802 } 803 804 /** 805 * Gets the last reason information when the call is not established, cancelled or terminated. 806 * 807 * @return the last reason information 808 */ 809 public ImsReasonInfo getLastReasonInfo() { 810 synchronized(mLockObj) { 811 return mLastReasonInfo; 812 } 813 } 814 815 /** 816 * Checks if the call has a pending update operation. 817 * 818 * @return true if the call has a pending update operation 819 */ 820 public boolean hasPendingUpdate() { 821 synchronized(mLockObj) { 822 return (mUpdateRequest != UPDATE_NONE); 823 } 824 } 825 826 /** 827 * Checks if the call is pending a hold operation. 828 * 829 * @return true if the call is pending a hold operation. 830 */ 831 public boolean isPendingHold() { 832 synchronized(mLockObj) { 833 return (mUpdateRequest == UPDATE_HOLD); 834 } 835 } 836 837 /** 838 * Checks if the call is established. 839 * 840 * @return true if the call is established 841 */ 842 public boolean isInCall() { 843 synchronized(mLockObj) { 844 return mInCall; 845 } 846 } 847 848 /** 849 * Checks if the call is muted. 850 * 851 * @return true if the call is muted 852 */ 853 public boolean isMuted() { 854 synchronized(mLockObj) { 855 return mMute; 856 } 857 } 858 859 /** 860 * Checks if the call is on hold. 861 * 862 * @return true if the call is on hold 863 */ 864 public boolean isOnHold() { 865 synchronized(mLockObj) { 866 return mHold; 867 } 868 } 869 870 /** 871 * Determines if the call is a multiparty call. 872 * 873 * @return {@code True} if the call is a multiparty call. 874 */ 875 public boolean isMultiparty() { 876 synchronized(mLockObj) { 877 if (mSession == null) { 878 return false; 879 } 880 881 return mSession.isMultiparty(); 882 } 883 } 884 885 /** 886 * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the 887 * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this 888 * {@link ImsCall} is a member of a conference hosted on another device. 889 * 890 * @return {@code true} if this call is the origin of the conference call it is a member of, 891 * {@code false} otherwise. 892 */ 893 public boolean isConferenceHost() { 894 synchronized(mLockObj) { 895 return isMultiparty() && mIsConferenceHost; 896 } 897 } 898 899 /** 900 * Marks whether an IMS call is merged. This should be set {@code true} when the call merges 901 * into a conference. 902 * 903 * @param isMerged Whether the call is merged. 904 */ 905 public void setIsMerged(boolean isMerged) { 906 mIsMerged = isMerged; 907 } 908 909 /** 910 * @return {@code true} if the call recently merged into a conference call. 911 */ 912 public boolean isMerged() { 913 return mIsMerged; 914 } 915 916 /** 917 * Sets the listener to listen to the IMS call events. 918 * The method calls {@link #setListener setListener(listener, false)}. 919 * 920 * @param listener to listen to the IMS call events of this object; null to remove listener 921 * @see #setListener(Listener, boolean) 922 */ 923 public void setListener(ImsCall.Listener listener) { 924 setListener(listener, false); 925 } 926 927 /** 928 * Sets the listener to listen to the IMS call events. 929 * A {@link ImsCall} can only hold one listener at a time. Subsequent calls 930 * to this method override the previous listener. 931 * 932 * @param listener to listen to the IMS call events of this object; null to remove listener 933 * @param callbackImmediately set to true if the caller wants to be called 934 * back immediately on the current state 935 */ 936 public void setListener(ImsCall.Listener listener, boolean callbackImmediately) { 937 boolean inCall; 938 boolean onHold; 939 int state; 940 ImsReasonInfo lastReasonInfo; 941 942 synchronized(mLockObj) { 943 mListener = listener; 944 945 if ((listener == null) || !callbackImmediately) { 946 return; 947 } 948 949 inCall = mInCall; 950 onHold = mHold; 951 state = getState(); 952 lastReasonInfo = mLastReasonInfo; 953 } 954 955 try { 956 if (lastReasonInfo != null) { 957 listener.onCallError(this, lastReasonInfo); 958 } else if (inCall) { 959 if (onHold) { 960 listener.onCallHeld(this); 961 } else { 962 listener.onCallStarted(this); 963 } 964 } else { 965 switch (state) { 966 case ImsCallSession.State.ESTABLISHING: 967 listener.onCallProgressing(this); 968 break; 969 case ImsCallSession.State.TERMINATED: 970 listener.onCallTerminated(this, lastReasonInfo); 971 break; 972 default: 973 // Ignore it. There is no action in the other state. 974 break; 975 } 976 } 977 } catch (Throwable t) { 978 loge("setListener() :: ", t); 979 } 980 } 981 982 /** 983 * Mutes or unmutes the mic for the active call. 984 * 985 * @param muted true if the call is muted, false otherwise 986 */ 987 public void setMute(boolean muted) throws ImsException { 988 synchronized(mLockObj) { 989 if (mMute != muted) { 990 logi("setMute :: turning mute " + (muted ? "on" : "off")); 991 mMute = muted; 992 993 try { 994 mSession.setMute(muted); 995 } catch (Throwable t) { 996 loge("setMute :: ", t); 997 throwImsException(t, 0); 998 } 999 } 1000 } 1001 } 1002 1003 /** 1004 * Attaches an incoming call to this call object. 1005 * 1006 * @param session the session that receives the incoming call 1007 * @throws ImsException if the IMS service fails to attach this object to the session 1008 */ 1009 public void attachSession(ImsCallSession session) throws ImsException { 1010 logi("attachSession :: session=" + session); 1011 1012 synchronized(mLockObj) { 1013 mSession = session; 1014 1015 try { 1016 mSession.setListener(createCallSessionListener()); 1017 } catch (Throwable t) { 1018 loge("attachSession :: ", t); 1019 throwImsException(t, 0); 1020 } 1021 } 1022 } 1023 1024 /** 1025 * Initiates an IMS call with the call profile which is provided 1026 * when creating a {@link ImsCall}. 1027 * 1028 * @param session the {@link ImsCallSession} for carrying out the call 1029 * @param callee callee information to initiate an IMS call 1030 * @throws ImsException if the IMS service fails to initiate the call 1031 */ 1032 public void start(ImsCallSession session, String callee) 1033 throws ImsException { 1034 logi("start(1) :: session=" + session); 1035 1036 synchronized(mLockObj) { 1037 mSession = session; 1038 1039 try { 1040 session.setListener(createCallSessionListener()); 1041 session.start(callee, mCallProfile); 1042 } catch (Throwable t) { 1043 loge("start(1) :: ", t); 1044 throw new ImsException("start(1)", t, 0); 1045 } 1046 } 1047 } 1048 1049 /** 1050 * Initiates an IMS conferenca call with the call profile which is provided 1051 * when creating a {@link ImsCall}. 1052 * 1053 * @param session the {@link ImsCallSession} for carrying out the call 1054 * @param participants participant list to initiate an IMS conference call 1055 * @throws ImsException if the IMS service fails to initiate the call 1056 */ 1057 public void start(ImsCallSession session, String[] participants) 1058 throws ImsException { 1059 logi("start(n) :: session=" + session); 1060 1061 synchronized(mLockObj) { 1062 mSession = session; 1063 1064 try { 1065 session.setListener(createCallSessionListener()); 1066 session.start(participants, mCallProfile); 1067 } catch (Throwable t) { 1068 loge("start(n) :: ", t); 1069 throw new ImsException("start(n)", t, 0); 1070 } 1071 } 1072 } 1073 1074 /** 1075 * Accepts a call. 1076 * 1077 * @see Listener#onCallStarted 1078 * 1079 * @param callType The call type the user agreed to for accepting the call. 1080 * @throws ImsException if the IMS service fails to accept the call 1081 */ 1082 public void accept(int callType) throws ImsException { 1083 accept(callType, new ImsStreamMediaProfile()); 1084 } 1085 1086 /** 1087 * Accepts a call. 1088 * 1089 * @param callType call type to be answered in {@link ImsCallProfile} 1090 * @param profile a media profile to be answered (audio/audio & video, direction, ...) 1091 * @see Listener#onCallStarted 1092 * @throws ImsException if the IMS service fails to accept the call 1093 */ 1094 public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException { 1095 logi("accept :: callType=" + callType + ", profile=" + profile); 1096 1097 synchronized(mLockObj) { 1098 if (mSession == null) { 1099 throw new ImsException("No call to answer", 1100 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1101 } 1102 1103 try { 1104 mSession.accept(callType, profile); 1105 } catch (Throwable t) { 1106 loge("accept :: ", t); 1107 throw new ImsException("accept()", t, 0); 1108 } 1109 1110 if (mInCall && (mProposedCallProfile != null)) { 1111 if (DBG) { 1112 logi("accept :: call profile will be updated"); 1113 } 1114 1115 mCallProfile = mProposedCallProfile; 1116 trackVideoStateHistory(mCallProfile); 1117 mProposedCallProfile = null; 1118 } 1119 1120 // Other call update received 1121 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) { 1122 mUpdateRequest = UPDATE_NONE; 1123 } 1124 } 1125 } 1126 1127 /** 1128 * Rejects a call. 1129 * 1130 * @param reason reason code to reject an incoming call 1131 * @see Listener#onCallStartFailed 1132 * @throws ImsException if the IMS service fails to reject the call 1133 */ 1134 public void reject(int reason) throws ImsException { 1135 logi("reject :: reason=" + reason); 1136 1137 synchronized(mLockObj) { 1138 if (mSession != null) { 1139 mSession.reject(reason); 1140 } 1141 1142 if (mInCall && (mProposedCallProfile != null)) { 1143 if (DBG) { 1144 logi("reject :: call profile is not updated; destroy it..."); 1145 } 1146 1147 mProposedCallProfile = null; 1148 } 1149 1150 // Other call update received 1151 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) { 1152 mUpdateRequest = UPDATE_NONE; 1153 } 1154 } 1155 } 1156 1157 public void terminate(int reason, int overrideReason) throws ImsException { 1158 logi("terminate :: reason=" + reason + " ; overrideReadon=" + overrideReason); 1159 mOverrideReason = overrideReason; 1160 terminate(reason); 1161 } 1162 1163 /** 1164 * Terminates an IMS call (e.g. user initiated). 1165 * 1166 * @param reason reason code to terminate a call 1167 * @throws ImsException if the IMS service fails to terminate the call 1168 */ 1169 public void terminate(int reason) throws ImsException { 1170 logi("terminate :: reason=" + reason); 1171 1172 synchronized(mLockObj) { 1173 mHold = false; 1174 mInCall = false; 1175 mTerminationRequestPending = true; 1176 1177 if (mSession != null) { 1178 // TODO: Fix the fact that user invoked call terminations during 1179 // the process of establishing a conference call needs to be handled 1180 // as a special case. 1181 // Currently, any terminations (both invoked by the user or 1182 // by the network results in a callSessionTerminated() callback 1183 // from the network. When establishing a conference call we bury 1184 // these callbacks until we get closure on all participants of the 1185 // conference. In some situations, we will throw away the callback 1186 // (when the underlying session of the host of the new conference 1187 // is terminated) or will will unbury it when the conference has been 1188 // established, like when the peer of the new conference goes away 1189 // after the conference has been created. The UI relies on the callback 1190 // to reflect the fact that the call is gone. 1191 // So if a user decides to terminated a call while it is merging, it 1192 // could take a long time to reflect in the UI due to the conference 1193 // processing but we should probably cancel that and just terminate 1194 // the call immediately and clean up. This is not a huge issue right 1195 // now because we have not seen instances where establishing a 1196 // conference takes a long time (more than a second or two). 1197 mSession.terminate(reason); 1198 } 1199 } 1200 } 1201 1202 1203 /** 1204 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called. 1205 * 1206 * @see Listener#onCallHeld, Listener#onCallHoldFailed 1207 * @throws ImsException if the IMS service fails to hold the call 1208 */ 1209 public void hold() throws ImsException { 1210 logi("hold :: "); 1211 1212 if (isOnHold()) { 1213 if (DBG) { 1214 logi("hold :: call is already on hold"); 1215 } 1216 return; 1217 } 1218 1219 synchronized(mLockObj) { 1220 if (mUpdateRequest != UPDATE_NONE) { 1221 loge("hold :: update is in progress; request=" + 1222 updateRequestToString(mUpdateRequest)); 1223 throw new ImsException("Call update is in progress", 1224 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1225 } 1226 1227 if (mSession == null) { 1228 throw new ImsException("No call session", 1229 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1230 } 1231 1232 mSession.hold(createHoldMediaProfile()); 1233 // FIXME: We should update the state on the callback because that is where 1234 // we can confirm that the hold request was successful or not. 1235 mHold = true; 1236 mUpdateRequest = UPDATE_HOLD; 1237 } 1238 } 1239 1240 /** 1241 * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called. 1242 * 1243 * @see Listener#onCallResumed, Listener#onCallResumeFailed 1244 * @throws ImsException if the IMS service fails to resume the call 1245 */ 1246 public void resume() throws ImsException { 1247 logi("resume :: "); 1248 1249 if (!isOnHold()) { 1250 if (DBG) { 1251 logi("resume :: call is not being held"); 1252 } 1253 return; 1254 } 1255 1256 synchronized(mLockObj) { 1257 if (mUpdateRequest != UPDATE_NONE) { 1258 loge("resume :: update is in progress; request=" + 1259 updateRequestToString(mUpdateRequest)); 1260 throw new ImsException("Call update is in progress", 1261 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1262 } 1263 1264 if (mSession == null) { 1265 loge("resume :: "); 1266 throw new ImsException("No call session", 1267 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1268 } 1269 1270 // mHold is set to false in confirmation callback that the 1271 // ImsCall was resumed. 1272 mUpdateRequest = UPDATE_RESUME; 1273 mSession.resume(createResumeMediaProfile()); 1274 } 1275 } 1276 1277 /** 1278 * Merges the active & hold call. 1279 * 1280 * @see Listener#onCallMerged, Listener#onCallMergeFailed 1281 * @throws ImsException if the IMS service fails to merge the call 1282 */ 1283 private void merge() throws ImsException { 1284 logi("merge :: "); 1285 1286 synchronized(mLockObj) { 1287 if (mUpdateRequest != UPDATE_NONE) { 1288 loge("merge :: update is in progress; request=" + 1289 updateRequestToString(mUpdateRequest)); 1290 throw new ImsException("Call update is in progress", 1291 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1292 } 1293 1294 if (mSession == null) { 1295 loge("merge :: no call session"); 1296 throw new ImsException("No call session", 1297 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1298 } 1299 1300 // if skipHoldBeforeMerge = true, IMS service implementation will 1301 // merge without explicitly holding the call. 1302 if (mHold || (mContext.getResources().getBoolean( 1303 com.android.internal.R.bool.skipHoldBeforeMerge))) { 1304 1305 if (mMergePeer != null && !mMergePeer.isMultiparty() && !isMultiparty()) { 1306 // We only set UPDATE_MERGE when we are adding the first 1307 // calls to the Conference. If there is already a conference 1308 // no special handling is needed. The existing conference 1309 // session will just go active and any other sessions will be terminated 1310 // if needed. There will be no merge failed callback. 1311 // Mark both the host and peer UPDATE_MERGE to ensure both are aware that a 1312 // merge is pending. 1313 mUpdateRequest = UPDATE_MERGE; 1314 mMergePeer.mUpdateRequest = UPDATE_MERGE; 1315 } 1316 1317 mSession.merge(); 1318 } else { 1319 // This code basically says, we need to explicitly hold before requesting a merge 1320 // when we get the callback that the hold was successful (or failed), we should 1321 // automatically request a merge. 1322 mSession.hold(createHoldMediaProfile()); 1323 mHold = true; 1324 mUpdateRequest = UPDATE_HOLD_MERGE; 1325 } 1326 } 1327 } 1328 1329 /** 1330 * Merges the active & hold call. 1331 * 1332 * @param bgCall the background (holding) call 1333 * @see Listener#onCallMerged, Listener#onCallMergeFailed 1334 * @throws ImsException if the IMS service fails to merge the call 1335 */ 1336 public void merge(ImsCall bgCall) throws ImsException { 1337 logi("merge(1) :: bgImsCall=" + bgCall); 1338 1339 if (bgCall == null) { 1340 throw new ImsException("No background call", 1341 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 1342 } 1343 1344 synchronized(mLockObj) { 1345 // Mark both sessions as pending merge. 1346 this.setCallSessionMergePending(true); 1347 bgCall.setCallSessionMergePending(true); 1348 1349 if ((!isMultiparty() && !bgCall.isMultiparty()) || isMultiparty()) { 1350 // If neither call is multiparty, the current call is the merge host and the bg call 1351 // is the merge peer (ie we're starting a new conference). 1352 // OR 1353 // If this call is multiparty, it is the merge host and the other call is the merge 1354 // peer. 1355 setMergePeer(bgCall); 1356 } else { 1357 // If the bg call is multiparty, it is the merge host. 1358 setMergeHost(bgCall); 1359 } 1360 } 1361 1362 if (isMultiparty()) { 1363 mMergeRequestedByConference = true; 1364 } else { 1365 logi("merge : mMergeRequestedByConference not set"); 1366 } 1367 merge(); 1368 } 1369 1370 /** 1371 * Updates the current call's properties (ex. call mode change: video upgrade / downgrade). 1372 */ 1373 public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException { 1374 logi("update :: callType=" + callType + ", mediaProfile=" + mediaProfile); 1375 1376 if (isOnHold()) { 1377 if (DBG) { 1378 logi("update :: call is on hold"); 1379 } 1380 throw new ImsException("Not in a call to update call", 1381 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1382 } 1383 1384 synchronized(mLockObj) { 1385 if (mUpdateRequest != UPDATE_NONE) { 1386 if (DBG) { 1387 logi("update :: update is in progress; request=" + 1388 updateRequestToString(mUpdateRequest)); 1389 } 1390 throw new ImsException("Call update is in progress", 1391 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1392 } 1393 1394 if (mSession == null) { 1395 loge("update :: "); 1396 throw new ImsException("No call session", 1397 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1398 } 1399 1400 mSession.update(callType, mediaProfile); 1401 mUpdateRequest = UPDATE_UNSPECIFIED; 1402 } 1403 } 1404 1405 /** 1406 * Extends this call (1-to-1 call) to the conference call 1407 * inviting the specified participants to. 1408 * 1409 */ 1410 public void extendToConference(String[] participants) throws ImsException { 1411 logi("extendToConference ::"); 1412 1413 if (isOnHold()) { 1414 if (DBG) { 1415 logi("extendToConference :: call is on hold"); 1416 } 1417 throw new ImsException("Not in a call to extend a call to conference", 1418 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1419 } 1420 1421 synchronized(mLockObj) { 1422 if (mUpdateRequest != UPDATE_NONE) { 1423 if (CONF_DBG) { 1424 logi("extendToConference :: update is in progress; request=" + 1425 updateRequestToString(mUpdateRequest)); 1426 } 1427 throw new ImsException("Call update is in progress", 1428 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 1429 } 1430 1431 if (mSession == null) { 1432 loge("extendToConference :: "); 1433 throw new ImsException("No call session", 1434 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1435 } 1436 1437 mSession.extendToConference(participants); 1438 mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE; 1439 } 1440 } 1441 1442 /** 1443 * Requests the conference server to invite an additional participants to the conference. 1444 * 1445 */ 1446 public void inviteParticipants(String[] participants) throws ImsException { 1447 logi("inviteParticipants ::"); 1448 1449 synchronized(mLockObj) { 1450 if (mSession == null) { 1451 loge("inviteParticipants :: "); 1452 throw new ImsException("No call session", 1453 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1454 } 1455 1456 mSession.inviteParticipants(participants); 1457 } 1458 } 1459 1460 /** 1461 * Requests the conference server to remove the specified participants from the conference. 1462 * 1463 */ 1464 public void removeParticipants(String[] participants) throws ImsException { 1465 logi("removeParticipants :: session=" + mSession); 1466 synchronized(mLockObj) { 1467 if (mSession == null) { 1468 loge("removeParticipants :: "); 1469 throw new ImsException("No call session", 1470 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1471 } 1472 1473 mSession.removeParticipants(participants); 1474 1475 } 1476 } 1477 1478 /** 1479 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, 1480 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, 1481 * and event flash to 16. Currently, event flash is not supported. 1482 * 1483 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. 1484 * @param result the result message to send when done. 1485 */ 1486 public void sendDtmf(char c, Message result) { 1487 logi("sendDtmf :: code=" + c); 1488 1489 synchronized(mLockObj) { 1490 if (mSession != null) { 1491 mSession.sendDtmf(c, result); 1492 } 1493 } 1494 } 1495 1496 /** 1497 * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, 1498 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, 1499 * and event flash to 16. Currently, event flash is not supported. 1500 * 1501 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. 1502 */ 1503 public void startDtmf(char c) { 1504 logi("startDtmf :: code=" + c); 1505 1506 synchronized(mLockObj) { 1507 if (mSession != null) { 1508 mSession.startDtmf(c); 1509 } 1510 } 1511 } 1512 1513 /** 1514 * Stop a DTMF code. 1515 */ 1516 public void stopDtmf() { 1517 logi("stopDtmf :: "); 1518 1519 synchronized(mLockObj) { 1520 if (mSession != null) { 1521 mSession.stopDtmf(); 1522 } 1523 } 1524 } 1525 1526 /** 1527 * Sends an USSD message. 1528 * 1529 * @param ussdMessage USSD message to send 1530 */ 1531 public void sendUssd(String ussdMessage) throws ImsException { 1532 logi("sendUssd :: ussdMessage=" + ussdMessage); 1533 1534 synchronized(mLockObj) { 1535 if (mSession == null) { 1536 loge("sendUssd :: "); 1537 throw new ImsException("No call session", 1538 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); 1539 } 1540 1541 mSession.sendUssd(ussdMessage); 1542 } 1543 } 1544 1545 private void clear(ImsReasonInfo lastReasonInfo) { 1546 mInCall = false; 1547 mHold = false; 1548 mUpdateRequest = UPDATE_NONE; 1549 mLastReasonInfo = lastReasonInfo; 1550 } 1551 1552 /** 1553 * Creates an IMS call session listener. 1554 */ 1555 private ImsCallSession.Listener createCallSessionListener() { 1556 mImsCallSessionListenerProxy = new ImsCallSessionListenerProxy(); 1557 return mImsCallSessionListenerProxy; 1558 } 1559 1560 /** 1561 * @return the current ImsCallSessionListenerProxy. NOTE: ONLY FOR USE WITH TESTING. 1562 */ 1563 @VisibleForTesting 1564 public ImsCallSessionListenerProxy getImsCallSessionListenerProxy() { 1565 return mImsCallSessionListenerProxy; 1566 } 1567 1568 private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) { 1569 ImsCall call = new ImsCall(mContext, profile); 1570 1571 try { 1572 call.attachSession(session); 1573 } catch (ImsException e) { 1574 if (call != null) { 1575 call.close(); 1576 call = null; 1577 } 1578 } 1579 1580 // Do additional operations... 1581 1582 return call; 1583 } 1584 1585 private ImsStreamMediaProfile createHoldMediaProfile() { 1586 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(); 1587 1588 if (mCallProfile == null) { 1589 return mediaProfile; 1590 } 1591 1592 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality; 1593 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality; 1594 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND; 1595 1596 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) { 1597 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND; 1598 } 1599 1600 return mediaProfile; 1601 } 1602 1603 private ImsStreamMediaProfile createResumeMediaProfile() { 1604 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(); 1605 1606 if (mCallProfile == null) { 1607 return mediaProfile; 1608 } 1609 1610 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality; 1611 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality; 1612 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; 1613 1614 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) { 1615 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; 1616 } 1617 1618 return mediaProfile; 1619 } 1620 1621 private void enforceConversationMode() { 1622 if (mInCall) { 1623 mHold = false; 1624 mUpdateRequest = UPDATE_NONE; 1625 } 1626 } 1627 1628 private void mergeInternal() { 1629 if (CONF_DBG) { 1630 logi("mergeInternal :: "); 1631 } 1632 1633 mSession.merge(); 1634 mUpdateRequest = UPDATE_MERGE; 1635 } 1636 1637 private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) { 1638 ImsCall.Listener listener = mListener; 1639 clear(reasonInfo); 1640 1641 if (listener != null) { 1642 try { 1643 listener.onCallTerminated(this, reasonInfo); 1644 } catch (Throwable t) { 1645 loge("notifyConferenceSessionTerminated :: ", t); 1646 } 1647 } 1648 } 1649 1650 private void notifyConferenceStateUpdated(ImsConferenceState state) { 1651 if (state == null || state.mParticipants == null) { 1652 return; 1653 } 1654 1655 Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet(); 1656 1657 if (participants == null) { 1658 return; 1659 } 1660 1661 Iterator<Entry<String, Bundle>> iterator = participants.iterator(); 1662 mConferenceParticipants = new ArrayList<>(participants.size()); 1663 while (iterator.hasNext()) { 1664 Entry<String, Bundle> entry = iterator.next(); 1665 1666 String key = entry.getKey(); 1667 Bundle confInfo = entry.getValue(); 1668 String status = confInfo.getString(ImsConferenceState.STATUS); 1669 String user = confInfo.getString(ImsConferenceState.USER); 1670 String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT); 1671 String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT); 1672 1673 if (CONF_DBG) { 1674 logi("notifyConferenceStateUpdated :: key=" + key + 1675 ", status=" + status + 1676 ", user=" + user + 1677 ", displayName= " + displayName + 1678 ", endpoint=" + endpoint); 1679 } 1680 1681 Uri handle = Uri.parse(user); 1682 if (endpoint == null) { 1683 endpoint = ""; 1684 } 1685 Uri endpointUri = Uri.parse(endpoint); 1686 int connectionState = ImsConferenceState.getConnectionStateForStatus(status); 1687 1688 if (connectionState != Connection.STATE_DISCONNECTED) { 1689 ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle, 1690 displayName, endpointUri, connectionState); 1691 mConferenceParticipants.add(conferenceParticipant); 1692 } 1693 } 1694 1695 if (mConferenceParticipants != null && mListener != null) { 1696 try { 1697 mListener.onConferenceParticipantsStateChanged(this, mConferenceParticipants); 1698 } catch (Throwable t) { 1699 loge("notifyConferenceStateUpdated :: ", t); 1700 } 1701 } 1702 } 1703 1704 /** 1705 * Perform all cleanup and notification around the termination of a session. 1706 * Note that there are 2 distinct modes of operation. The first is when 1707 * we receive a session termination on the primary session when we are 1708 * in the processing of merging. The second is when we are not merging anything 1709 * and the call is terminated. 1710 * 1711 * @param reasonInfo The reason for the session termination 1712 */ 1713 private void processCallTerminated(ImsReasonInfo reasonInfo) { 1714 logi("processCallTerminated :: reason=" + reasonInfo + " userInitiated = " + 1715 mTerminationRequestPending); 1716 1717 ImsCall.Listener listener = null; 1718 synchronized(ImsCall.this) { 1719 // If we are in the midst of establishing a conference, we will bury the termination 1720 // until the merge has completed. If necessary we can surface the termination at 1721 // this point. 1722 // We will also NOT bury the termination if a termination was initiated locally. 1723 if (isCallSessionMergePending() && !mTerminationRequestPending) { 1724 // Since we are in the process of a merge, this trigger means something 1725 // else because it is probably due to the merge happening vs. the 1726 // session is really terminated. Let's flag this and revisit if 1727 // the merge() ends up failing because we will need to take action on the 1728 // mSession in that case since the termination was not due to the merge 1729 // succeeding. 1730 if (CONF_DBG) { 1731 logi("processCallTerminated :: burying termination during ongoing merge."); 1732 } 1733 mSessionEndDuringMerge = true; 1734 mSessionEndDuringMergeReasonInfo = reasonInfo; 1735 return; 1736 } 1737 1738 // If we are terminating the conference call, notify using conference listeners. 1739 if (isMultiparty()) { 1740 notifyConferenceSessionTerminated(reasonInfo); 1741 return; 1742 } else { 1743 listener = mListener; 1744 clear(reasonInfo); 1745 } 1746 } 1747 1748 if (listener != null) { 1749 try { 1750 listener.onCallTerminated(ImsCall.this, reasonInfo); 1751 } catch (Throwable t) { 1752 loge("processCallTerminated :: ", t); 1753 } 1754 } 1755 } 1756 1757 /** 1758 * This function determines if the ImsCallSession is our actual ImsCallSession or if is 1759 * the transient session used in the process of creating a conference. This function should only 1760 * be called within callbacks that are not directly related to conference merging but might 1761 * potentially still be called on the transient ImsCallSession sent to us from 1762 * callSessionMergeStarted() when we don't really care. In those situations, we probably don't 1763 * want to take any action so we need to know that we can return early. 1764 * 1765 * @param session - The {@link ImsCallSession} that the function needs to analyze 1766 * @return true if this is the transient {@link ImsCallSession}, false otherwise. 1767 */ 1768 private boolean isTransientConferenceSession(ImsCallSession session) { 1769 if (session != null && session != mSession && session == mTransientConferenceSession) { 1770 return true; 1771 } 1772 return false; 1773 } 1774 1775 private void setTransientSessionAsPrimary(ImsCallSession transientSession) { 1776 synchronized (ImsCall.this) { 1777 mSession.setListener(null); 1778 mSession = transientSession; 1779 mSession.setListener(createCallSessionListener()); 1780 } 1781 } 1782 1783 private void markCallAsMerged(boolean playDisconnectTone) { 1784 if (!isSessionAlive(mSession)) { 1785 // If the peer is dead, let's not play a disconnect sound for it when we 1786 // unbury the termination callback. 1787 logi("markCallAsMerged"); 1788 setIsMerged(playDisconnectTone); 1789 mSessionEndDuringMerge = true; 1790 String reasonInfo; 1791 if (playDisconnectTone) { 1792 reasonInfo = "Call ended by network"; 1793 } else { 1794 reasonInfo = "Call ended during conference merge process."; 1795 } 1796 mSessionEndDuringMergeReasonInfo = new ImsReasonInfo( 1797 ImsReasonInfo.CODE_UNSPECIFIED, 0, reasonInfo); 1798 } 1799 } 1800 1801 /** 1802 * Checks if the merge was requested by foreground conference call 1803 * 1804 * @return true if the merge was requested by foreground conference call 1805 */ 1806 public boolean isMergeRequestedByConf() { 1807 synchronized(mLockObj) { 1808 return mMergeRequestedByConference; 1809 } 1810 } 1811 1812 /** 1813 * Resets the flag which indicates merge request was sent by 1814 * foreground conference call 1815 */ 1816 public void resetIsMergeRequestedByConf(boolean value) { 1817 synchronized(mLockObj) { 1818 mMergeRequestedByConference = value; 1819 } 1820 } 1821 1822 /** 1823 * Returns current ImsCallSession 1824 * 1825 * @return current session 1826 */ 1827 public ImsCallSession getSession() { 1828 synchronized(mLockObj) { 1829 return mSession; 1830 } 1831 } 1832 1833 /** 1834 * We have detected that a initial conference call has been fully configured. The internal 1835 * state of both {@code ImsCall} objects need to be cleaned up to reflect the new state. 1836 * This function should only be called in the context of the merge host to simplify logic 1837 * 1838 */ 1839 private void processMergeComplete() { 1840 logi("processMergeComplete :: "); 1841 1842 // The logic simplifies if we can assume that this function is only called on 1843 // the merge host. 1844 if (!isMergeHost()) { 1845 loge("processMergeComplete :: We are not the merge host!"); 1846 return; 1847 } 1848 1849 ImsCall.Listener listener; 1850 boolean swapRequired = false; 1851 1852 ImsCall finalHostCall; 1853 ImsCall finalPeerCall; 1854 1855 synchronized(ImsCall.this) { 1856 if (isMultiparty()) { 1857 setIsMerged(false); 1858 // if case handles Case 4 explained in callSessionMergeComplete 1859 // otherwise it is case 5 1860 if (!mMergeRequestedByConference) { 1861 // single call in fg, conference call in bg. 1862 // Finally conf call becomes active after conference 1863 this.mHold = false; 1864 swapRequired = true; 1865 } 1866 mMergePeer.markCallAsMerged(false); 1867 finalHostCall = this; 1868 finalPeerCall = mMergePeer; 1869 } else { 1870 // If we are here, we are not trying to merge a new call into an existing 1871 // conference. That means that there is a transient session on the merge 1872 // host that represents the future conference once all the parties 1873 // have been added to it. So make sure that it exists or else something 1874 // very wrong is going on. 1875 if (mTransientConferenceSession == null) { 1876 loge("processMergeComplete :: No transient session!"); 1877 return; 1878 } 1879 if (mMergePeer == null) { 1880 loge("processMergeComplete :: No merge peer!"); 1881 return; 1882 } 1883 1884 // Since we are the host, we have the transient session attached to us. Let's detach 1885 // it and figure out where we need to set it for the final conference configuration. 1886 ImsCallSession transientConferenceSession = mTransientConferenceSession; 1887 mTransientConferenceSession = null; 1888 1889 // Clear the listener for this transient session, we'll create a new listener 1890 // when it is attached to the final ImsCall that it should live on. 1891 transientConferenceSession.setListener(null); 1892 1893 // Determine which call the transient session should be moved to. If the current 1894 // call session is still alive and the merge peer's session is not, we have a 1895 // situation where the current call failed to merge into the conference but the 1896 // merge peer did merge in to the conference. In this type of scenario the current 1897 // call will continue as a single party call, yet the background call will become 1898 // the conference. 1899 1900 // handles Case 3 explained in callSessionMergeComplete 1901 if (isSessionAlive(mSession) && !isSessionAlive(mMergePeer.getCallSession())) { 1902 // I'm the host but we are moving the transient session to the peer since its 1903 // session was disconnected and my session is still alive. This signifies that 1904 // their session was properly added to the conference but mine was not because 1905 // it is probably in the held state as opposed to part of the final conference. 1906 // In this case, we need to set isMerged to false on both calls so the 1907 // disconnect sound is called when either call disconnects. 1908 // Note that this case is only valid if this is an initial conference being 1909 // brought up. 1910 mMergePeer.mHold = false; 1911 this.mHold = true; 1912 if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) { 1913 mMergePeer.mConferenceParticipants = mConferenceParticipants; 1914 } 1915 // At this point both host & peer will have participant information. 1916 // Peer will transition to host & the participant information 1917 // from that will be used 1918 // HostCall that failed to merge will remain as a single call with 1919 // mConferenceParticipants, which should not be used. 1920 // Expectation is that if this call becomes part of a conference call in future, 1921 // mConferenceParticipants will be overriten with new CEP that is received. 1922 finalHostCall = mMergePeer; 1923 finalPeerCall = this; 1924 swapRequired = true; 1925 setIsMerged(false); 1926 mMergePeer.setIsMerged(false); 1927 if (CONF_DBG) { 1928 logi("processMergeComplete :: transient will transfer to merge peer"); 1929 } 1930 } else if (!isSessionAlive(mSession) && 1931 isSessionAlive(mMergePeer.getCallSession())) { 1932 // Handles case 2 explained in callSessionMergeComplete 1933 // The transient session stays with us and the disconnect sound should be played 1934 // when the merge peer eventually disconnects since it was not actually added to 1935 // the conference and is probably sitting in the held state. 1936 finalHostCall = this; 1937 finalPeerCall = mMergePeer; 1938 swapRequired = false; 1939 setIsMerged(false); 1940 mMergePeer.setIsMerged(false); // Play the disconnect sound 1941 if (CONF_DBG) { 1942 logi("processMergeComplete :: transient will stay with the merge host"); 1943 } 1944 } else { 1945 // Handles case 1 explained in callSessionMergeComplete 1946 // The transient session stays with us and the disconnect sound should not be 1947 // played when we ripple up the disconnect for the merge peer because it was 1948 // only disconnected to be added to the conference. 1949 finalHostCall = this; 1950 finalPeerCall = mMergePeer; 1951 mMergePeer.markCallAsMerged(false); 1952 swapRequired = false; 1953 setIsMerged(false); 1954 mMergePeer.setIsMerged(true); 1955 if (CONF_DBG) { 1956 logi("processMergeComplete :: transient will stay with us (I'm the host)."); 1957 } 1958 } 1959 1960 if (CONF_DBG) { 1961 logi("processMergeComplete :: call=" + finalHostCall + " is the final host"); 1962 } 1963 1964 // Add the transient session to the ImsCall that ended up being the host for the 1965 // conference. 1966 finalHostCall.setTransientSessionAsPrimary(transientConferenceSession); 1967 } 1968 1969 listener = finalHostCall.mListener; 1970 1971 updateCallProfile(finalPeerCall); 1972 updateCallProfile(finalHostCall); 1973 1974 // Clear all the merge related flags. 1975 clearMergeInfo(); 1976 1977 // For the final peer...let's bubble up any possible disconnects that we had 1978 // during the merge process 1979 finalPeerCall.notifySessionTerminatedDuringMerge(); 1980 // For the final host, let's just bury the disconnects that we my have received 1981 // during the merge process since we are now the host of the conference call. 1982 finalHostCall.clearSessionTerminationFlags(); 1983 1984 // Keep track of the fact that merge host is the origin of a conference call in 1985 // progress. This is important so that we can later determine if a multiparty ImsCall 1986 // is multiparty because it was the origin of a conference call, or because it is a 1987 // member of a conference on another device. 1988 finalHostCall.mIsConferenceHost = true; 1989 } 1990 if (listener != null) { 1991 try { 1992 // finalPeerCall will have the participant that was not merged and 1993 // it will be held state 1994 // if peer was merged successfully, finalPeerCall will be null 1995 listener.onCallMerged(finalHostCall, finalPeerCall, swapRequired); 1996 } catch (Throwable t) { 1997 loge("processMergeComplete :: ", t); 1998 } 1999 if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) { 2000 try { 2001 listener.onConferenceParticipantsStateChanged(finalHostCall, 2002 mConferenceParticipants); 2003 } catch (Throwable t) { 2004 loge("processMergeComplete :: ", t); 2005 } 2006 } 2007 } 2008 return; 2009 } 2010 2011 private static void updateCallProfile(ImsCall call) { 2012 if (call != null) { 2013 call.updateCallProfile(); 2014 } 2015 } 2016 2017 private void updateCallProfile() { 2018 synchronized (mLockObj) { 2019 if (mSession != null) { 2020 setCallProfile(mSession.getCallProfile()); 2021 } 2022 } 2023 } 2024 2025 /** 2026 * Handles the case where the session has ended during a merge by reporting the termination 2027 * reason to listeners. 2028 */ 2029 private void notifySessionTerminatedDuringMerge() { 2030 ImsCall.Listener listener; 2031 boolean notifyFailure = false; 2032 ImsReasonInfo notifyFailureReasonInfo = null; 2033 2034 synchronized(ImsCall.this) { 2035 listener = mListener; 2036 if (mSessionEndDuringMerge) { 2037 // Set some local variables that will send out a notification about a 2038 // previously buried termination callback for our primary session now that 2039 // we know that this is not due to the conference call merging successfully. 2040 if (CONF_DBG) { 2041 logi("notifySessionTerminatedDuringMerge ::reporting terminate during merge"); 2042 } 2043 notifyFailure = true; 2044 notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo; 2045 } 2046 clearSessionTerminationFlags(); 2047 } 2048 2049 if (listener != null && notifyFailure) { 2050 try { 2051 processCallTerminated(notifyFailureReasonInfo); 2052 } catch (Throwable t) { 2053 loge("notifySessionTerminatedDuringMerge :: ", t); 2054 } 2055 } 2056 } 2057 2058 private void clearSessionTerminationFlags() { 2059 mSessionEndDuringMerge = false; 2060 mSessionEndDuringMergeReasonInfo = null; 2061 } 2062 2063 /** 2064 * We received a callback from ImsCallSession that a merge failed. Clean up all 2065 * internal state to represent this state change. The calling function is a callback 2066 * and should have been called on the session that was in the foreground 2067 * when merge() was originally called. It is assumed that this function will be called 2068 * on the merge host. 2069 * 2070 * @param reasonInfo The {@link ImsReasonInfo} why the merge failed. 2071 */ 2072 private void processMergeFailed(ImsReasonInfo reasonInfo) { 2073 logi("processMergeFailed :: reason=" + reasonInfo); 2074 2075 ImsCall.Listener listener; 2076 synchronized(ImsCall.this) { 2077 // The logic simplifies if we can assume that this function is only called on 2078 // the merge host. 2079 if (!isMergeHost()) { 2080 loge("processMergeFailed :: We are not the merge host!"); 2081 return; 2082 } 2083 2084 // Try to clean up the transient session if it exists. 2085 if (mTransientConferenceSession != null) { 2086 mTransientConferenceSession.setListener(null); 2087 mTransientConferenceSession = null; 2088 } 2089 2090 listener = mListener; 2091 2092 // Ensure the calls being conferenced into the conference has isMerged = false. 2093 // Ensure any terminations are surfaced from this session. 2094 markCallAsMerged(true); 2095 setCallSessionMergePending(false); 2096 notifySessionTerminatedDuringMerge(); 2097 2098 // Perform the same cleanup on the merge peer if it exists. 2099 if (mMergePeer != null) { 2100 mMergePeer.markCallAsMerged(true); 2101 mMergePeer.setCallSessionMergePending(false); 2102 mMergePeer.notifySessionTerminatedDuringMerge(); 2103 } else { 2104 loge("processMergeFailed :: No merge peer!"); 2105 } 2106 2107 // Clear all the various flags around coordinating this merge. 2108 clearMergeInfo(); 2109 } 2110 if (listener != null) { 2111 try { 2112 listener.onCallMergeFailed(ImsCall.this, reasonInfo); 2113 } catch (Throwable t) { 2114 loge("processMergeFailed :: ", t); 2115 } 2116 } 2117 2118 return; 2119 } 2120 2121 @VisibleForTesting 2122 public class ImsCallSessionListenerProxy extends ImsCallSession.Listener { 2123 @Override 2124 public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) { 2125 logi("callSessionProgressing :: session=" + session + " profile=" + profile); 2126 2127 if (isTransientConferenceSession(session)) { 2128 // If it is a transient (conference) session, there is no action for this signal. 2129 logi("callSessionProgressing :: not supported for transient conference session=" + 2130 session); 2131 return; 2132 } 2133 2134 ImsCall.Listener listener; 2135 2136 synchronized(ImsCall.this) { 2137 listener = mListener; 2138 mCallProfile.mMediaProfile.copyFrom(profile); 2139 } 2140 2141 if (listener != null) { 2142 try { 2143 listener.onCallProgressing(ImsCall.this); 2144 } catch (Throwable t) { 2145 loge("callSessionProgressing :: ", t); 2146 } 2147 } 2148 } 2149 2150 @Override 2151 public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) { 2152 logi("callSessionStarted :: session=" + session + " profile=" + profile); 2153 2154 if (!isTransientConferenceSession(session)) { 2155 // In the case that we are in the middle of a merge (either host or peer), we have 2156 // closure as far as this call's primary session is concerned. If we are not 2157 // merging...its a NOOP. 2158 setCallSessionMergePending(false); 2159 } else { 2160 logi("callSessionStarted :: on transient session=" + session); 2161 return; 2162 } 2163 2164 if (isTransientConferenceSession(session)) { 2165 // No further processing is needed if this is the transient session. 2166 return; 2167 } 2168 2169 ImsCall.Listener listener; 2170 2171 synchronized(ImsCall.this) { 2172 listener = mListener; 2173 setCallProfile(profile); 2174 } 2175 2176 if (listener != null) { 2177 try { 2178 listener.onCallStarted(ImsCall.this); 2179 } catch (Throwable t) { 2180 loge("callSessionStarted :: ", t); 2181 } 2182 } 2183 } 2184 2185 @Override 2186 public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2187 loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo); 2188 2189 if (isTransientConferenceSession(session)) { 2190 // We should not get this callback for a transient session. 2191 logi("callSessionStartFailed :: not supported for transient conference session=" + 2192 session); 2193 return; 2194 } 2195 2196 ImsCall.Listener listener; 2197 2198 synchronized(ImsCall.this) { 2199 listener = mListener; 2200 mLastReasonInfo = reasonInfo; 2201 } 2202 2203 if (listener != null) { 2204 try { 2205 listener.onCallStartFailed(ImsCall.this, reasonInfo); 2206 } catch (Throwable t) { 2207 loge("callSessionStarted :: ", t); 2208 } 2209 } 2210 } 2211 2212 @Override 2213 public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) { 2214 logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo); 2215 2216 if (isTransientConferenceSession(session)) { 2217 logi("callSessionTerminated :: on transient session=" + session); 2218 // This is bad, it should be treated much a callSessionMergeFailed since the 2219 // transient session only exists when in the process of a merge and the 2220 // termination of this session is effectively the end of the merge. 2221 processMergeFailed(reasonInfo); 2222 return; 2223 } 2224 2225 if (mOverrideReason != ImsReasonInfo.CODE_UNSPECIFIED) { 2226 logi("callSessionTerminated :: overrideReasonInfo=" + mOverrideReason); 2227 reasonInfo = new ImsReasonInfo(mOverrideReason, reasonInfo.getExtraCode(), 2228 reasonInfo.getExtraMessage()); 2229 } 2230 2231 // Process the termination first. If we are in the midst of establishing a conference 2232 // call, we may bury this callback until we are done. If there so no conference 2233 // call, the code after this function will be a NOOP. 2234 processCallTerminated(reasonInfo); 2235 2236 // If session has terminated, it is no longer pending merge. 2237 setCallSessionMergePending(false); 2238 2239 } 2240 2241 @Override 2242 public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) { 2243 logi("callSessionHeld :: session=" + session + "profile=" + profile); 2244 ImsCall.Listener listener; 2245 2246 synchronized(ImsCall.this) { 2247 // If the session was held, it is no longer pending a merge -- this means it could 2248 // not be merged into the conference and was held instead. 2249 setCallSessionMergePending(false); 2250 2251 setCallProfile(profile); 2252 2253 if (mUpdateRequest == UPDATE_HOLD_MERGE) { 2254 // This hold request was made to set the stage for a merge. 2255 mergeInternal(); 2256 return; 2257 } 2258 2259 mUpdateRequest = UPDATE_NONE; 2260 listener = mListener; 2261 } 2262 2263 if (listener != null) { 2264 try { 2265 listener.onCallHeld(ImsCall.this); 2266 } catch (Throwable t) { 2267 loge("callSessionHeld :: ", t); 2268 } 2269 } 2270 } 2271 2272 @Override 2273 public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2274 loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo); 2275 2276 if (isTransientConferenceSession(session)) { 2277 // We should not get this callback for a transient session. 2278 logi("callSessionHoldFailed :: not supported for transient conference session=" + 2279 session); 2280 return; 2281 } 2282 2283 logi("callSessionHoldFailed :: session=" + session + 2284 ", reasonInfo=" + reasonInfo); 2285 2286 synchronized (mLockObj) { 2287 mHold = false; 2288 } 2289 2290 boolean isHoldForMerge = false; 2291 ImsCall.Listener listener; 2292 2293 synchronized(ImsCall.this) { 2294 if (mUpdateRequest == UPDATE_HOLD_MERGE) { 2295 isHoldForMerge = true; 2296 } 2297 2298 mUpdateRequest = UPDATE_NONE; 2299 listener = mListener; 2300 } 2301 2302 if (listener != null) { 2303 try { 2304 listener.onCallHoldFailed(ImsCall.this, reasonInfo); 2305 } catch (Throwable t) { 2306 loge("callSessionHoldFailed :: ", t); 2307 } 2308 } 2309 } 2310 2311 @Override 2312 public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) { 2313 logi("callSessionHoldReceived :: session=" + session + "profile=" + profile); 2314 2315 if (isTransientConferenceSession(session)) { 2316 // We should not get this callback for a transient session. 2317 logi("callSessionHoldReceived :: not supported for transient conference session=" + 2318 session); 2319 return; 2320 } 2321 2322 ImsCall.Listener listener; 2323 2324 synchronized(ImsCall.this) { 2325 listener = mListener; 2326 setCallProfile(profile); 2327 } 2328 2329 if (listener != null) { 2330 try { 2331 listener.onCallHoldReceived(ImsCall.this); 2332 } catch (Throwable t) { 2333 loge("callSessionHoldReceived :: ", t); 2334 } 2335 } 2336 } 2337 2338 @Override 2339 public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) { 2340 logi("callSessionResumed :: session=" + session + "profile=" + profile); 2341 2342 if (isTransientConferenceSession(session)) { 2343 logi("callSessionResumed :: not supported for transient conference session=" + 2344 session); 2345 return; 2346 } 2347 2348 // If this call was pending a merge, it is not anymore. This is the case when we 2349 // are merging in a new call into an existing conference. 2350 setCallSessionMergePending(false); 2351 2352 // TOOD: When we are merging a new call into an existing conference we are waiting 2353 // for 2 triggers to let us know that the conference has been established, the first 2354 // is a termination for the new calls (since it is added to the conference) the second 2355 // would be a resume on the existing conference. If the resume comes first, then 2356 // we will make the onCallResumed() callback and its unclear how this will behave if 2357 // the termination has not come yet. 2358 2359 ImsCall.Listener listener; 2360 synchronized(ImsCall.this) { 2361 listener = mListener; 2362 setCallProfile(profile); 2363 mUpdateRequest = UPDATE_NONE; 2364 mHold = false; 2365 } 2366 2367 if (listener != null) { 2368 try { 2369 listener.onCallResumed(ImsCall.this); 2370 } catch (Throwable t) { 2371 loge("callSessionResumed :: ", t); 2372 } 2373 } 2374 } 2375 2376 @Override 2377 public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2378 loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo); 2379 2380 if (isTransientConferenceSession(session)) { 2381 logi("callSessionResumeFailed :: not supported for transient conference session=" + 2382 session); 2383 return; 2384 } 2385 2386 synchronized(mLockObj) { 2387 mHold = true; 2388 } 2389 2390 ImsCall.Listener listener; 2391 2392 synchronized(ImsCall.this) { 2393 listener = mListener; 2394 mUpdateRequest = UPDATE_NONE; 2395 } 2396 2397 if (listener != null) { 2398 try { 2399 listener.onCallResumeFailed(ImsCall.this, reasonInfo); 2400 } catch (Throwable t) { 2401 loge("callSessionResumeFailed :: ", t); 2402 } 2403 } 2404 } 2405 2406 @Override 2407 public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) { 2408 logi("callSessionResumeReceived :: session=" + session + "profile=" + profile); 2409 2410 if (isTransientConferenceSession(session)) { 2411 logi("callSessionResumeReceived :: not supported for transient conference session=" + 2412 session); 2413 return; 2414 } 2415 2416 ImsCall.Listener listener; 2417 2418 synchronized(ImsCall.this) { 2419 listener = mListener; 2420 setCallProfile(profile); 2421 } 2422 2423 if (listener != null) { 2424 try { 2425 listener.onCallResumeReceived(ImsCall.this); 2426 } catch (Throwable t) { 2427 loge("callSessionResumeReceived :: ", t); 2428 } 2429 } 2430 } 2431 2432 @Override 2433 public void callSessionMergeStarted(ImsCallSession session, 2434 ImsCallSession newSession, ImsCallProfile profile) { 2435 logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession + 2436 ", profile=" + profile); 2437 2438 return; 2439 } 2440 2441 /* 2442 * This method check if session exists as a session on the current 2443 * ImsCall or its counterpart if it is in the process of a conference 2444 */ 2445 private boolean doesCallSessionExistsInMerge(ImsCallSession cs) { 2446 String callId = cs.getCallId(); 2447 return ((isMergeHost() && Objects.equals(mMergePeer.mSession.getCallId(), callId)) || 2448 (isMergePeer() && Objects.equals(mMergeHost.mSession.getCallId(), callId)) || 2449 Objects.equals(mSession.getCallId(), callId)); 2450 } 2451 2452 /** 2453 * We received a callback from ImsCallSession that merge completed. 2454 * @param newSession - this session can have 2 values based on the below scenarios 2455 * 2456 * Conference Scenarios : 2457 * Case 1 - 3 way success case 2458 * Case 2 - 3 way success case but held call fails to merge 2459 * Case 3 - 3 way success case but active call fails to merge 2460 * case 4 - 4 way success case, where merge is initiated on the foreground single-party 2461 * call and the conference (mergeHost) is the background call. 2462 * case 5 - 4 way success case, where merge is initiated on the foreground conference 2463 * call (mergeHost) and the single party call is in the background. 2464 * 2465 * Conference Result: 2466 * session : new session after conference 2467 * newSession = new session for case 1, 2, 3. 2468 * Should be considered as mTransientConferencession 2469 * newSession = Active conference session for case 5 will be null 2470 * mergehost was foreground call 2471 * mTransientConferencession will be null 2472 * newSession = Active conference session for case 4 will be null 2473 * mergeHost was background call 2474 * mTransientConferencession will be null 2475 */ 2476 @Override 2477 public void callSessionMergeComplete(ImsCallSession newSession) { 2478 logi("callSessionMergeComplete :: newSession =" + newSession); 2479 if (!isMergeHost()) { 2480 // Handles case 4 2481 mMergeHost.processMergeComplete(); 2482 } else { 2483 // Handles case 1, 2, 3 2484 if (newSession != null) { 2485 mTransientConferenceSession = doesCallSessionExistsInMerge(newSession) ? 2486 null: newSession; 2487 } 2488 // Handles case 5 2489 processMergeComplete(); 2490 } 2491 } 2492 2493 @Override 2494 public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2495 loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo); 2496 2497 // Its possible that there could be threading issues with the other thread handling 2498 // the other call. This could affect our state. 2499 synchronized (ImsCall.this) { 2500 // Let's tell our parent ImsCall that the merge has failed and we need to clean 2501 // up any temporary, transient state. Note this only gets called for an initial 2502 // conference. If a merge into an existing conference fails, the two sessions will 2503 // just go back to their original state (ACTIVE or HELD). 2504 if (isMergeHost()) { 2505 processMergeFailed(reasonInfo); 2506 } else if (mMergeHost != null) { 2507 mMergeHost.processMergeFailed(reasonInfo); 2508 } else { 2509 loge("callSessionMergeFailed :: No merge host for this conference!"); 2510 } 2511 } 2512 } 2513 2514 @Override 2515 public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) { 2516 logi("callSessionUpdated :: session=" + session + " profile=" + profile); 2517 2518 if (isTransientConferenceSession(session)) { 2519 logi("callSessionUpdated :: not supported for transient conference session=" + 2520 session); 2521 return; 2522 } 2523 2524 ImsCall.Listener listener; 2525 2526 synchronized(ImsCall.this) { 2527 listener = mListener; 2528 setCallProfile(profile); 2529 } 2530 2531 if (listener != null) { 2532 try { 2533 listener.onCallUpdated(ImsCall.this); 2534 } catch (Throwable t) { 2535 loge("callSessionUpdated :: ", t); 2536 } 2537 } 2538 } 2539 2540 @Override 2541 public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2542 loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo); 2543 2544 if (isTransientConferenceSession(session)) { 2545 logi("callSessionUpdateFailed :: not supported for transient conference session=" + 2546 session); 2547 return; 2548 } 2549 2550 ImsCall.Listener listener; 2551 2552 synchronized(ImsCall.this) { 2553 listener = mListener; 2554 mUpdateRequest = UPDATE_NONE; 2555 } 2556 2557 if (listener != null) { 2558 try { 2559 listener.onCallUpdateFailed(ImsCall.this, reasonInfo); 2560 } catch (Throwable t) { 2561 loge("callSessionUpdateFailed :: ", t); 2562 } 2563 } 2564 } 2565 2566 @Override 2567 public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) { 2568 logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile); 2569 2570 if (isTransientConferenceSession(session)) { 2571 logi("callSessionUpdateReceived :: not supported for transient conference " + 2572 "session=" + session); 2573 return; 2574 } 2575 2576 ImsCall.Listener listener; 2577 2578 synchronized(ImsCall.this) { 2579 listener = mListener; 2580 mProposedCallProfile = profile; 2581 mUpdateRequest = UPDATE_UNSPECIFIED; 2582 } 2583 2584 if (listener != null) { 2585 try { 2586 listener.onCallUpdateReceived(ImsCall.this); 2587 } catch (Throwable t) { 2588 loge("callSessionUpdateReceived :: ", t); 2589 } 2590 } 2591 } 2592 2593 @Override 2594 public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession, 2595 ImsCallProfile profile) { 2596 logi("callSessionConferenceExtended :: session=" + session + " newSession=" + 2597 newSession + ", profile=" + profile); 2598 2599 if (isTransientConferenceSession(session)) { 2600 logi("callSessionConferenceExtended :: not supported for transient conference " + 2601 "session=" + session); 2602 return; 2603 } 2604 2605 ImsCall newCall = createNewCall(newSession, profile); 2606 2607 if (newCall == null) { 2608 callSessionConferenceExtendFailed(session, new ImsReasonInfo()); 2609 return; 2610 } 2611 2612 ImsCall.Listener listener; 2613 2614 synchronized(ImsCall.this) { 2615 listener = mListener; 2616 mUpdateRequest = UPDATE_NONE; 2617 } 2618 2619 if (listener != null) { 2620 try { 2621 listener.onCallConferenceExtended(ImsCall.this, newCall); 2622 } catch (Throwable t) { 2623 loge("callSessionConferenceExtended :: ", t); 2624 } 2625 } 2626 } 2627 2628 @Override 2629 public void callSessionConferenceExtendFailed(ImsCallSession session, 2630 ImsReasonInfo reasonInfo) { 2631 loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo); 2632 2633 if (isTransientConferenceSession(session)) { 2634 logi("callSessionConferenceExtendFailed :: not supported for transient " + 2635 "conference session=" + session); 2636 return; 2637 } 2638 2639 ImsCall.Listener listener; 2640 2641 synchronized(ImsCall.this) { 2642 listener = mListener; 2643 mUpdateRequest = UPDATE_NONE; 2644 } 2645 2646 if (listener != null) { 2647 try { 2648 listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo); 2649 } catch (Throwable t) { 2650 loge("callSessionConferenceExtendFailed :: ", t); 2651 } 2652 } 2653 } 2654 2655 @Override 2656 public void callSessionConferenceExtendReceived(ImsCallSession session, 2657 ImsCallSession newSession, ImsCallProfile profile) { 2658 logi("callSessionConferenceExtendReceived :: newSession=" + newSession + 2659 ", profile=" + profile); 2660 2661 if (isTransientConferenceSession(session)) { 2662 logi("callSessionConferenceExtendReceived :: not supported for transient " + 2663 "conference session" + session); 2664 return; 2665 } 2666 2667 ImsCall newCall = createNewCall(newSession, profile); 2668 2669 if (newCall == null) { 2670 // Should all the calls be terminated...??? 2671 return; 2672 } 2673 2674 ImsCall.Listener listener; 2675 2676 synchronized(ImsCall.this) { 2677 listener = mListener; 2678 } 2679 2680 if (listener != null) { 2681 try { 2682 listener.onCallConferenceExtendReceived(ImsCall.this, newCall); 2683 } catch (Throwable t) { 2684 loge("callSessionConferenceExtendReceived :: ", t); 2685 } 2686 } 2687 } 2688 2689 @Override 2690 public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) { 2691 logi("callSessionInviteParticipantsRequestDelivered ::"); 2692 2693 if (isTransientConferenceSession(session)) { 2694 logi("callSessionInviteParticipantsRequestDelivered :: not supported for " + 2695 "conference session=" + session); 2696 return; 2697 } 2698 2699 ImsCall.Listener listener; 2700 2701 synchronized(ImsCall.this) { 2702 listener = mListener; 2703 } 2704 2705 if (listener != null) { 2706 try { 2707 listener.onCallInviteParticipantsRequestDelivered(ImsCall.this); 2708 } catch (Throwable t) { 2709 loge("callSessionInviteParticipantsRequestDelivered :: ", t); 2710 } 2711 } 2712 } 2713 2714 @Override 2715 public void callSessionInviteParticipantsRequestFailed(ImsCallSession session, 2716 ImsReasonInfo reasonInfo) { 2717 loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo); 2718 2719 if (isTransientConferenceSession(session)) { 2720 logi("callSessionInviteParticipantsRequestFailed :: not supported for " + 2721 "conference session=" + session); 2722 return; 2723 } 2724 2725 ImsCall.Listener listener; 2726 2727 synchronized(ImsCall.this) { 2728 listener = mListener; 2729 } 2730 2731 if (listener != null) { 2732 try { 2733 listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo); 2734 } catch (Throwable t) { 2735 loge("callSessionInviteParticipantsRequestFailed :: ", t); 2736 } 2737 } 2738 } 2739 2740 @Override 2741 public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) { 2742 logi("callSessionRemoveParticipantsRequestDelivered ::"); 2743 2744 if (isTransientConferenceSession(session)) { 2745 logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " + 2746 "conference session=" + session); 2747 return; 2748 } 2749 2750 ImsCall.Listener listener; 2751 2752 synchronized(ImsCall.this) { 2753 listener = mListener; 2754 } 2755 2756 if (listener != null) { 2757 try { 2758 listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this); 2759 } catch (Throwable t) { 2760 loge("callSessionRemoveParticipantsRequestDelivered :: ", t); 2761 } 2762 } 2763 } 2764 2765 @Override 2766 public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session, 2767 ImsReasonInfo reasonInfo) { 2768 loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo); 2769 2770 if (isTransientConferenceSession(session)) { 2771 logi("callSessionRemoveParticipantsRequestFailed :: not supported for " + 2772 "conference session=" + session); 2773 return; 2774 } 2775 2776 ImsCall.Listener listener; 2777 2778 synchronized(ImsCall.this) { 2779 listener = mListener; 2780 } 2781 2782 if (listener != null) { 2783 try { 2784 listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo); 2785 } catch (Throwable t) { 2786 loge("callSessionRemoveParticipantsRequestFailed :: ", t); 2787 } 2788 } 2789 } 2790 2791 @Override 2792 public void callSessionConferenceStateUpdated(ImsCallSession session, 2793 ImsConferenceState state) { 2794 logi("callSessionConferenceStateUpdated :: state=" + state); 2795 2796 conferenceStateUpdated(state); 2797 } 2798 2799 @Override 2800 public void callSessionUssdMessageReceived(ImsCallSession session, int mode, 2801 String ussdMessage) { 2802 logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" + 2803 ussdMessage); 2804 2805 if (isTransientConferenceSession(session)) { 2806 logi("callSessionUssdMessageReceived :: not supported for transient " + 2807 "conference session=" + session); 2808 return; 2809 } 2810 2811 ImsCall.Listener listener; 2812 2813 synchronized(ImsCall.this) { 2814 listener = mListener; 2815 } 2816 2817 if (listener != null) { 2818 try { 2819 listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage); 2820 } catch (Throwable t) { 2821 loge("callSessionUssdMessageReceived :: ", t); 2822 } 2823 } 2824 } 2825 2826 @Override 2827 public void callSessionTtyModeReceived(ImsCallSession session, int mode) { 2828 logi("callSessionTtyModeReceived :: mode=" + mode); 2829 2830 ImsCall.Listener listener; 2831 2832 synchronized(ImsCall.this) { 2833 listener = mListener; 2834 } 2835 2836 if (listener != null) { 2837 try { 2838 listener.onCallSessionTtyModeReceived(ImsCall.this, mode); 2839 } catch (Throwable t) { 2840 loge("callSessionTtyModeReceived :: ", t); 2841 } 2842 } 2843 } 2844 2845 /** 2846 * Notifies of a change to the multiparty state for this {@code ImsCallSession}. 2847 * 2848 * @param session The call session. 2849 * @param isMultiParty {@code true} if the session became multiparty, {@code false} 2850 * otherwise. 2851 */ 2852 @Override 2853 public void callSessionMultipartyStateChanged(ImsCallSession session, 2854 boolean isMultiParty) { 2855 if (VDBG) { 2856 logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y" 2857 : "N")); 2858 } 2859 2860 ImsCall.Listener listener; 2861 2862 synchronized(ImsCall.this) { 2863 listener = mListener; 2864 } 2865 2866 if (listener != null) { 2867 try { 2868 listener.onMultipartyStateChanged(ImsCall.this, isMultiParty); 2869 } catch (Throwable t) { 2870 loge("callSessionMultipartyStateChanged :: ", t); 2871 } 2872 } 2873 } 2874 2875 public void callSessionHandover(ImsCallSession session, int srcAccessTech, 2876 int targetAccessTech, ImsReasonInfo reasonInfo) { 2877 logi("callSessionHandover :: session=" + session + ", srcAccessTech=" + 2878 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + 2879 reasonInfo); 2880 2881 ImsCall.Listener listener; 2882 2883 synchronized(ImsCall.this) { 2884 listener = mListener; 2885 } 2886 2887 if (listener != null) { 2888 try { 2889 listener.onCallHandover(ImsCall.this, srcAccessTech, targetAccessTech, 2890 reasonInfo); 2891 } catch (Throwable t) { 2892 loge("callSessionHandover :: ", t); 2893 } 2894 } 2895 } 2896 2897 @Override 2898 public void callSessionHandoverFailed(ImsCallSession session, int srcAccessTech, 2899 int targetAccessTech, ImsReasonInfo reasonInfo) { 2900 loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" + 2901 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + 2902 reasonInfo); 2903 2904 ImsCall.Listener listener; 2905 2906 synchronized(ImsCall.this) { 2907 listener = mListener; 2908 } 2909 2910 if (listener != null) { 2911 try { 2912 listener.onCallHandoverFailed(ImsCall.this, srcAccessTech, targetAccessTech, 2913 reasonInfo); 2914 } catch (Throwable t) { 2915 loge("callSessionHandoverFailed :: ", t); 2916 } 2917 } 2918 } 2919 2920 @Override 2921 public void callSessionSuppServiceReceived(ImsCallSession session, 2922 ImsSuppServiceNotification suppServiceInfo ) { 2923 if (isTransientConferenceSession(session)) { 2924 logi("callSessionSuppServiceReceived :: not supported for transient conference" 2925 + " session=" + session); 2926 return; 2927 } 2928 2929 logi("callSessionSuppServiceReceived :: session=" + session + 2930 ", suppServiceInfo" + suppServiceInfo); 2931 2932 ImsCall.Listener listener; 2933 2934 synchronized(ImsCall.this) { 2935 listener = mListener; 2936 } 2937 2938 if (listener != null) { 2939 try { 2940 listener.onCallSuppServiceReceived(ImsCall.this, suppServiceInfo); 2941 } catch (Throwable t) { 2942 loge("callSessionSuppServiceReceived :: ", t); 2943 } 2944 } 2945 } 2946 } 2947 2948 /** 2949 * Report a new conference state to the current {@link ImsCall} and inform listeners of the 2950 * change. Marked as {@code VisibleForTesting} so that the 2951 * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference 2952 * event package into a regular ongoing IMS call. 2953 * 2954 * @param state The {@link ImsConferenceState}. 2955 */ 2956 @VisibleForTesting 2957 public void conferenceStateUpdated(ImsConferenceState state) { 2958 Listener listener; 2959 2960 synchronized(this) { 2961 notifyConferenceStateUpdated(state); 2962 listener = mListener; 2963 } 2964 2965 if (listener != null) { 2966 try { 2967 listener.onCallConferenceStateUpdated(this, state); 2968 } catch (Throwable t) { 2969 loge("callSessionConferenceStateUpdated :: ", t); 2970 } 2971 } 2972 } 2973 2974 /** 2975 * Provides a human-readable string representation of an update request. 2976 * 2977 * @param updateRequest The update request. 2978 * @return The string representation. 2979 */ 2980 private String updateRequestToString(int updateRequest) { 2981 switch (updateRequest) { 2982 case UPDATE_NONE: 2983 return "NONE"; 2984 case UPDATE_HOLD: 2985 return "HOLD"; 2986 case UPDATE_HOLD_MERGE: 2987 return "HOLD_MERGE"; 2988 case UPDATE_RESUME: 2989 return "RESUME"; 2990 case UPDATE_MERGE: 2991 return "MERGE"; 2992 case UPDATE_EXTEND_TO_CONFERENCE: 2993 return "EXTEND_TO_CONFERENCE"; 2994 case UPDATE_UNSPECIFIED: 2995 return "UNSPECIFIED"; 2996 default: 2997 return "UNKNOWN"; 2998 } 2999 } 3000 3001 /** 3002 * Clears the merge peer for this call, ensuring that the peer's connection to this call is also 3003 * severed at the same time. 3004 */ 3005 private void clearMergeInfo() { 3006 if (CONF_DBG) { 3007 logi("clearMergeInfo :: clearing all merge info"); 3008 } 3009 3010 // First clear out the merge partner then clear ourselves out. 3011 if (mMergeHost != null) { 3012 mMergeHost.mMergePeer = null; 3013 mMergeHost.mUpdateRequest = UPDATE_NONE; 3014 mMergeHost.mCallSessionMergePending = false; 3015 } 3016 if (mMergePeer != null) { 3017 mMergePeer.mMergeHost = null; 3018 mMergePeer.mUpdateRequest = UPDATE_NONE; 3019 mMergePeer.mCallSessionMergePending = false; 3020 } 3021 mMergeHost = null; 3022 mMergePeer = null; 3023 mUpdateRequest = UPDATE_NONE; 3024 mCallSessionMergePending = false; 3025 } 3026 3027 /** 3028 * Sets the merge peer for the current call. The merge peer is the background call that will be 3029 * merged into this call. On the merge peer, sets the merge host to be this call. 3030 * 3031 * @param mergePeer The peer call to be merged into this one. 3032 */ 3033 private void setMergePeer(ImsCall mergePeer) { 3034 mMergePeer = mergePeer; 3035 mMergeHost = null; 3036 3037 mergePeer.mMergeHost = ImsCall.this; 3038 mergePeer.mMergePeer = null; 3039 } 3040 3041 /** 3042 * Sets the merge hody for the current call. The merge host is the foreground call this call 3043 * will be merged into. On the merge host, sets the merge peer to be this call. 3044 * 3045 * @param mergeHost The merge host this call will be merged into. 3046 */ 3047 public void setMergeHost(ImsCall mergeHost) { 3048 mMergeHost = mergeHost; 3049 mMergePeer = null; 3050 3051 mergeHost.mMergeHost = null; 3052 mergeHost.mMergePeer = ImsCall.this; 3053 } 3054 3055 /** 3056 * Determines if the current call is in the process of merging with another call or conference. 3057 * 3058 * @return {@code true} if in the process of merging. 3059 */ 3060 private boolean isMerging() { 3061 return mMergePeer != null || mMergeHost != null; 3062 } 3063 3064 /** 3065 * Determines if the current call is the host of the merge. 3066 * 3067 * @return {@code true} if the call is the merge host. 3068 */ 3069 private boolean isMergeHost() { 3070 return mMergePeer != null && mMergeHost == null; 3071 } 3072 3073 /** 3074 * Determines if the current call is the peer of the merge. 3075 * 3076 * @return {@code true} if the call is the merge peer. 3077 */ 3078 private boolean isMergePeer() { 3079 return mMergePeer == null && mMergeHost != null; 3080 } 3081 3082 /** 3083 * Determines if the call session is pending merge into a conference or not. 3084 * 3085 * @return {@code true} if a merge into a conference is pending, {@code false} otherwise. 3086 */ 3087 private boolean isCallSessionMergePending() { 3088 return mCallSessionMergePending; 3089 } 3090 3091 /** 3092 * Sets flag indicating whether the call session is pending merge into a conference or not. 3093 * 3094 * @param callSessionMergePending {@code true} if a merge into the conference is pending, 3095 * {@code false} otherwise. 3096 */ 3097 private void setCallSessionMergePending(boolean callSessionMergePending) { 3098 mCallSessionMergePending = callSessionMergePending; 3099 } 3100 3101 /** 3102 * Determines if there is a conference merge in process. If there is a merge in process, 3103 * determines if both the merge host and peer sessions have completed the merge process. This 3104 * means that we have received terminate or hold signals for the sessions, indicating that they 3105 * are no longer in the process of being merged into the conference. 3106 * <p> 3107 * The sessions are considered to have merged if: both calls still have merge peer/host 3108 * relationships configured, both sessions are not waiting to be merged into the conference, 3109 * and the transient conference session is alive in the case of an initial conference. 3110 * 3111 * @return {@code true} where the host and peer sessions have finished merging into the 3112 * conference, {@code false} if the merge has not yet completed, and {@code false} if there 3113 * is no conference merge in progress. 3114 */ 3115 private boolean shouldProcessConferenceResult() { 3116 boolean areMergeTriggersDone = false; 3117 3118 synchronized (ImsCall.this) { 3119 // if there is a merge going on, then the merge host/peer relationships should have been 3120 // set up. This works for both the initial conference or merging a call into an 3121 // existing conference. 3122 if (!isMergeHost() && !isMergePeer()) { 3123 if (CONF_DBG) { 3124 loge("shouldProcessConferenceResult :: no merge in progress"); 3125 } 3126 return false; 3127 } 3128 3129 // There is a merge in progress, so check the sessions to ensure: 3130 // 1. Both calls have completed being merged (or failing to merge) into the conference. 3131 // 2. The transient conference session is alive. 3132 if (isMergeHost()) { 3133 if (CONF_DBG) { 3134 logi("shouldProcessConferenceResult :: We are a merge host"); 3135 logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer); 3136 } 3137 areMergeTriggersDone = !isCallSessionMergePending() && 3138 !mMergePeer.isCallSessionMergePending(); 3139 if (!isMultiparty()) { 3140 // Only check the transient session when there is no existing conference 3141 areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession); 3142 } 3143 } else if (isMergePeer()) { 3144 if (CONF_DBG) { 3145 logi("shouldProcessConferenceResult :: We are a merge peer"); 3146 logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost); 3147 } 3148 areMergeTriggersDone = !isCallSessionMergePending() && 3149 !mMergeHost.isCallSessionMergePending(); 3150 if (!mMergeHost.isMultiparty()) { 3151 // Only check the transient session when there is no existing conference 3152 areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession); 3153 } else { 3154 // This else block is a special case for Verizon to handle these steps 3155 // 1. Establish a conference call. 3156 // 2. Add a new call (conference in in BG) 3157 // 3. Swap (conference active on FG) 3158 // 4. Merge 3159 // What happens here is that the BG call gets a terminated callback 3160 // because it was added to the conference. I've seen where 3161 // the FG gets no callback at all because its already active. 3162 // So if we continue to wait for it to set its isCallSessionMerging 3163 // flag to false...we'll be waiting forever. 3164 areMergeTriggersDone = !isCallSessionMergePending(); 3165 } 3166 } else { 3167 // Realistically this shouldn't happen, but best to be safe. 3168 loge("shouldProcessConferenceResult : merge in progress but call is neither" + 3169 " host nor peer."); 3170 } 3171 if (CONF_DBG) { 3172 logi("shouldProcessConferenceResult :: returning:" + 3173 (areMergeTriggersDone ? "true" : "false")); 3174 } 3175 } 3176 return areMergeTriggersDone; 3177 } 3178 3179 /** 3180 * Provides a string representation of the {@link ImsCall}. Primarily intended for use in log 3181 * statements. 3182 * 3183 * @return String representation of call. 3184 */ 3185 @Override 3186 public String toString() { 3187 StringBuilder sb = new StringBuilder(); 3188 sb.append("[ImsCall objId:"); 3189 sb.append(System.identityHashCode(this)); 3190 sb.append(" onHold:"); 3191 sb.append(isOnHold() ? "Y" : "N"); 3192 sb.append(" mute:"); 3193 sb.append(isMuted() ? "Y" : "N"); 3194 if (mCallProfile != null) { 3195 sb.append(" tech:"); 3196 sb.append(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE)); 3197 } 3198 sb.append(" updateRequest:"); 3199 sb.append(updateRequestToString(mUpdateRequest)); 3200 sb.append(" merging:"); 3201 sb.append(isMerging() ? "Y" : "N"); 3202 if (isMerging()) { 3203 if (isMergePeer()) { 3204 sb.append("P"); 3205 } else { 3206 sb.append("H"); 3207 } 3208 } 3209 sb.append(" merge action pending:"); 3210 sb.append(isCallSessionMergePending() ? "Y" : "N"); 3211 sb.append(" merged:"); 3212 sb.append(isMerged() ? "Y" : "N"); 3213 sb.append(" multiParty:"); 3214 sb.append(isMultiparty() ? "Y" : "N"); 3215 sb.append(" confHost:"); 3216 sb.append(isConferenceHost() ? "Y" : "N"); 3217 sb.append(" buried term:"); 3218 sb.append(mSessionEndDuringMerge ? "Y" : "N"); 3219 sb.append(" wasVideo: "); 3220 sb.append(mWasVideoCall ? "Y" : "N"); 3221 sb.append(" session:"); 3222 sb.append(mSession); 3223 sb.append(" transientSession:"); 3224 sb.append(mTransientConferenceSession); 3225 sb.append("]"); 3226 return sb.toString(); 3227 } 3228 3229 private void throwImsException(Throwable t, int code) throws ImsException { 3230 if (t instanceof ImsException) { 3231 throw (ImsException) t; 3232 } else { 3233 throw new ImsException(String.valueOf(code), t, code); 3234 } 3235 } 3236 3237 /** 3238 * Append the ImsCall information to the provided string. Usefull for as a logging helper. 3239 * @param s The original string 3240 * @return The original string with {@code ImsCall} information appended to it. 3241 */ 3242 private String appendImsCallInfoToString(String s) { 3243 StringBuilder sb = new StringBuilder(); 3244 sb.append(s); 3245 sb.append(" ImsCall="); 3246 sb.append(ImsCall.this); 3247 return sb.toString(); 3248 } 3249 3250 /** 3251 * Updates {@link #mWasVideoCall} based on the current {@link ImsCallProfile} for the call. 3252 * 3253 * @param profile The current {@link ImsCallProfile} for the call. 3254 */ 3255 private void trackVideoStateHistory(ImsCallProfile profile) { 3256 mWasVideoCall = mWasVideoCall || profile.isVideoCall(); 3257 } 3258 3259 /** 3260 * @return {@code true} if this call was a video call at some point in its life span, 3261 * {@code false} otherwise. 3262 */ 3263 public boolean wasVideoCall() { 3264 return mWasVideoCall; 3265 } 3266 3267 /** 3268 * @return {@code true} if this call is a video call, {@code false} otherwise. 3269 */ 3270 public boolean isVideoCall() { 3271 synchronized(mLockObj) { 3272 return mCallProfile != null && mCallProfile.isVideoCall(); 3273 } 3274 } 3275 3276 /** 3277 * Determines if the current call radio access technology is over WIFI. 3278 * Note: This depends on the RIL exposing the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra. 3279 * This method is primarily intended to be used when checking if answering an incoming audio 3280 * call should cause a wifi video call to drop (e.g. 3281 * {@link android.telephony.CarrierConfigManager# 3282 * KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is set). 3283 * 3284 * @return {@code true} if the call is over WIFI, {@code false} otherwise. 3285 */ 3286 public boolean isWifiCall() { 3287 synchronized(mLockObj) { 3288 if (mCallProfile == null) { 3289 return false; 3290 } 3291 String callType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE); 3292 if (callType == null || callType.isEmpty()) { 3293 callType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT); 3294 } 3295 3296 // The RIL (sadly) sends us the EXTRA_CALL_RAT_TYPE as a string extra, rather than an 3297 // integer extra, so we need to parse it. 3298 int radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN; 3299 try { 3300 radioTechnology = Integer.parseInt(callType); 3301 } catch (NumberFormatException nfe) { 3302 radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN; 3303 } 3304 3305 return radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN; 3306 } 3307 } 3308 3309 /** 3310 * Log a string to the radio buffer at the info level. 3311 * @param s The message to log 3312 */ 3313 private void logi(String s) { 3314 Log.i(TAG, appendImsCallInfoToString(s)); 3315 } 3316 3317 /** 3318 * Log a string to the radio buffer at the debug level. 3319 * @param s The message to log 3320 */ 3321 private void logd(String s) { 3322 Log.d(TAG, appendImsCallInfoToString(s)); 3323 } 3324 3325 /** 3326 * Log a string to the radio buffer at the verbose level. 3327 * @param s The message to log 3328 */ 3329 private void logv(String s) { 3330 Log.v(TAG, appendImsCallInfoToString(s)); 3331 } 3332 3333 /** 3334 * Log a string to the radio buffer at the error level. 3335 * @param s The message to log 3336 */ 3337 private void loge(String s) { 3338 Log.e(TAG, appendImsCallInfoToString(s)); 3339 } 3340 3341 /** 3342 * Log a string to the radio buffer at the error level with a throwable 3343 * @param s The message to log 3344 * @param t The associated throwable 3345 */ 3346 private void loge(String s, Throwable t) { 3347 Log.e(TAG, appendImsCallInfoToString(s), t); 3348 } 3349 } 3350