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