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