/*
 * Copyright (c) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ims.internal;

import android.os.Message;
import android.os.RemoteException;
import android.telecom.Connection;

import java.util.Objects;
import android.util.Log;
import com.android.ims.ImsCallProfile;
import com.android.ims.ImsConferenceState;
import com.android.ims.ImsReasonInfo;
import com.android.ims.ImsStreamMediaProfile;
import com.android.ims.ImsSuppServiceNotification;

/**
 * Provides the call initiation/termination, and media exchange between two IMS endpoints.
 * It directly communicates with IMS service which implements the IMS protocol behavior.
 *
 * @hide
 */
public class ImsCallSession {
    private static final String TAG = "ImsCallSession";

    /**
     * Defines IMS call session state.
     */
    public static class State {
        public static final int IDLE = 0;
        public static final int INITIATED = 1;
        public static final int NEGOTIATING = 2;
        public static final int ESTABLISHING = 3;
        public static final int ESTABLISHED = 4;

        public static final int RENEGOTIATING = 5;
        public static final int REESTABLISHING = 6;

        public static final int TERMINATING = 7;
        public static final int TERMINATED = 8;

        public static final int INVALID = (-1);

        /**
         * Converts the state to string.
         */
        public static String toString(int state) {
            switch (state) {
                case IDLE:
                    return "IDLE";
                case INITIATED:
                    return "INITIATED";
                case NEGOTIATING:
                    return "NEGOTIATING";
                case ESTABLISHING:
                    return "ESTABLISHING";
                case ESTABLISHED:
                    return "ESTABLISHED";
                case RENEGOTIATING:
                    return "RENEGOTIATING";
                case REESTABLISHING:
                    return "REESTABLISHING";
                case TERMINATING:
                    return "TERMINATING";
                case TERMINATED:
                    return "TERMINATED";
                default:
                    return "UNKNOWN";
            }
        }

        private State() {
        }
    }

    /**
     * Listener for events relating to an IMS session, such as when a session is being
     * recieved ("on ringing") or a call is outgoing ("on calling").
     * <p>Many of these events are also received by {@link ImsCall.Listener}.</p>
     */
    public static class Listener {
        /**
         * Called when a request is sent out to initiate a new session
         * and 1xx response is received from the network.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionProgressing(ImsCallSession session,
                ImsStreamMediaProfile profile) {
            // no-op
        }

        /**
         * Called when the session is established.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionStarted(ImsCallSession session,
                ImsCallProfile profile) {
            // no-op
        }

        /**
         * Called when the session establishment is failed.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the session establishment failure
         */
        public void callSessionStartFailed(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
        }

        /**
         * Called when the session is terminated.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the session termination
         */
        public void callSessionTerminated(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
        }

        /**
         * Called when the session is in hold.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionHeld(ImsCallSession session,
                ImsCallProfile profile) {
        }

        /**
         * Called when the session hold is failed.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the session hold failure
         */
        public void callSessionHoldFailed(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
        }

        /**
         * Called when the session hold is received from the remote user.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionHoldReceived(ImsCallSession session,
                ImsCallProfile profile) {
        }

        /**
         * Called when the session resume is done.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionResumed(ImsCallSession session,
                ImsCallProfile profile) {
        }

        /**
         * Called when the session resume is failed.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the session resume failure
         */
        public void callSessionResumeFailed(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
        }

        /**
         * Called when the session resume is received from the remote user.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionResumeReceived(ImsCallSession session,
                ImsCallProfile profile) {
        }

        /**
         * Called when the session merge has been started.  At this point, the {@code newSession}
         * represents the session which has been initiated to the IMS conference server for the
         * new merged conference.
         *
         * @param session the session object that carries out the IMS session
         * @param newSession the session object that is merged with an active & hold session
         */
        public void callSessionMergeStarted(ImsCallSession session,
                ImsCallSession newSession, ImsCallProfile profile) {
        }

        /**
         * Called when the session merge is successful and the merged session is active.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionMergeComplete(ImsCallSession session) {
        }

        /**
         * Called when the session merge has failed.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the call merge failure
         */
        public void callSessionMergeFailed(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
        }

        /**
         * Called when the session is updated (except for hold/unhold).
         *
         * @param call the call object that carries out the IMS call
         */
        public void callSessionUpdated(ImsCallSession session,
                ImsCallProfile profile) {
        }

