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