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