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