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