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