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 mCallProfile.mMediaProfile.copyFrom(profile); 2542 } 2543 2544 if (listener != null) { 2545 try { 2546 listener.onCallProgressing(ImsCall.this); 2547 } catch (Throwable t) { 2548 loge("callSessionProgressing :: ", t); 2549 } 2550 } 2551 } 2552 2553 @Override callSessionStarted(ImsCallSession session, ImsCallProfile profile)2554 public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) { 2555 logi("callSessionStarted :: session=" + session + " profile=" + profile); 2556 2557 if (!isTransientConferenceSession(session)) { 2558 // In the case that we are in the middle of a merge (either host or peer), we have 2559 // closure as far as this call's primary session is concerned. If we are not 2560 // merging...its a NOOP. 2561 setCallSessionMergePending(false); 2562 } else { 2563 logi("callSessionStarted :: on transient session=" + session); 2564 return; 2565 } 2566 2567 if (isTransientConferenceSession(session)) { 2568 // No further processing is needed if this is the transient session. 2569 return; 2570 } 2571 2572 ImsCall.Listener listener; 2573 2574 synchronized(ImsCall.this) { 2575 listener = mListener; 2576 setCallProfile(profile); 2577 } 2578 2579 if (listener != null) { 2580 try { 2581 listener.onCallStarted(ImsCall.this); 2582 } catch (Throwable t) { 2583 loge("callSessionStarted :: ", t); 2584 } 2585 } 2586 } 2587 2588 @Override callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2589 public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2590 loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo); 2591 2592 if (isTransientConferenceSession(session)) { 2593 // We should not get this callback for a transient session. 2594 logi("callSessionStartFailed :: not supported for transient conference session=" + 2595 session); 2596 return; 2597 } 2598 2599 if (mIsConferenceHost) { 2600 // If the dial request was a adhoc conf calling one, this call would have 2601 // been marked the conference host as part of the request. 2602 mIsConferenceHost = false; 2603 } 2604 2605 ImsCall.Listener listener; 2606 2607 synchronized(ImsCall.this) { 2608 listener = mListener; 2609 mLastReasonInfo = reasonInfo; 2610 } 2611 2612 if (listener != null) { 2613 try { 2614 listener.onCallStartFailed(ImsCall.this, reasonInfo); 2615 } catch (Throwable t) { 2616 loge("callSessionStarted :: ", t); 2617 } 2618 } 2619 } 2620 2621 @Override callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo)2622 public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) { 2623 logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo); 2624 2625 if (isTransientConferenceSession(session)) { 2626 logi("callSessionTerminated :: on transient session=" + session); 2627 // This is bad, it should be treated much a callSessionMergeFailed since the 2628 // transient session only exists when in the process of a merge and the 2629 // termination of this session is effectively the end of the merge. 2630 processMergeFailed(reasonInfo); 2631 return; 2632 } 2633 2634 if (mOverrideReason != ImsReasonInfo.CODE_UNSPECIFIED) { 2635 logi("callSessionTerminated :: overrideReasonInfo=" + mOverrideReason); 2636 reasonInfo = new ImsReasonInfo(mOverrideReason, reasonInfo.getExtraCode(), 2637 reasonInfo.getExtraMessage()); 2638 } 2639 2640 // Process the termination first. If we are in the midst of establishing a conference 2641 // call, we may bury this callback until we are done. If there so no conference 2642 // call, the code after this function will be a NOOP. 2643 processCallTerminated(reasonInfo); 2644 2645 // If session has terminated, it is no longer pending merge. 2646 setCallSessionMergePending(false); 2647 2648 } 2649 2650 @Override callSessionHeld(ImsCallSession session, ImsCallProfile profile)2651 public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) { 2652 logi("callSessionHeld :: session=" + session + "profile=" + profile); 2653 ImsCall.Listener listener; 2654 2655 synchronized(ImsCall.this) { 2656 // If the session was held, it is no longer pending a merge -- this means it could 2657 // not be merged into the conference and was held instead. 2658 setCallSessionMergePending(false); 2659 2660 setCallProfile(profile); 2661 2662 if (mUpdateRequest == UPDATE_HOLD_MERGE) { 2663 // This hold request was made to set the stage for a merge. 2664 mergeInternal(); 2665 return; 2666 } 2667 2668 mHold = true; 2669 mUpdateRequest = UPDATE_NONE; 2670 listener = mListener; 2671 } 2672 2673 if (listener != null) { 2674 try { 2675 listener.onCallHeld(ImsCall.this); 2676 } catch (Throwable t) { 2677 loge("callSessionHeld :: ", t); 2678 } 2679 } 2680 } 2681 2682 @Override callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2683 public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2684 loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo); 2685 2686 if (isTransientConferenceSession(session)) { 2687 // We should not get this callback for a transient session. 2688 logi("callSessionHoldFailed :: not supported for transient conference session=" + 2689 session); 2690 return; 2691 } 2692 2693 logi("callSessionHoldFailed :: session=" + session + 2694 ", reasonInfo=" + reasonInfo); 2695 2696 synchronized (mLockObj) { 2697 mHold = false; 2698 } 2699 2700 boolean isHoldForMerge = false; 2701 ImsCall.Listener listener; 2702 2703 synchronized(ImsCall.this) { 2704 if (mUpdateRequest == UPDATE_HOLD_MERGE) { 2705 isHoldForMerge = true; 2706 } 2707 2708 mUpdateRequest = UPDATE_NONE; 2709 listener = mListener; 2710 } 2711 2712 if (listener != null) { 2713 try { 2714 listener.onCallHoldFailed(ImsCall.this, reasonInfo); 2715 } catch (Throwable t) { 2716 loge("callSessionHoldFailed :: ", t); 2717 } 2718 } 2719 } 2720 2721 /** 2722 * Indicates that an {@link ImsCallSession} has been remotely held. This can be due to the 2723 * remote party holding the current call, or swapping between calls. 2724 * @param session the session which was held. 2725 * @param profile the profile for the held call. 2726 */ 2727 @Override callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile)2728 public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) { 2729 logi("callSessionHoldReceived :: session=" + session + "profile=" + profile); 2730 2731 if (isTransientConferenceSession(session)) { 2732 // We should not get this callback for a transient session. 2733 logi("callSessionHoldReceived :: not supported for transient conference session=" + 2734 session); 2735 return; 2736 } 2737 2738 ImsCall.Listener listener; 2739 2740 synchronized(ImsCall.this) { 2741 listener = mListener; 2742 setCallProfile(profile); 2743 } 2744 2745 if (listener != null) { 2746 try { 2747 listener.onCallHoldReceived(ImsCall.this); 2748 } catch (Throwable t) { 2749 loge("callSessionHoldReceived :: ", t); 2750 } 2751 } 2752 } 2753 2754 /** 2755 * Indicates that an {@link ImsCallSession} has been remotely resumed. This can be due to 2756 * the remote party un-holding the current call, or swapping back to this call. 2757 * @param session the session which was resumed. 2758 * @param profile the profile for the held call. 2759 */ 2760 @Override callSessionResumed(ImsCallSession session, ImsCallProfile profile)2761 public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) { 2762 logi("callSessionResumed :: session=" + session + "profile=" + profile); 2763 2764 if (isTransientConferenceSession(session)) { 2765 logi("callSessionResumed :: not supported for transient conference session=" + 2766 session); 2767 return; 2768 } 2769 2770 // If this call was pending a merge, it is not anymore. This is the case when we 2771 // are merging in a new call into an existing conference. 2772 setCallSessionMergePending(false); 2773 2774 // TOOD: When we are merging a new call into an existing conference we are waiting 2775 // for 2 triggers to let us know that the conference has been established, the first 2776 // is a termination for the new calls (since it is added to the conference) the second 2777 // would be a resume on the existing conference. If the resume comes first, then 2778 // we will make the onCallResumed() callback and its unclear how this will behave if 2779 // the termination has not come yet. 2780 2781 ImsCall.Listener listener; 2782 synchronized(ImsCall.this) { 2783 listener = mListener; 2784 setCallProfile(profile); 2785 mUpdateRequest = UPDATE_NONE; 2786 mHold = false; 2787 } 2788 2789 if (listener != null) { 2790 try { 2791 listener.onCallResumed(ImsCall.this); 2792 } catch (Throwable t) { 2793 loge("callSessionResumed :: ", t); 2794 } 2795 } 2796 } 2797 2798 @Override callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2799 public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2800 loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo); 2801 2802 if (isTransientConferenceSession(session)) { 2803 logi("callSessionResumeFailed :: not supported for transient conference session=" + 2804 session); 2805 return; 2806 } 2807 2808 synchronized(mLockObj) { 2809 mHold = true; 2810 } 2811 2812 ImsCall.Listener listener; 2813 2814 synchronized(ImsCall.this) { 2815 listener = mListener; 2816 mUpdateRequest = UPDATE_NONE; 2817 } 2818 2819 if (listener != null) { 2820 try { 2821 listener.onCallResumeFailed(ImsCall.this, reasonInfo); 2822 } catch (Throwable t) { 2823 loge("callSessionResumeFailed :: ", t); 2824 } 2825 } 2826 } 2827 2828 @Override callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile)2829 public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) { 2830 logi("callSessionResumeReceived :: session=" + session + "profile=" + profile); 2831 2832 if (isTransientConferenceSession(session)) { 2833 logi("callSessionResumeReceived :: not supported for transient conference session=" + 2834 session); 2835 return; 2836 } 2837 2838 ImsCall.Listener listener; 2839 2840 synchronized(ImsCall.this) { 2841 listener = mListener; 2842 setCallProfile(profile); 2843 } 2844 2845 if (listener != null) { 2846 try { 2847 listener.onCallResumeReceived(ImsCall.this); 2848 } catch (Throwable t) { 2849 loge("callSessionResumeReceived :: ", t); 2850 } 2851 } 2852 } 2853 2854 @Override callSessionMergeStarted(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)2855 public void callSessionMergeStarted(ImsCallSession session, 2856 ImsCallSession newSession, ImsCallProfile profile) { 2857 logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession + 2858 ", profile=" + profile); 2859 2860 return; 2861 } 2862 2863 /** 2864 * We received a callback from ImsCallSession that merge completed. 2865 * @param newSession - this session can have 2 values based on the below scenarios 2866 * 2867 * Conference Scenarios : 2868 * Case 1 - 3 way success case 2869 * Case 2 - 3 way success case but held call fails to merge 2870 * Case 3 - 3 way success case but active call fails to merge 2871 * case 4 - 4 way success case, where merge is initiated on the foreground single-party 2872 * call and the conference (mergeHost) is the background call. 2873 * case 5 - 4 way success case, where merge is initiated on the foreground conference 2874 * call (mergeHost) and the single party call is in the background. 2875 * 2876 * Conference Result: 2877 * session : new session after conference 2878 * newSession = new session for case 1, 2, 3. 2879 * Should be considered as mTransientConferencession 2880 * newSession = Active conference session for case 5 will be null 2881 * mergehost was foreground call 2882 * mTransientConferencession will be null 2883 * newSession = Active conference session for case 4 will be null 2884 * mergeHost was background call 2885 * mTransientConferencession will be null 2886 */ 2887 @Override callSessionMergeComplete(ImsCallSession newSession)2888 public void callSessionMergeComplete(ImsCallSession newSession) { 2889 logi("callSessionMergeComplete :: newSession =" + newSession); 2890 if (!isMergeHost()) { 2891 // Handles case 4 2892 mMergeHost.processMergeComplete(); 2893 } else { 2894 // Handles case 1, 2, 3 2895 if (newSession != null) { 2896 mTransientConferenceSession = newSession; 2897 } 2898 // Handles case 5 2899 processMergeComplete(); 2900 } 2901 } 2902 2903 @Override callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2904 public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2905 loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo); 2906 2907 // Its possible that there could be threading issues with the other thread handling 2908 // the other call. This could affect our state. 2909 synchronized (ImsCall.this) { 2910 // Let's tell our parent ImsCall that the merge has failed and we need to clean 2911 // up any temporary, transient state. Note this only gets called for an initial 2912 // conference. If a merge into an existing conference fails, the two sessions will 2913 // just go back to their original state (ACTIVE or HELD). 2914 if (isMergeHost()) { 2915 processMergeFailed(reasonInfo); 2916 } else if (mMergeHost != null) { 2917 mMergeHost.processMergeFailed(reasonInfo); 2918 } else { 2919 loge("callSessionMergeFailed :: No merge host for this conference!"); 2920 } 2921 } 2922 } 2923 2924 @Override callSessionUpdated(ImsCallSession session, ImsCallProfile profile)2925 public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) { 2926 logi("callSessionUpdated :: session=" + session + " profile=" + profile); 2927 2928 if (isTransientConferenceSession(session)) { 2929 logi("callSessionUpdated :: not supported for transient conference session=" + 2930 session); 2931 return; 2932 } 2933 2934 ImsCall.Listener listener; 2935 2936 synchronized(ImsCall.this) { 2937 listener = mListener; 2938 setCallProfile(profile); 2939 } 2940 2941 if (listener != null) { 2942 try { 2943 listener.onCallUpdated(ImsCall.this); 2944 } catch (Throwable t) { 2945 loge("callSessionUpdated :: ", t); 2946 } 2947 } 2948 } 2949 2950 @Override callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2951 public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 2952 loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo); 2953 2954 if (isTransientConferenceSession(session)) { 2955 logi("callSessionUpdateFailed :: not supported for transient conference session=" + 2956 session); 2957 return; 2958 } 2959 2960 ImsCall.Listener listener; 2961 2962 synchronized(ImsCall.this) { 2963 listener = mListener; 2964 mUpdateRequest = UPDATE_NONE; 2965 } 2966 2967 if (listener != null) { 2968 try { 2969 listener.onCallUpdateFailed(ImsCall.this, reasonInfo); 2970 } catch (Throwable t) { 2971 loge("callSessionUpdateFailed :: ", t); 2972 } 2973 } 2974 } 2975 2976 @Override callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile)2977 public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) { 2978 logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile); 2979 2980 if (isTransientConferenceSession(session)) { 2981 logi("callSessionUpdateReceived :: not supported for transient conference " + 2982 "session=" + session); 2983 return; 2984 } 2985 2986 ImsCall.Listener listener; 2987 2988 synchronized(ImsCall.this) { 2989 listener = mListener; 2990 mProposedCallProfile = profile; 2991 mUpdateRequest = UPDATE_UNSPECIFIED; 2992 } 2993 2994 if (listener != null) { 2995 try { 2996 listener.onCallUpdateReceived(ImsCall.this); 2997 } catch (Throwable t) { 2998 loge("callSessionUpdateReceived :: ", t); 2999 } 3000 } 3001 } 3002 3003 @Override callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)3004 public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession, 3005 ImsCallProfile profile) { 3006 logi("callSessionConferenceExtended :: session=" + session + " newSession=" + 3007 newSession + ", profile=" + profile); 3008 3009 if (isTransientConferenceSession(session)) { 3010 logi("callSessionConferenceExtended :: not supported for transient conference " + 3011 "session=" + session); 3012 return; 3013 } 3014 3015 ImsCall newCall = createNewCall(newSession, profile); 3016 3017 if (newCall == null) { 3018 callSessionConferenceExtendFailed(session, new ImsReasonInfo()); 3019 return; 3020 } 3021 3022 ImsCall.Listener listener; 3023 3024 synchronized(ImsCall.this) { 3025 listener = mListener; 3026 mUpdateRequest = UPDATE_NONE; 3027 } 3028 3029 if (listener != null) { 3030 try { 3031 listener.onCallConferenceExtended(ImsCall.this, newCall); 3032 } catch (Throwable t) { 3033 loge("callSessionConferenceExtended :: ", t); 3034 } 3035 } 3036 } 3037 3038 @Override callSessionConferenceExtendFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3039 public void callSessionConferenceExtendFailed(ImsCallSession session, 3040 ImsReasonInfo reasonInfo) { 3041 loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo); 3042 3043 if (isTransientConferenceSession(session)) { 3044 logi("callSessionConferenceExtendFailed :: not supported for transient " + 3045 "conference session=" + session); 3046 return; 3047 } 3048 3049 ImsCall.Listener listener; 3050 3051 synchronized(ImsCall.this) { 3052 listener = mListener; 3053 mUpdateRequest = UPDATE_NONE; 3054 } 3055 3056 if (listener != null) { 3057 try { 3058 listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo); 3059 } catch (Throwable t) { 3060 loge("callSessionConferenceExtendFailed :: ", t); 3061 } 3062 } 3063 } 3064 3065 @Override callSessionConferenceExtendReceived(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)3066 public void callSessionConferenceExtendReceived(ImsCallSession session, 3067 ImsCallSession newSession, ImsCallProfile profile) { 3068 logi("callSessionConferenceExtendReceived :: newSession=" + newSession + 3069 ", profile=" + profile); 3070 3071 if (isTransientConferenceSession(session)) { 3072 logi("callSessionConferenceExtendReceived :: not supported for transient " + 3073 "conference session" + session); 3074 return; 3075 } 3076 3077 ImsCall newCall = createNewCall(newSession, profile); 3078 3079 if (newCall == null) { 3080 // Should all the calls be terminated...??? 3081 return; 3082 } 3083 3084 ImsCall.Listener listener; 3085 3086 synchronized(ImsCall.this) { 3087 listener = mListener; 3088 } 3089 3090 if (listener != null) { 3091 try { 3092 listener.onCallConferenceExtendReceived(ImsCall.this, newCall); 3093 } catch (Throwable t) { 3094 loge("callSessionConferenceExtendReceived :: ", t); 3095 } 3096 } 3097 } 3098 3099 @Override callSessionInviteParticipantsRequestDelivered(ImsCallSession session)3100 public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) { 3101 logi("callSessionInviteParticipantsRequestDelivered ::"); 3102 3103 if (isTransientConferenceSession(session)) { 3104 logi("callSessionInviteParticipantsRequestDelivered :: not supported for " + 3105 "conference session=" + session); 3106 return; 3107 } 3108 3109 ImsCall.Listener listener; 3110 3111 synchronized(ImsCall.this) { 3112 listener = mListener; 3113 } 3114 3115 mIsConferenceHost = true; 3116 3117 if (listener != null) { 3118 try { 3119 listener.onCallInviteParticipantsRequestDelivered(ImsCall.this); 3120 } catch (Throwable t) { 3121 loge("callSessionInviteParticipantsRequestDelivered :: ", t); 3122 } 3123 } 3124 } 3125 3126 @Override callSessionInviteParticipantsRequestFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3127 public void callSessionInviteParticipantsRequestFailed(ImsCallSession session, 3128 ImsReasonInfo reasonInfo) { 3129 loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo); 3130 3131 if (isTransientConferenceSession(session)) { 3132 logi("callSessionInviteParticipantsRequestFailed :: not supported for " + 3133 "conference session=" + session); 3134 return; 3135 } 3136 3137 ImsCall.Listener listener; 3138 3139 synchronized(ImsCall.this) { 3140 listener = mListener; 3141 } 3142 3143 if (listener != null) { 3144 try { 3145 listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo); 3146 } catch (Throwable t) { 3147 loge("callSessionInviteParticipantsRequestFailed :: ", t); 3148 } 3149 } 3150 } 3151 3152 @Override callSessionRemoveParticipantsRequestDelivered(ImsCallSession session)3153 public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) { 3154 logi("callSessionRemoveParticipantsRequestDelivered ::"); 3155 3156 if (isTransientConferenceSession(session)) { 3157 logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " + 3158 "conference session=" + session); 3159 return; 3160 } 3161 3162 ImsCall.Listener listener; 3163 3164 synchronized(ImsCall.this) { 3165 listener = mListener; 3166 } 3167 3168 if (listener != null) { 3169 try { 3170 listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this); 3171 } catch (Throwable t) { 3172 loge("callSessionRemoveParticipantsRequestDelivered :: ", t); 3173 } 3174 } 3175 } 3176 3177 @Override callSessionRemoveParticipantsRequestFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3178 public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session, 3179 ImsReasonInfo reasonInfo) { 3180 loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo); 3181 3182 if (isTransientConferenceSession(session)) { 3183 logi("callSessionRemoveParticipantsRequestFailed :: not supported for " + 3184 "conference session=" + session); 3185 return; 3186 } 3187 3188 ImsCall.Listener listener; 3189 3190 synchronized(ImsCall.this) { 3191 listener = mListener; 3192 } 3193 3194 if (listener != null) { 3195 try { 3196 listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo); 3197 } catch (Throwable t) { 3198 loge("callSessionRemoveParticipantsRequestFailed :: ", t); 3199 } 3200 } 3201 } 3202 3203 @Override callSessionConferenceStateUpdated(ImsCallSession session, ImsConferenceState state)3204 public void callSessionConferenceStateUpdated(ImsCallSession session, 3205 ImsConferenceState state) { 3206 logi("callSessionConferenceStateUpdated :: state=" + state); 3207 conferenceStateUpdated(state); 3208 } 3209 3210 @Override callSessionUssdMessageReceived(ImsCallSession session, int mode, String ussdMessage)3211 public void callSessionUssdMessageReceived(ImsCallSession session, int mode, 3212 String ussdMessage) { 3213 logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" + 3214 ussdMessage); 3215 3216 if (isTransientConferenceSession(session)) { 3217 logi("callSessionUssdMessageReceived :: not supported for transient " + 3218 "conference session=" + session); 3219 return; 3220 } 3221 3222 ImsCall.Listener listener; 3223 3224 synchronized(ImsCall.this) { 3225 listener = mListener; 3226 } 3227 3228 if (listener != null) { 3229 try { 3230 listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage); 3231 } catch (Throwable t) { 3232 loge("callSessionUssdMessageReceived :: ", t); 3233 } 3234 } 3235 } 3236 3237 @Override callSessionTtyModeReceived(ImsCallSession session, int mode)3238 public void callSessionTtyModeReceived(ImsCallSession session, int mode) { 3239 logi("callSessionTtyModeReceived :: mode=" + mode); 3240 3241 ImsCall.Listener listener; 3242 3243 synchronized(ImsCall.this) { 3244 listener = mListener; 3245 } 3246 3247 if (listener != null) { 3248 try { 3249 listener.onCallSessionTtyModeReceived(ImsCall.this, mode); 3250 } catch (Throwable t) { 3251 loge("callSessionTtyModeReceived :: ", t); 3252 } 3253 } 3254 } 3255 3256 /** 3257 * Notifies of a change to the multiparty state for this {@code ImsCallSession}. 3258 * 3259 * @param session The call session. 3260 * @param isMultiParty {@code true} if the session became multiparty, {@code false} 3261 * otherwise. 3262 */ 3263 @Override callSessionMultipartyStateChanged(ImsCallSession session, boolean isMultiParty)3264 public void callSessionMultipartyStateChanged(ImsCallSession session, 3265 boolean isMultiParty) { 3266 if (VDBG) { 3267 logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y" 3268 : "N")); 3269 } 3270 3271 ImsCall.Listener listener; 3272 3273 synchronized(ImsCall.this) { 3274 listener = mListener; 3275 } 3276 3277 if (listener != null) { 3278 try { 3279 listener.onMultipartyStateChanged(ImsCall.this, isMultiParty); 3280 } catch (Throwable t) { 3281 loge("callSessionMultipartyStateChanged :: ", t); 3282 } 3283 } 3284 } 3285 callSessionHandover(ImsCallSession session, int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo)3286 public void callSessionHandover(ImsCallSession session, int srcNetworkType, 3287 int targetNetworkType, ImsReasonInfo reasonInfo) { 3288 logi("callSessionHandover :: session=" + session + ", srcAccessTech=" + 3289 srcNetworkType + ", targetAccessTech=" + targetNetworkType + ", reasonInfo=" + 3290 reasonInfo); 3291 3292 ImsCall.Listener listener; 3293 3294 synchronized(ImsCall.this) { 3295 listener = mListener; 3296 } 3297 3298 if (listener != null) { 3299 try { 3300 listener.onCallHandover(ImsCall.this, 3301 ServiceState.networkTypeToRilRadioTechnology(srcNetworkType), 3302 ServiceState.networkTypeToRilRadioTechnology(targetNetworkType), 3303 reasonInfo); 3304 } catch (Throwable t) { 3305 loge("callSessionHandover :: ", t); 3306 } 3307 } 3308 } 3309 3310 @Override callSessionHandoverFailed(ImsCallSession session, int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo)3311 public void callSessionHandoverFailed(ImsCallSession session, int srcNetworkType, 3312 int targetNetworkType, ImsReasonInfo reasonInfo) { 3313 loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" + 3314 srcNetworkType + ", targetAccessTech=" + targetNetworkType + ", reasonInfo=" + 3315 reasonInfo); 3316 3317 ImsCall.Listener listener; 3318 3319 synchronized(ImsCall.this) { 3320 listener = mListener; 3321 } 3322 3323 if (listener != null) { 3324 try { 3325 listener.onCallHandoverFailed(ImsCall.this, 3326 ServiceState.networkTypeToRilRadioTechnology(srcNetworkType), 3327 ServiceState.networkTypeToRilRadioTechnology(targetNetworkType), 3328 reasonInfo); 3329 } catch (Throwable t) { 3330 loge("callSessionHandoverFailed :: ", t); 3331 } 3332 } 3333 } 3334 3335 @Override callSessionSuppServiceReceived(ImsCallSession session, ImsSuppServiceNotification suppServiceInfo )3336 public void callSessionSuppServiceReceived(ImsCallSession session, 3337 ImsSuppServiceNotification suppServiceInfo ) { 3338 if (isTransientConferenceSession(session)) { 3339 logi("callSessionSuppServiceReceived :: not supported for transient conference" 3340 + " session=" + session); 3341 return; 3342 } 3343 3344 logi("callSessionSuppServiceReceived :: session=" + session + 3345 ", suppServiceInfo" + suppServiceInfo); 3346 3347 ImsCall.Listener listener; 3348 3349 synchronized(ImsCall.this) { 3350 listener = mListener; 3351 } 3352 3353 if (listener != null) { 3354 try { 3355 listener.onCallSuppServiceReceived(ImsCall.this, suppServiceInfo); 3356 } catch (Throwable t) { 3357 loge("callSessionSuppServiceReceived :: ", t); 3358 } 3359 } 3360 } 3361 3362 @Override callSessionRttModifyRequestReceived(ImsCallSession session, ImsCallProfile callProfile)3363 public void callSessionRttModifyRequestReceived(ImsCallSession session, 3364 ImsCallProfile callProfile) { 3365 ImsCall.Listener listener; 3366 logi("callSessionRttModifyRequestReceived"); 3367 3368 synchronized(ImsCall.this) { 3369 listener = mListener; 3370 } 3371 3372 if (!callProfile.mMediaProfile.isRttCall()) { 3373 logi("callSessionRttModifyRequestReceived:: ignoring request, requested profile " + 3374 "is not RTT."); 3375 return; 3376 } 3377 3378 if (listener != null) { 3379 try { 3380 listener.onRttModifyRequestReceived(ImsCall.this); 3381 } catch (Throwable t) { 3382 loge("callSessionRttModifyRequestReceived:: ", t); 3383 } 3384 } 3385 } 3386 3387 @Override callSessionRttModifyResponseReceived(int status)3388 public void callSessionRttModifyResponseReceived(int status) { 3389 ImsCall.Listener listener; 3390 3391 logi("callSessionRttModifyResponseReceived: " + status); 3392 synchronized(ImsCall.this) { 3393 listener = mListener; 3394 } 3395 3396 if (listener != null) { 3397 try { 3398 listener.onRttModifyResponseReceived(ImsCall.this, status); 3399 } catch (Throwable t) { 3400 loge("callSessionRttModifyResponseReceived:: ", t); 3401 } 3402 } 3403 } 3404 3405 @Override callSessionRttMessageReceived(String rttMessage)3406 public void callSessionRttMessageReceived(String rttMessage) { 3407 ImsCall.Listener listener; 3408 3409 synchronized(ImsCall.this) { 3410 listener = mListener; 3411 } 3412 3413 if (listener != null) { 3414 try { 3415 listener.onRttMessageReceived(ImsCall.this, rttMessage); 3416 } catch (Throwable t) { 3417 loge("callSessionRttMessageReceived:: ", t); 3418 } 3419 } 3420 } 3421 3422 @Override callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile)3423 public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) { 3424 ImsCall.Listener listener; 3425 3426 synchronized(ImsCall.this) { 3427 listener = mListener; 3428 } 3429 3430 if (listener != null) { 3431 try { 3432 listener.onRttAudioIndicatorChanged(ImsCall.this, profile); 3433 } catch (Throwable t) { 3434 loge("callSessionRttAudioIndicatorChanged:: ", t); 3435 } 3436 } 3437 } 3438 3439 @Override callSessionTransferred(ImsCallSession session)3440 public void callSessionTransferred(ImsCallSession session) { 3441 ImsCall.Listener listener; 3442 3443 synchronized(ImsCall.this) { 3444 listener = mListener; 3445 } 3446 3447 if (listener != null) { 3448 try { 3449 listener.onCallSessionTransferred(ImsCall.this); 3450 } catch (Throwable t) { 3451 loge("callSessionTransferred:: ", t); 3452 } 3453 } 3454 } 3455 3456 @Override callSessionTransferFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3457 public void callSessionTransferFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { 3458 ImsCall.Listener listener; 3459 3460 synchronized(ImsCall.this) { 3461 listener = mListener; 3462 } 3463 3464 if (listener != null) { 3465 try { 3466 listener.onCallSessionTransferFailed(ImsCall.this, reasonInfo); 3467 } catch (Throwable t) { 3468 loge("callSessionTransferFailed:: ", t); 3469 } 3470 } 3471 } 3472 3473 @Override callSessionDtmfReceived(char digit)3474 public void callSessionDtmfReceived(char digit) { 3475 ImsCall.Listener listener; 3476 3477 synchronized(ImsCall.this) { 3478 listener = mListener; 3479 } 3480 3481 if (listener != null) { 3482 try { 3483 listener.onCallSessionDtmfReceived(ImsCall.this, digit); 3484 } catch (Throwable t) { 3485 loge("callSessionDtmfReceived:: ", t); 3486 } 3487 } 3488 } 3489 3490 @Override callQualityChanged(CallQuality callQuality)3491 public void callQualityChanged(CallQuality callQuality) { 3492 ImsCall.Listener listener; 3493 3494 synchronized (ImsCall.this) { 3495 listener = mListener; 3496 } 3497 3498 if (listener != null) { 3499 try { 3500 listener.onCallQualityChanged(ImsCall.this, callQuality); 3501 } catch (Throwable t) { 3502 loge("callQualityChanged:: ", t); 3503 } 3504 } 3505 } 3506 3507 @Override callSessionRtpHeaderExtensionsReceived( @onNull Set<RtpHeaderExtension> extensions)3508 public void callSessionRtpHeaderExtensionsReceived( 3509 @NonNull Set<RtpHeaderExtension> extensions) { 3510 ImsCall.Listener listener; 3511 3512 synchronized (ImsCall.this) { 3513 listener = mListener; 3514 } 3515 3516 if (listener != null) { 3517 try { 3518 listener.onCallSessionRtpHeaderExtensionsReceived(ImsCall.this, extensions); 3519 } catch (Throwable t) { 3520 loge("callSessionRtpHeaderExtensionsReceived:: ", t); 3521 } 3522 } 3523 } 3524 3525 @Override callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond)3526 public void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) { 3527 ImsCall.Listener listener; 3528 3529 logi("callSessionSendAnbrQuery in ImsCall"); 3530 synchronized (ImsCall.this) { 3531 listener = mListener; 3532 } 3533 3534 if (listener != null) { 3535 try { 3536 listener.onCallSessionSendAnbrQuery(ImsCall.this, mediaType, 3537 direction, bitsPerSecond); 3538 } catch (Throwable t) { 3539 loge("callSessionSendAnbrQuery:: ", t); 3540 } 3541 } 3542 } 3543 } 3544 3545 /** 3546 * Report a new conference state to the current {@link ImsCall} and inform listeners of the 3547 * change. Marked as {@code VisibleForTesting} so that the 3548 * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference 3549 * event package into a regular ongoing IMS call. 3550 * 3551 * @param state The {@link ImsConferenceState}. 3552 */ 3553 @VisibleForTesting conferenceStateUpdated(ImsConferenceState state)3554 public void conferenceStateUpdated(ImsConferenceState state) { 3555 Listener listener; 3556 3557 synchronized(this) { 3558 notifyConferenceStateUpdated(state); 3559 listener = mListener; 3560 } 3561 3562 if (listener != null) { 3563 try { 3564 listener.onCallConferenceStateUpdated(this, state); 3565 } catch (Throwable t) { 3566 loge("callSessionConferenceStateUpdated :: ", t); 3567 } 3568 } 3569 } 3570 3571 /** 3572 * Provides a human-readable string representation of an update request. 3573 * 3574 * @param updateRequest The update request. 3575 * @return The string representation. 3576 */ updateRequestToString(int updateRequest)3577 private String updateRequestToString(int updateRequest) { 3578 switch (updateRequest) { 3579 case UPDATE_NONE: 3580 return "NONE"; 3581 case UPDATE_HOLD: 3582 return "HOLD"; 3583 case UPDATE_HOLD_MERGE: 3584 return "HOLD_MERGE"; 3585 case UPDATE_RESUME: 3586 return "RESUME"; 3587 case UPDATE_MERGE: 3588 return "MERGE"; 3589 case UPDATE_EXTEND_TO_CONFERENCE: 3590 return "EXTEND_TO_CONFERENCE"; 3591 case UPDATE_UNSPECIFIED: 3592 return "UNSPECIFIED"; 3593 default: 3594 return "UNKNOWN"; 3595 } 3596 } 3597 3598 /** 3599 * Clears the merge peer for this call, ensuring that the peer's connection to this call is also 3600 * severed at the same time. 3601 */ clearMergeInfo()3602 private void clearMergeInfo() { 3603 if (CONF_DBG) { 3604 logi("clearMergeInfo :: clearing all merge info"); 3605 } 3606 3607 // First clear out the merge partner then clear ourselves out. 3608 if (mMergeHost != null) { 3609 mMergeHost.mMergePeer = null; 3610 mMergeHost.mUpdateRequest = UPDATE_NONE; 3611 mMergeHost.mCallSessionMergePending = false; 3612 } 3613 if (mMergePeer != null) { 3614 mMergePeer.mMergeHost = null; 3615 mMergePeer.mUpdateRequest = UPDATE_NONE; 3616 mMergePeer.mCallSessionMergePending = false; 3617 } 3618 mMergeHost = null; 3619 mMergePeer = null; 3620 mUpdateRequest = UPDATE_NONE; 3621 mCallSessionMergePending = false; 3622 } 3623 3624 /** 3625 * Sets the merge peer for the current call. The merge peer is the background call that will be 3626 * merged into this call. On the merge peer, sets the merge host to be this call. 3627 * 3628 * @param mergePeer The peer call to be merged into this one. 3629 */ setMergePeer(ImsCall mergePeer)3630 private void setMergePeer(ImsCall mergePeer) { 3631 mMergePeer = mergePeer; 3632 mMergeHost = null; 3633 3634 mergePeer.mMergeHost = ImsCall.this; 3635 mergePeer.mMergePeer = null; 3636 } 3637 3638 /** 3639 * Sets the merge hody for the current call. The merge host is the foreground call this call 3640 * will be merged into. On the merge host, sets the merge peer to be this call. 3641 * 3642 * @param mergeHost The merge host this call will be merged into. 3643 */ setMergeHost(ImsCall mergeHost)3644 public void setMergeHost(ImsCall mergeHost) { 3645 mMergeHost = mergeHost; 3646 mMergePeer = null; 3647 3648 mergeHost.mMergeHost = null; 3649 mergeHost.mMergePeer = ImsCall.this; 3650 } 3651 3652 /** 3653 * Determines if the current call is in the process of merging with another call or conference. 3654 * 3655 * @return {@code true} if in the process of merging. 3656 */ isMerging()3657 private boolean isMerging() { 3658 return mMergePeer != null || mMergeHost != null; 3659 } 3660 3661 /** 3662 * Determines if the current call is the host of the merge. 3663 * 3664 * @return {@code true} if the call is the merge host. 3665 */ isMergeHost()3666 private boolean isMergeHost() { 3667 return mMergePeer != null && mMergeHost == null; 3668 } 3669 3670 /** 3671 * Determines if the current call is the peer of the merge. 3672 * 3673 * @return {@code true} if the call is the merge peer. 3674 */ isMergePeer()3675 private boolean isMergePeer() { 3676 return mMergePeer == null && mMergeHost != null; 3677 } 3678 3679 /** 3680 * Determines if the call session is pending merge into a conference or not. 3681 * 3682 * @return {@code true} if a merge into a conference is pending, {@code false} otherwise. 3683 */ isCallSessionMergePending()3684 public boolean isCallSessionMergePending() { 3685 return mCallSessionMergePending; 3686 } 3687 3688 /** 3689 * Sets flag indicating whether the call session is pending merge into a conference or not. 3690 * 3691 * @param callSessionMergePending {@code true} if a merge into the conference is pending, 3692 * {@code false} otherwise. 3693 */ setCallSessionMergePending(boolean callSessionMergePending)3694 private void setCallSessionMergePending(boolean callSessionMergePending) { 3695 mCallSessionMergePending = callSessionMergePending; 3696 } 3697 3698 /** 3699 * Determines if there is a conference merge in process. If there is a merge in process, 3700 * determines if both the merge host and peer sessions have completed the merge process. This 3701 * means that we have received terminate or hold signals for the sessions, indicating that they 3702 * are no longer in the process of being merged into the conference. 3703 * <p> 3704 * The sessions are considered to have merged if: both calls still have merge peer/host 3705 * relationships configured, both sessions are not waiting to be merged into the conference, 3706 * and the transient conference session is alive in the case of an initial conference. 3707 * 3708 * @return {@code true} where the host and peer sessions have finished merging into the 3709 * conference, {@code false} if the merge has not yet completed, and {@code false} if there 3710 * is no conference merge in progress. 3711 */ shouldProcessConferenceResult()3712 private boolean shouldProcessConferenceResult() { 3713 boolean areMergeTriggersDone = false; 3714 3715 synchronized (ImsCall.this) { 3716 // if there is a merge going on, then the merge host/peer relationships should have been 3717 // set up. This works for both the initial conference or merging a call into an 3718 // existing conference. 3719 if (!isMergeHost() && !isMergePeer()) { 3720 if (CONF_DBG) { 3721 loge("shouldProcessConferenceResult :: no merge in progress"); 3722 } 3723 return false; 3724 } 3725 3726 // There is a merge in progress, so check the sessions to ensure: 3727 // 1. Both calls have completed being merged (or failing to merge) into the conference. 3728 // 2. The transient conference session is alive. 3729 if (isMergeHost()) { 3730 if (CONF_DBG) { 3731 logi("shouldProcessConferenceResult :: We are a merge host"); 3732 logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer); 3733 } 3734 areMergeTriggersDone = !isCallSessionMergePending() && 3735 !mMergePeer.isCallSessionMergePending(); 3736 if (!isMultiparty()) { 3737 // Only check the transient session when there is no existing conference 3738 areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession); 3739 } 3740 } else if (isMergePeer()) { 3741 if (CONF_DBG) { 3742 logi("shouldProcessConferenceResult :: We are a merge peer"); 3743 logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost); 3744 } 3745 areMergeTriggersDone = !isCallSessionMergePending() && 3746 !mMergeHost.isCallSessionMergePending(); 3747 if (!mMergeHost.isMultiparty()) { 3748 // Only check the transient session when there is no existing conference 3749 areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession); 3750 } else { 3751 // This else block is a special case for Verizon to handle these steps 3752 // 1. Establish a conference call. 3753 // 2. Add a new call (conference in in BG) 3754 // 3. Swap (conference active on FG) 3755 // 4. Merge 3756 // What happens here is that the BG call gets a terminated callback 3757 // because it was added to the conference. I've seen where 3758 // the FG gets no callback at all because its already active. 3759 // So if we continue to wait for it to set its isCallSessionMerging 3760 // flag to false...we'll be waiting forever. 3761 areMergeTriggersDone = !isCallSessionMergePending(); 3762 } 3763 } else { 3764 // Realistically this shouldn't happen, but best to be safe. 3765 loge("shouldProcessConferenceResult : merge in progress but call is neither" + 3766 " host nor peer."); 3767 } 3768 if (CONF_DBG) { 3769 logi("shouldProcessConferenceResult :: returning:" + 3770 (areMergeTriggersDone ? "true" : "false")); 3771 } 3772 } 3773 return areMergeTriggersDone; 3774 } 3775 3776 /** 3777 * Provides a string representation of the {@link ImsCall}. Primarily intended for use in log 3778 * statements. 3779 * 3780 * @return String representation of call. 3781 */ 3782 @Override toString()3783 public String toString() { 3784 StringBuilder sb = new StringBuilder(); 3785 sb.append("[ImsCall objId:"); 3786 sb.append(System.identityHashCode(this)); 3787 sb.append(" onHold:"); 3788 sb.append(isOnHold() ? "Y" : "N"); 3789 sb.append(" mute:"); 3790 sb.append(isMuted() ? "Y" : "N"); 3791 ImsCallProfile imsCallProfile = mCallProfile; 3792 if (imsCallProfile != null) { 3793 sb.append(" mCallProfile:" + imsCallProfile); 3794 sb.append(" networkType:"); 3795 sb.append(getNetworkType()); 3796 } 3797 sb.append(" updateRequest:"); 3798 sb.append(updateRequestToString(mUpdateRequest)); 3799 sb.append(" merging:"); 3800 sb.append(isMerging() ? "Y" : "N"); 3801 if (isMerging()) { 3802 if (isMergePeer()) { 3803 sb.append("P"); 3804 } else { 3805 sb.append("H"); 3806 } 3807 } 3808 sb.append(" merge action pending:"); 3809 sb.append(isCallSessionMergePending() ? "Y" : "N"); 3810 sb.append(" merged:"); 3811 sb.append(isMerged() ? "Y" : "N"); 3812 sb.append(" multiParty:"); 3813 sb.append(isMultiparty() ? "Y" : "N"); 3814 sb.append(" confHost:"); 3815 sb.append(isConferenceHost() ? "Y" : "N"); 3816 sb.append(" buried term:"); 3817 sb.append(mSessionEndDuringMerge ? "Y" : "N"); 3818 sb.append(" isVideo: "); 3819 sb.append(isVideoCall() ? "Y" : "N"); 3820 sb.append(" wasVideo: "); 3821 sb.append(mWasVideoCall ? "Y" : "N"); 3822 sb.append(" isWifi: "); 3823 sb.append(isWifiCall() ? "Y" : "N"); 3824 sb.append(" session:"); 3825 sb.append(mSession); 3826 sb.append(" transientSession:"); 3827 sb.append(mTransientConferenceSession); 3828 sb.append("]"); 3829 return sb.toString(); 3830 } 3831 throwImsException(Throwable t, int code)3832 private void throwImsException(Throwable t, int code) throws ImsException { 3833 if (t instanceof ImsException) { 3834 throw (ImsException) t; 3835 } else { 3836 throw new ImsException(String.valueOf(code), t, code); 3837 } 3838 } 3839 3840 /** 3841 * Append the ImsCall information to the provided string. Usefull for as a logging helper. 3842 * @param s The original string 3843 * @return The original string with {@code ImsCall} information appended to it. 3844 */ appendImsCallInfoToString(String s)3845 private String appendImsCallInfoToString(String s) { 3846 StringBuilder sb = new StringBuilder(); 3847 sb.append(s); 3848 sb.append(" ImsCall="); 3849 sb.append(ImsCall.this); 3850 return sb.toString(); 3851 } 3852 3853 /** 3854 * Updates {@link #mWasVideoCall} based on the current {@link ImsCallProfile} for the call. 3855 * 3856 * @param profile The current {@link ImsCallProfile} for the call. 3857 */ trackVideoStateHistory(ImsCallProfile profile)3858 private void trackVideoStateHistory(ImsCallProfile profile) { 3859 mWasVideoCall = mWasVideoCall || ( profile != null && profile.isVideoCall()); 3860 } 3861 3862 /** 3863 * @return {@code true} if this call was a video call at some point in its life span, 3864 * {@code false} otherwise. 3865 */ wasVideoCall()3866 public boolean wasVideoCall() { 3867 return mWasVideoCall; 3868 } 3869 3870 /** 3871 * @return {@code true} if this call is a video call, {@code false} otherwise. 3872 */ isVideoCall()3873 public boolean isVideoCall() { 3874 synchronized(mLockObj) { 3875 return mCallProfile != null && mCallProfile.isVideoCall(); 3876 } 3877 } 3878 3879 /** 3880 * Determines if the current call radio access technology is over WIFI. 3881 * Note: This depends on the RIL exposing the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra. 3882 * This method is primarily intended to be used when checking if answering an incoming audio 3883 * call should cause a wifi video call to drop (e.g. 3884 * {@link android.telephony.CarrierConfigManager# 3885 * KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is set). 3886 * 3887 * @return {@code true} if the call is over WIFI, {@code false} otherwise. 3888 */ isWifiCall()3889 public boolean isWifiCall() { 3890 synchronized(mLockObj) { 3891 if (mCallProfile == null) { 3892 return false; 3893 } 3894 return getNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN; 3895 } 3896 } 3897 3898 /** 3899 * Determines the network type for the {@link ImsCall}. 3900 * @return The {@link TelephonyManager} {@code NETWORK_TYPE_*} code in use. 3901 */ getNetworkType()3902 public int getNetworkType() { 3903 synchronized(mLockObj) { 3904 if (mCallProfile == null) { 3905 return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN; 3906 } 3907 int networkType = mCallProfile.getCallExtraInt(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE, 3908 TelephonyManager.NETWORK_TYPE_UNKNOWN); 3909 if (networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN) { 3910 // Try to look at old extras to see if the ImsService is using deprecated behavior. 3911 String oldRatType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE); 3912 if (TextUtils.isEmpty(oldRatType)) { 3913 oldRatType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT); 3914 } 3915 try { 3916 int oldRatTypeConverted = Integer.parseInt(oldRatType); 3917 networkType = ServiceState.rilRadioTechnologyToNetworkType(oldRatTypeConverted); 3918 } catch (NumberFormatException e) { 3919 networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; 3920 } 3921 } 3922 return networkType; 3923 } 3924 } 3925 3926 /** 3927 * Determines if the current call is a cross sim call 3928 * Note: This depends on the RIL exposing the 3929 * {@link ImsCallProfile#EXTRA_IS_CROSS_SIM_CALL} extra. 3930 * 3931 * @return {@code true} if the call is Cross SIM, {@code false} otherwise. 3932 */ isCrossSimCall()3933 public boolean isCrossSimCall() { 3934 synchronized(mLockObj) { 3935 if (mCallProfile == null) { 3936 return false; 3937 } 3938 return mCallProfile.getCallExtraBoolean( 3939 ImsCallProfile.EXTRA_IS_CROSS_SIM_CALL, 3940 false); 3941 } 3942 } 3943 3944 /** 3945 * Log a string to the radio buffer at the info level. 3946 * @param s The message to log 3947 */ logi(String s)3948 private void logi(String s) { 3949 Log.i(TAG, appendImsCallInfoToString(s)); 3950 } 3951 3952 /** 3953 * Log a string to the radio buffer at the debug level. 3954 * @param s The message to log 3955 */ logd(String s)3956 private void logd(String s) { 3957 Log.d(TAG, appendImsCallInfoToString(s)); 3958 } 3959 3960 /** 3961 * Log a string to the radio buffer at the verbose level. 3962 * @param s The message to log 3963 */ logv(String s)3964 private void logv(String s) { 3965 Log.v(TAG, appendImsCallInfoToString(s)); 3966 } 3967 3968 /** 3969 * Log a string to the radio buffer at the error level. 3970 * @param s The message to log 3971 */ loge(String s)3972 private void loge(String s) { 3973 Log.e(TAG, appendImsCallInfoToString(s)); 3974 } 3975 3976 /** 3977 * Log a string to the radio buffer at the error level with a throwable 3978 * @param s The message to log 3979 * @param t The associated throwable 3980 */ loge(String s, Throwable t)3981 private void loge(String s, Throwable t) { 3982 Log.e(TAG, appendImsCallInfoToString(s), t); 3983 } 3984 } 3985