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