        /**
         * Called when the session update is failed.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the session update failure
         */
        public void callSessionUpdateFailed(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
        }

        /**
         * Called when the session update is received from the remote user.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionUpdateReceived(ImsCallSession session,
                ImsCallProfile profile) {
            // no-op
        }

        /**
         * Called when the session is extended to the conference session.
         *
         * @param session the session object that carries out the IMS session
         * @param newSession the session object that is extended to the conference
         *      from the active session
         */
        public void callSessionConferenceExtended(ImsCallSession session,
                ImsCallSession newSession, ImsCallProfile profile) {
        }

        /**
         * Called when the conference extension is failed.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the conference extension failure
         */
        public void callSessionConferenceExtendFailed(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
        }

        /**
         * Called when the conference extension is received from the remote user.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionConferenceExtendReceived(ImsCallSession session,
                ImsCallSession newSession, ImsCallProfile profile) {
            // no-op
        }

        /**
         * Called when the invitation request of the participants is delivered to the conference
         * server.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
            // no-op
        }

        /**
         * Called when the invitation request of the participants is failed.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the conference invitation failure
         */
        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
            // no-op
        }

        /**
         * Called when the removal request of the participants is delivered to the conference
         * server.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
            // no-op
        }

        /**
         * Called when the removal request of the participants is failed.
         *
         * @param session the session object that carries out the IMS session
         * @param reasonInfo detailed reason of the conference removal failure
         */
        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
                ImsReasonInfo reasonInfo) {
            // no-op
        }

        /**
         * Called when the conference state is updated.
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionConferenceStateUpdated(ImsCallSession session,
                ImsConferenceState state) {
            // no-op
        }

        /**
         * Called when the USSD message is received from the network.
         *
         * @param mode mode of the USSD message (REQUEST / NOTIFY)
         * @param ussdMessage USSD message
         */
        public void callSessionUssdMessageReceived(ImsCallSession session,
                int mode, String ussdMessage) {
            // no-op
        }

        /**
         * Called when session access technology changes
         *
         * @param session IMS session object
         * @param srcAccessTech original access technology
         * @param targetAccessTech new access technology
         * @param reasonInfo
         */
        public void callSessionHandover(ImsCallSession session,
                                 int srcAccessTech, int targetAccessTech,
                                 ImsReasonInfo reasonInfo) {
            // no-op
        }

        /**
         * Called when session access technology change fails
         *
         * @param session IMS session object
         * @param srcAccessTech original access technology
         * @param targetAccessTech new access technology
         * @param reasonInfo handover failure reason
         */
        public void callSessionHandoverFailed(ImsCallSession session,
                                       int srcAccessTech, int targetAccessTech,
                                       ImsReasonInfo reasonInfo) {
            // no-op
        }

        /**
         * Called when TTY mode of remote party changed
         *
         * @param session IMS session object
         * @param mode TTY mode of remote party
         */
        public void callSessionTtyModeReceived(ImsCallSession session,
                                       int mode) {
            // no-op
        }

        /**
         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
         *
         * @param session The call session.
         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
         *      otherwise.
         */
        public void callSessionMultipartyStateChanged(ImsCallSession session,
                boolean isMultiParty) {
            // no-op
        }

        /**
         * Called when the session supplementary service is received
         *
         * @param session the session object that carries out the IMS session
         */
        public void callSessionSuppServiceReceived(ImsCallSession session,
                ImsSuppServiceNotification suppServiceInfo) {
        }
    }

    private final IImsCallSession miSession;
    private boolean mClosed = false;
    private Listener mListener;

    public ImsCallSession(IImsCallSession iSession) {
        miSession = iSession;

        if (iSession != null) {
            try {
                iSession.setListener(new IImsCallSessionListenerProxy());
            } catch (RemoteException e) {
            }
        } else {
            mClosed = true;
        }
    }

    public ImsCallSession(IImsCallSession iSession, Listener listener) {
        this(iSession);
        setListener(listener);
    }

    /**
     * Closes this object. This object is not usable after being closed.
     */
    public synchronized void close() {
        if (mClosed) {
            return;
        }

        try {
            miSession.close();
            mClosed = true;
        } catch (RemoteException e) {
        }
    }

    /**
     * Gets the call ID of the session.
     *
     * @return the call ID
     */
    public String getCallId() {
        if (mClosed) {
            return null;
        }

        try {
            return miSession.getCallId();
        } catch (RemoteException e) {
            return null;
        }
    }

    /**
     * Gets the call profile that this session is associated with
     *
     * @return the call profile that this session is associated with
     */
    public ImsCallProfile getCallProfile() {
        if (mClosed) {
            return null;
        }

        try {
            return miSession.getCallProfile();
        } catch (RemoteException e) {
            return null;
        }
    }

    /**
     * Gets the local call profile that this session is associated with
     *
     * @return the local call profile that this session is associated with
     */
    public ImsCallProfile getLocalCallProfile() {
        if (mClosed) {
            return null;
        }

        try {
            return miSession.getLocalCallProfile();
        } catch (RemoteException e) {
            return null;
        }
    }

    /**
     * Gets the remote call profile that this session is associated with
     *
     * @return the remote call profile that this session is associated with
     */
    public ImsCallProfile getRemoteCallProfile() {
        if (mClosed) {
            return null;
        }

        try {
            return miSession.getRemoteCallProfile();
        } catch (RemoteException e) {
            return null;
        }
    }

    /**
     * Gets the video call provider for the session.
     *
     * @return The video call provider.
     */
    public IImsVideoCallProvider getVideoCallProvider() {
        if (mClosed) {
            return null;
        }

        try {
            return miSession.getVideoCallProvider();
        } catch (RemoteException e) {
            return null;
        }
    }

    /**
     * Gets the value associated with the specified property of this session.
     *
     * @return the string value associated with the specified property
     */
    public String getProperty(String name) {
        if (mClosed) {
            return null;
        }

        try {
            return miSession.getProperty(name);
        } catch (RemoteException e) {
            return null;
        }
    }

    /**
     * Gets the session state.
     * The value returned must be one of the states in {@link State}.
     *
     * @return the session state
     */
    public int getState() {
        if (mClosed) {
            return State.INVALID;
        }

        try {
            return miSession.getState();
        } catch (RemoteException e) {
            return State.INVALID;
        }
    }

    /**
     * Determines if the {@link ImsCallSession} is currently alive (e.g. not in a terminated or
     * closed state).
     *
     * @return {@code True} if the session is alive.
     */
    public boolean isAlive() {
        if (mClosed) {
            return false;
        }

        int state = getState();
        switch (state) {
            case State.IDLE:
            case State.INITIATED:
            case State.NEGOTIATING:
            case State.ESTABLISHING:
            case State.ESTABLISHED:
            case State.RENEGOTIATING:
            case State.REESTABLISHING:
                return true;
            default:
                return false;
        }
    }

    /**
     * Gets the native IMS call session.
     * @hide
     */
    public IImsCallSession getSession() {
        return miSession;
    }

    /**
     * Checks if the session is in call.
     *
     * @return true if the session is in call
     */
    public boolean isInCall() {
        if (mClosed) {
            return false;
        }

        try {
            return miSession.isInCall();
        } catch (RemoteException e) {
            return false;
        }
    }

    /**
     * Sets the listener to listen to the session events. A {@link ImsCallSession}
     * can only hold one listener at a time. Subsequent calls to this method
     * override the previous listener.
     *
     * @param listener to listen to the session events of this object
     */
    public void setListener(Listener listener) {
        mListener = listener;
    }

    /**
     * Mutes or unmutes the mic for the active call.
     *
     * @param muted true if the call is muted, false otherwise
     */
    public void setMute(boolean muted) {
        if (mClosed) {
            return;
        }

        try {
            miSession.setMute(muted);
        } catch (RemoteException e) {
        }
    }

    /**
     * Initiates an IMS call with the specified target and call profile.
     * The session listener is called back upon defined session events.
     * The method is only valid to call when the session state is in
     * {@link ImsCallSession#State#IDLE}.
     *
     * @param callee dialed string to make the call to
     * @param profile call profile to make the call with the specified service type,
     *      call type and media information
     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
     */
    public void start(String callee, ImsCallProfile profile) {
        if (mClosed) {
            return;
        }

        try {
            miSession.start(callee, profile);
        } catch (RemoteException e) {
        }
    }

    /**
     * Initiates an IMS conference call with the specified target and call profile.
     * The session listener is called back upon defined session events.
     * The method is only valid to call when the session state is in
     * {@link ImsCallSession#State#IDLE}.
     *
     * @param participants participant list to initiate an IMS conference call
     * @param profile call profile to make the call with the specified service type,
     *      call type and media information
     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
     */
    public void start(String[] participants, ImsCallProfile profile) {
        if (mClosed) {
            return;
        }

        try {
            miSession.startConference(participants, profile);
        } catch (RemoteException e) {
        }
    }

    /**
     * Accepts an incoming call or session update.
     *
     * @param callType call type specified in {@link ImsCallProfile} to be answered
     * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered
     * @see Listener#callSessionStarted
     */
    public void accept(int callType, ImsStreamMediaProfile profile) {
        if (mClosed) {
            return;
        }

        try {
            miSession.accept(callType, profile);
        } catch (RemoteException e) {
        }
    }

    /**
     * Rejects an incoming call or session update.
     *
     * @param reason reason code to reject an incoming call
     * @see Listener#callSessionStartFailed
     */
    public void reject(int reason) {
        if (mClosed) {
            return;
        }

        try {
            miSession.reject(reason);
        } catch (RemoteException e) {
        }
    }

    /**
     * Terminates a call.
     *
     * @see Listener#callSessionTerminated
     */
    public void terminate(int reason) {
        if (mClosed) {
            return;
        }

        try {
            miSession.terminate(reason);
        } catch (RemoteException e) {
        }
    }

    /**
     * Puts a call on hold. When it succeeds, {@link Listener#callSessionHeld} is called.
     *
     * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call
     * @see Listener#callSessionHeld, Listener#callSessionHoldFailed
     */
    public void hold(ImsStreamMediaProfile profile) {
        if (mClosed) {
            return;
        }

        try {
            miSession.hold(profile);
        } catch (RemoteException e) {
        }
    }

    /**
     * Continues a call that's on hold. When it succeeds,
     * {@link Listener#callSessionResumed} is called.
     *
     * @param profile stream media profile {@link ImsStreamMediaProfile} to resume the call
     * @see Listener#callSessionResumed, Listener#callSessionResumeFailed
     */
    public void resume(ImsStreamMediaProfile profile) {
        if (mClosed) {
            return;
        }

        try {
            miSession.resume(profile);
        } catch (RemoteException e) {
        }
    }

    /**
     * Merges the active & hold call. When it succeeds,
     * {@link Listener#callSessionMergeStarted} is called.
     *
     * @see Listener#callSessionMergeStarted , Listener#callSessionMergeFailed
     */
    public void merge() {
        if (mClosed) {
            return;
        }

        try {
            miSession.merge();
        } catch (RemoteException e) {
        }
    }

    /**
     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
     *
     * @param callType call type specified in {@link ImsCallProfile} to be updated
     * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated
     * @see Listener#callSessionUpdated, Listener#callSessionUpdateFailed
     */
    public void update(int callType, ImsStreamMediaProfile profile) {
        if (mClosed) {
            return;
        }

        try {
            miSession.update(callType, profile);
        } catch (RemoteException e) {
        }
    }

    /**
     * Extends this call to the conference call with the specified recipients.
     *
     * @participants participant list to be invited to the conference call after extending the call
     * @see Listener#sessionConferenceExtened, Listener#sessionConferenceExtendFailed
     */
    public void extendToConference(String[] participants) {
        if (mClosed) {
            return;
        }

        try {
            miSession.extendToConference(participants);
        } catch (RemoteException e) {
        }
    }

    /**
     * Requests the conference server to invite an additional participants to the conference.
     *
     * @participants participant list to be invited to the conference call
     * @see Listener#sessionInviteParticipantsRequestDelivered,
     *      Listener#sessionInviteParticipantsRequestFailed
     */
    public void inviteParticipants(String[] participants) {
        if (mClosed) {
            return;
        }

        try {
            miSession.inviteParticipants(participants);
        } catch (RemoteException e) {
        }
    }

    /**
     * Requests the conference server to remove the specified participants from the conference.
     *
     * @param participants participant list to be removed from the conference call
     * @see Listener#sessionRemoveParticipantsRequestDelivered,
     *      Listener#sessionRemoveParticipantsRequestFailed
     */
    public void removeParticipants(String[] participants) {
        if (mClosed) {
            return;
        }

        try {
            miSession.removeParticipants(participants);
        } catch (RemoteException e) {
        }
    }


    /**
     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
     * and event flash to 16. Currently, event flash is not supported.
     *
     * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
     */
    public void sendDtmf(char c, Message result) {
        if (mClosed) {
            return;
        }

        try {
            miSession.sendDtmf(c, result);
        } catch (RemoteException e) {
        }
    }

    /**
     * Starts a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
     * and event flash to 16. Currently, event flash is not supported.
     *
     * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
     */
    public void startDtmf(char c) {
        if (mClosed) {
            return;
        }

        try {
            miSession.startDtmf(c);
        } catch (RemoteException e) {
        }
    }

    /**
     * Stops a DTMF code.
     */
    public void stopDtmf() {
        if (mClosed) {
            return;
        }

        try {
            miSession.stopDtmf();
        } catch (RemoteException e) {
        }
    }

    /**
     * Sends an USSD message.
     *
     * @param ussdMessage USSD message to send
     */
    public void sendUssd(String ussdMessage) {
        if (mClosed) {
            return;
        }

        try {
            miSession.sendUssd(ussdMessage);
        } catch (RemoteException e) {
        }
    }

    /**
     * Determines if the session is multiparty.
     *
     * @return {@code True} if the session is multiparty.
     */
    public boolean isMultiparty() {
        if (mClosed) {
            return false;
        }

        try {
            return miSession.isMultiparty();
        } catch (RemoteException e) {
            return false;
        }
    }

    /**
     * A listener type for receiving notification on IMS call session events.
     * When an event is generated for an {@link IImsCallSession},
     * the application is notified by having one of the methods called on
     * the {@link IImsCallSessionListener}.
     */
    private class IImsCallSessionListenerProxy extends IImsCallSessionListener.Stub {
        /**
         * Notifies the result of the basic session operation (setup / terminate).
         */
        @Override
        public void callSessionProgressing(IImsCallSession session,
                ImsStreamMediaProfile profile) {
            if (mListener != null) {
                mListener.callSessionProgressing(ImsCallSession.this, profile);
            }
        }

        @Override
        public void callSessionStarted(IImsCallSession session,
                ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionStarted(ImsCallSession.this, profile);
            }
        }

        @Override
        public void callSessionStartFailed(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
            }
        }

        @Override
        public void callSessionTerminated(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionTerminated(ImsCallSession.this, reasonInfo);
            }
        }

        /**
         * Notifies the result of the call hold/resume operation.
         */
        @Override
        public void callSessionHeld(IImsCallSession session,
                ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionHeld(ImsCallSession.this, profile);
            }
        }

        @Override
        public void callSessionHoldFailed(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo);
            }
        }

        @Override
        public void callSessionHoldReceived(IImsCallSession session,
                ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionHoldReceived(ImsCallSession.this, profile);
            }
        }

        @Override
        public void callSessionResumed(IImsCallSession session,
                ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionResumed(ImsCallSession.this, profile);
            }
        }

        @Override
        public void callSessionResumeFailed(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo);
            }
        }

        @Override
        public void callSessionResumeReceived(IImsCallSession session,
                ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionResumeReceived(ImsCallSession.this, profile);
            }
        }

        /**
         * Notifies the start of a call merge operation.
         *
         * @param session The call session.
         * @param newSession The merged call session.
         * @param profile The call profile.
         */
        @Override
        public void callSessionMergeStarted(IImsCallSession session,
                IImsCallSession newSession, ImsCallProfile profile) {
            // This callback can be used for future use to add additional
            // functionality that may be needed between conference start and complete
            Log.d(TAG, "callSessionMergeStarted");
        }

        /**
         * Notifies the successful completion of a call merge operation.
         *
         * @param session The call session.
         */
        @Override
        public void callSessionMergeComplete(IImsCallSession newSession) {
            if (mListener != null) {
                if (newSession != null) {
                    // Check if the active session is the same session that was
                    // active before the merge request was sent.
                    ImsCallSession validActiveSession = ImsCallSession.this;
                    try {
                        if (!Objects.equals(miSession.getCallId(), newSession.getCallId())) {
                            // New session created after conference
                            validActiveSession = new ImsCallSession(newSession);
                        }
                    } catch (RemoteException rex) {
                        Log.e(TAG, "callSessionMergeComplete: exception for getCallId!");
                    }
                    mListener.callSessionMergeComplete(validActiveSession);
               } else {
                   // Session already exists. Hence no need to pass
                   mListener.callSessionMergeComplete(null);
               }
            }
        }

        /**
         * Notifies of a failure to perform a call merge operation.
         *
         * @param session The call session.
         * @param reasonInfo The merge failure reason.
         */
        @Override
        public void callSessionMergeFailed(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo);
            }
        }

        /**
         * Notifies the result of call upgrade / downgrade or any other call updates.
         */
        @Override
        public void callSessionUpdated(IImsCallSession session,
                ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionUpdated(ImsCallSession.this, profile);
            }
        }

        @Override
        public void callSessionUpdateFailed(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo);
            }
        }

        @Override
        public void callSessionUpdateReceived(IImsCallSession session,
                ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionUpdateReceived(ImsCallSession.this, profile);
            }
        }

        /**
         * Notifies the result of conference extension.
         */
        @Override
        public void callSessionConferenceExtended(IImsCallSession session,
                IImsCallSession newSession, ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionConferenceExtended(ImsCallSession.this,
                        new ImsCallSession(newSession), profile);
            }
        }

        @Override
        public void callSessionConferenceExtendFailed(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo);
            }
        }

        @Override
        public void callSessionConferenceExtendReceived(IImsCallSession session,
                IImsCallSession newSession, ImsCallProfile profile) {
            if (mListener != null) {
                mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
                        new ImsCallSession(newSession), profile);
            }
        }

        /**
         * Notifies the result of the participant invitation / removal to/from
         * the conference session.
         */
        @Override
        public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) {
            if (mListener != null) {
                mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this);
            }
        }

        @Override
        public void callSessionInviteParticipantsRequestFailed(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
                        reasonInfo);
            }
        }

        @Override
        public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) {
            if (mListener != null) {
                mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this);
            }
        }

        @Override
        public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session,
                ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
                        reasonInfo);
            }
        }

        /**
         * Notifies the changes of the conference info. in the conference session.
         */
        @Override
        public void callSessionConferenceStateUpdated(IImsCallSession session,
                ImsConferenceState state) {
            if (mListener != null) {
                mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state);
            }
        }

        /**
         * Notifies the incoming USSD message.
         */
        @Override
        public void callSessionUssdMessageReceived(IImsCallSession session,
                int mode, String ussdMessage) {
            if (mListener != null) {
                mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage);
            }
        }

        /**
         * Notifies of handover information for this call
         */
        @Override
        public void callSessionHandover(IImsCallSession session,
                                 int srcAccessTech, int targetAccessTech,
                                 ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionHandover(ImsCallSession.this, srcAccessTech,
                        targetAccessTech, reasonInfo);
            }
        }

        /**
         * Notifies of handover failure info for this call
         */
        @Override
        public void callSessionHandoverFailed(IImsCallSession session,
                                       int srcAccessTech, int targetAccessTech,
                                       ImsReasonInfo reasonInfo) {
            if (mListener != null) {
                mListener.callSessionHandoverFailed(ImsCallSession.this, srcAccessTech,
                        targetAccessTech, reasonInfo);
            }
        }

        /**
         * Notifies the TTY mode received from remote party.
         */
        @Override
        public void callSessionTtyModeReceived(IImsCallSession session,
                int mode) {
            if (mListener != null) {
                mListener.callSessionTtyModeReceived(ImsCallSession.this, mode);
            }
        }

        /**
         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
         *
         * @param session The call session.
         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
         *      otherwise.
         */
        public void callSessionMultipartyStateChanged(IImsCallSession session,
                boolean isMultiParty) {

            if (mListener != null) {
                mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty);
            }
        }

        @Override
        public void callSessionSuppServiceReceived(IImsCallSession session,
                ImsSuppServiceNotification suppServiceInfo ) {
            if (mListener != null) {
                mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo);
            }
        }

    }

    /**
     * Provides a string representation of the {@link ImsCallSession}.  Primarily intended for
     * use in log statements.
     *
     * @return String representation of session.
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[ImsCallSession objId:");
        sb.append(System.identityHashCode(this));
        sb.append(" state:");
        sb.append(State.toString(getState()));
        sb.append(" callId:");
        sb.append(getCallId());
        sb.append("]");
        return sb.toString();
    }
}
