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