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