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