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