/*
 * Copyright (C) 2017 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 android.telephony.ims.stub;

import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsCallSessionListener;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsStreamMediaProfile;
import android.telephony.ims.ImsVideoCallProvider;
import android.telephony.ims.RtpHeaderExtension;
import android.telephony.ims.RtpHeaderExtensionType;
import android.telephony.ims.aidl.IImsCallSessionListener;
import android.util.ArraySet;
import android.util.Log;

import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsVideoCallProvider;
import com.android.internal.telephony.util.TelephonyUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;

/**
 * Base implementation of IImsCallSession, which implements stub versions of the methods available.
 *
 * Override the methods that your implementation of ImsCallSession supports.
 *
 * @hide
 */
@SystemApi
// DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
// will break other implementations of ImsCallSession maintained by other ImsServices.
public class ImsCallSessionImplBase implements AutoCloseable {

    private static final String LOG_TAG = "ImsCallSessionImplBase";
    /**
     * Notify USSD Mode.
     */
    public static final int USSD_MODE_NOTIFY = 0;
    /**
     * Request USSD Mode
     */
    public static final int USSD_MODE_REQUEST = 1;

    /** @hide */
    @IntDef(
        prefix = "MEDIA_STREAM_TYPE_",
        value = {
            MEDIA_STREAM_TYPE_AUDIO,
            MEDIA_STREAM_TYPE_VIDEO
        })
    @Retention(RetentionPolicy.SOURCE)
    public @interface MediaStreamType {}

    /**
     * Media Stream Type - Audio
     * @hide
     */
    public static final int MEDIA_STREAM_TYPE_AUDIO = 1;
    /**
     * Media Stream Type - Video
     * @hide
     */
    public static final int MEDIA_STREAM_TYPE_VIDEO = 2;

    /** @hide */
    @IntDef(
        prefix = "MEDIA_STREAM_DIRECTION_",
        value = {
            MEDIA_STREAM_DIRECTION_UPLINK,
            MEDIA_STREAM_DIRECTION_DOWNLINK
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface MediaStreamDirection {}

    /**
     * Media Stream Direction - Uplink
     * @hide
     */
    public static final int MEDIA_STREAM_DIRECTION_UPLINK = 1;
    /**
     * Media Stream Direction - Downlink
     * @hide
     */
    public static final int MEDIA_STREAM_DIRECTION_DOWNLINK = 2;

    /**
     * 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";
            }
        }

        /**
         * @hide
         */
        private State() {
        }
    }

    private Executor mExecutor = Runnable::run;

    // Non-final for injection by tests
    private IImsCallSession mServiceImpl = new IImsCallSession.Stub() {
        @Override
        public void close() {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.close(), "close");
        }

        @Override
        public String getCallId() {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getCallId(),
                    "getCallId");
        }

        @Override
        public ImsCallProfile getCallProfile() {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getCallProfile(),
                    "getCallProfile");
        }

        @Override
        public ImsCallProfile getLocalCallProfile() {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this
                    .getLocalCallProfile(), "getLocalCallProfile");
        }

        @Override
        public ImsCallProfile getRemoteCallProfile() {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this
                    .getRemoteCallProfile(), "getRemoteCallProfile");
        }

        @Override
        public String getProperty(String name) {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getProperty(name),
                    "getProperty");
        }

        @Override
        public int getState() {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getState(),
                    "getState");
        }

        @Override
        public boolean isInCall() {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.isInCall(),
                    "isInCall");
        }

        @Override
        public void setListener(IImsCallSessionListener listener) {
            ImsCallSessionListener iCSL = new ImsCallSessionListener(listener);
            iCSL.setDefaultExecutor(mExecutor);
            executeMethodAsync(() -> ImsCallSessionImplBase.this.setListener(iCSL), "setListener");
        }

        @Override
        public void setMute(boolean muted) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.setMute(muted), "setMute");
        }

        @Override
        public void start(String callee, ImsCallProfile profile) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.start(callee, profile), "start");
        }

        @Override
        public void startConference(String[] participants, ImsCallProfile profile) throws
                RemoteException {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.startConference(participants,
                    profile), "startConference");
        }

        @Override
        public void accept(int callType, ImsStreamMediaProfile profile) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.accept(callType, profile),
                    "accept");
        }

        @Override
        public void deflect(String deflectNumber) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.deflect(deflectNumber),
                    "deflect");
        }

        @Override
        public void reject(int reason) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.reject(reason), "reject");
        }

        @Override
        public void transfer(@NonNull String number, boolean isConfirmationRequired) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.transfer(number,
                    isConfirmationRequired), "transfer");
        }

        @Override
        public void consultativeTransfer(@NonNull IImsCallSession transferToSession) {
            executeMethodAsync(() -> {
                ImsCallSessionImplBase otherSession = new ImsCallSessionImplBase();
                otherSession.setServiceImpl(transferToSession);
                ImsCallSessionImplBase.this.transfer(otherSession);
            }, "consultativeTransfer");
        }

        @Override
        public void terminate(int reason) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.terminate(reason), "terminate");
        }

        @Override
        public void hold(ImsStreamMediaProfile profile) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.hold(profile), "hold");
        }

        @Override
        public void resume(ImsStreamMediaProfile profile) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.resume(profile), "resume");
        }

        @Override
        public void merge() {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.merge(), "merge");
        }

        @Override
        public void update(int callType, ImsStreamMediaProfile profile) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.update(callType, profile),
                    "update");
        }

        @Override
        public void extendToConference(String[] participants) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.extendToConference(participants),
                    "extendToConference");
        }

        @Override
        public void inviteParticipants(String[] participants) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.inviteParticipants(participants),
                    "inviteParticipants");
        }

        @Override
        public void removeParticipants(String[] participants) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.removeParticipants(participants),
                    "removeParticipants");
        }

        @Override
        public void sendDtmf(char c, Message result) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendDtmf(c, result), "sendDtmf");
        }

        @Override
        public void startDtmf(char c) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.startDtmf(c), "startDtmf");
        }

        @Override
        public void stopDtmf() {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.stopDtmf(), "stopDtmf");
        }

        @Override
        public void sendUssd(String ussdMessage) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendUssd(ussdMessage), "sendUssd");
        }

        @Override
        public IImsVideoCallProvider getVideoCallProvider() {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this
                    .getVideoCallProvider(), "getVideoCallProvider");
        }

        @Override
        public boolean isMultiparty() {
            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.isMultiparty(),
                    "isMultiparty");
        }

        @Override
        public void sendRttModifyRequest(ImsCallProfile toProfile) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttModifyRequest(toProfile),
                    "sendRttModifyRequest");
        }

        @Override
        public void sendRttModifyResponse(boolean status) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttModifyResponse(status),
                    "sendRttModifyResponse");
        }

        @Override
        public void sendRttMessage(String rttMessage) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttMessage(rttMessage),
                    "sendRttMessage");
        }

        @Override
        public void sendRtpHeaderExtensions(@NonNull List<RtpHeaderExtension> extensions) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRtpHeaderExtensions(
                    new ArraySet<RtpHeaderExtension>(extensions)), "sendRtpHeaderExtensions");
        }

        @Override
        public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
            executeMethodAsync(() -> ImsCallSessionImplBase.this.callSessionNotifyAnbr(
                    mediaType, direction, bitsPerSecond), "callSessionNotifyAnbr");
        }

        // Call the methods with a clean calling identity on the executor and wait indefinitely for
        // the future to return.
        private void executeMethodAsync(Runnable r, String errorLogName) {
            try {
                CompletableFuture.runAsync(
                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
            } catch (CancellationException | CompletionException e) {
                Log.w(LOG_TAG, "ImsCallSessionImplBase Binder - " + errorLogName + " exception: "
                        + e.getMessage());
            }
        }

        private <T> T executeMethodAsyncForResult(Supplier<T> r,
                String errorLogName) {
            CompletableFuture<T> future = CompletableFuture.supplyAsync(
                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
            try {
                return future.get();
            } catch (ExecutionException | InterruptedException e) {
                Log.w(LOG_TAG, "ImsCallSessionImplBase Binder - " + errorLogName + " exception: "
                        + e.getMessage());
                return null;
            }
        }
    };

    /**
     * @hide
     */
    public final void setListener(IImsCallSessionListener listener) throws RemoteException {
        setListener(new ImsCallSessionListener(listener));
    }

    /**
     * Sets the listener to listen to the session events. An {@link ImsCallSession}
     * can only hold one listener at a time. Subsequent calls to this method
     * override the previous listener.
     *
     * @param listener {@link ImsCallSessionListener} used to notify the framework of updates
     * to the ImsCallSession

     * @deprecated use {@link android.telephony.ims.feature.MmTelFeature#notifyIncomingCall(
     * ImsCallSessionImplBase, String, Bundle)} to get the listener instead
     */
    @Deprecated
    public void setListener(ImsCallSessionListener listener) {
    }

    /**
     * Closes the object. This {@link ImsCallSessionImplBase} is not usable after being closed.
     */
    @Override
    public void close() {

    }

    /**
     * @return A String containing the unique call ID of this {@link ImsCallSessionImplBase}.
     */
    public String getCallId() {
        return null;
    }

    /**
     * @return The {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is associated
     * with.
     */
    public ImsCallProfile getCallProfile() {
        return null;
    }

    /**
     * @return The local {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is
     * associated with.
     */
    public ImsCallProfile getLocalCallProfile() {
        return null;
    }

    /**
     * @return The remote {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is
     * associated with.
     */
    public ImsCallProfile getRemoteCallProfile() {
        return null;
    }

    /**
     * @param name The String extra key.
     * @return The string extra value associated with the specified property.
     */
    public String getProperty(String name) {
        return null;
    }

    /**
     * @return The {@link ImsCallSessionImplBase} state, defined in
     * {@link ImsCallSessionImplBase.State}.
     */
    public int getState() {
        return ImsCallSessionImplBase.State.INVALID;
    }

    /**
     * @return true if the {@link ImsCallSessionImplBase} is in a call, false otherwise.
     */
    public boolean isInCall() {
        return false;
    }

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

    /**
     * Initiates an IMS call with the specified number and call profile.
     * The session listener set in {@link #setListener(ImsCallSessionListener)} is called back upon
     * defined session events.
     * 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 {@link ImsCallSession.Listener#callSessionStarted},
     * {@link ImsCallSession.Listener#callSessionStartFailed}
     */
    public void start(String callee, ImsCallProfile profile) {
    }

    /**
     * Initiates an IMS call with the specified participants and call profile.
     * The session listener set in {@link #setListener(ImsCallSessionListener)} 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 {@link ImsCallSession.Listener#callSessionStarted},
     * {@link ImsCallSession.Listener#callSessionStartFailed}
     */
    public void startConference(String[] participants, ImsCallProfile profile) {
    }

    /**
     * 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 {@link ImsCallSession.Listener#callSessionStarted}
     */
    public void accept(int callType, ImsStreamMediaProfile profile) {
    }

    /**
     * Deflects an incoming call.
     *
     * @param deflectNumber number to deflect the call
     */
    public void deflect(String deflectNumber) {
    }

    /**
     * Rejects an incoming call or session update.
     *
     * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
     *               The {@link android.telecom.InCallService} (dialer app) can use the
     *               {@link android.telecom.Call#reject(int)} API to reject a call while specifying
     *               a user-indicated reason for rejecting the call.
     *               Normal call declines ({@link android.telecom.Call#REJECT_REASON_DECLINED}) will
     *               map to {@link ImsReasonInfo#CODE_USER_DECLINE}.
     *               Unwanted calls ({@link android.telecom.Call#REJECT_REASON_UNWANTED}) will map
     *               to {@link ImsReasonInfo#CODE_SIP_USER_MARKED_UNWANTED}.
     * {@link ImsCallSession.Listener#callSessionStartFailed}
     */
    public void reject(int reason) {
    }

    /**
     * Transfer an established call to given number
     *
     * @param number number to transfer the call
     * @param isConfirmationRequired if {@code True}, indicates a confirmed transfer,
     * if {@code False} it indicates an unconfirmed transfer.
     * @hide
     */
    public void transfer(@NonNull String number, boolean isConfirmationRequired) {
    }

    /**
     * Transfer an established call to another call session
     *
     * @param otherSession The other ImsCallSession to transfer the ongoing session to.
     * @hide
     */
    public void transfer(@NonNull ImsCallSessionImplBase otherSession) {
    }

    /**
     * Terminates a call.
     *
     * @param reason reason code to terminate a call, defined in {@link ImsReasonInfo}.
     *
     * @see {@link ImsCallSession.Listener#callSessionTerminated}
     */
    public void terminate(int reason) {
    }

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

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

    /**
     * Merges the active and held call. When the merge starts,
     * {@link ImsCallSession.Listener#callSessionMergeStarted} is called.
     * {@link ImsCallSession.Listener#callSessionMergeComplete} is called if the merge is
     * successful, and {@link ImsCallSession.Listener#callSessionMergeFailed} is called if the merge
     * fails.
     *
     * @see {@link ImsCallSession.Listener#callSessionMergeStarted},
     * {@link ImsCallSession.Listener#callSessionMergeComplete},
     *      {@link ImsCallSession.Listener#callSessionMergeFailed}
     */
    public void merge() {
    }

    /**
     * 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 {@link ImsCallSession.Listener#callSessionUpdated},
     * {@link ImsCallSession.Listener#callSessionUpdateFailed}
     */
    public void update(int callType, ImsStreamMediaProfile profile) {
    }

    /**
     * Extends this call to the conference call with the specified recipients.
     *
     * @param participants participant list to be invited to the conference call after extending the
     * call
     * @see {@link ImsCallSession.Listener#callSessionConferenceExtended},
     * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed}
     */
    public void extendToConference(String[] participants) {
    }

    /**
     * Requests the conference server to invite an additional participants to the conference.
     *
     * @param participants participant list to be invited to the conference call
     * @see {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestDelivered},
     *      {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestFailed}
     */
    public void inviteParticipants(String[] participants) {
    }

    /**
     * Requests the conference server to remove the specified participants from the conference.
     *
     * @param participants participant list to be removed from the conference call
     * @see {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestDelivered},
     *      {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed}
     */
    public void removeParticipants(String[] participants) {
    }

    /**
     * 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.
     * @param result If non-null, the {@link Message} to send when the operation is complete. This
     *         is done by using the associated {@link android.os.Messenger} in
     *         {@link Message#replyTo}. For example:
     * {@code
     *     // Send DTMF and other operations...
     *     try {
     *         // Notify framework that the DTMF was sent.
     *         Messenger dtmfMessenger = result.replyTo;
     *         if (dtmfMessenger != null) {
     *             dtmfMessenger.send(result);
     *         }
     *     } catch (RemoteException e) {
     *         // Remote side is dead
     *     }
     * }
     */
    public void sendDtmf(char c, Message result) {
    }

    /**
     * Start 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) {
    }

    /**
     * Stop a DTMF code.
     */
    public void stopDtmf() {
    }

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

    /**
     * See {@link #getImsVideoCallProvider()}, used directly in older ImsService implementations.
     * @hide
     */
    public IImsVideoCallProvider getVideoCallProvider() {
        ImsVideoCallProvider provider = getImsVideoCallProvider();
        return provider != null ? provider.getInterface() : null;
    }

    /**
     * @return The {@link ImsVideoCallProvider} implementation contained within the IMS service
     * process.
     */
    public ImsVideoCallProvider getImsVideoCallProvider() {
        return null;
    }

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

    /**
     * Device issues RTT modify request
     * @param toProfile The profile with requested changes made
     */
    public void sendRttModifyRequest(ImsCallProfile toProfile) {
    }

    /**
     * Device responds to Remote RTT modify request
     * @param status true if the the request was accepted or false of the request is defined.
     */
    public void sendRttModifyResponse(boolean status) {
    }

    /**
     * Device sends RTT message
     * @param rttMessage RTT message to be sent
     */
    public void sendRttMessage(String rttMessage) {
    }

    /**
     * Device requests that {@code rtpHeaderExtensions} are sent as a header extension with the next
     * RTP packet sent by the IMS stack.
     * <p>
     * The {@link RtpHeaderExtensionType}s negotiated during SDP (Session Description Protocol)
     * signalling determine the {@link RtpHeaderExtension}s which can be sent using this method.
     * See RFC8285 for more information.
     * <p>
     * By specification, the RTP header extension is an unacknowledged transmission and there is no
     * guarantee that the header extension will be delivered by the network to the other end of the
     * call.
     * @param rtpHeaderExtensions The RTP header extensions to be included in the next RTP header.
     */
    public void sendRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
    }

    /**
     * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer.
     *
     * @param mediaType MediaType is used to identify media stream such as audio or video.
     * @param direction Direction of this packet stream (e.g. uplink or downlink).
     * @param bitsPerSecond This value is the bitrate received from the NW through the Recommended
     *        bitrate MAC Control Element message and ImsStack converts this value from MAC bitrate
     *        to audio/video codec bitrate (defined in TS26.114).
     * @hide
     */
    public void callSessionNotifyAnbr(@MediaStreamType int mediaType,
            @MediaStreamDirection int direction, @IntRange(from = 0) int bitsPerSecond) {
        // Base Implementation - Should be overridden by IMS service
        Log.i(LOG_TAG, "ImsCallSessionImplBase callSessionNotifyAnbr - mediaType: " + mediaType);
    }

    /** @hide */
    public IImsCallSession getServiceImpl() {
        return mServiceImpl;
    }

    /** @hide */
    public void setServiceImpl(IImsCallSession serviceImpl) {
        mServiceImpl = serviceImpl;
    }

    /**
     * Set default Executor from MmTelFeature.
     * @param executor The default executor for the framework to use when executing the methods
     * overridden by the implementation of ImsCallSession.
     * @hide
     */
    public final void setDefaultExecutor(@NonNull Executor executor) {
        mExecutor = executor;
    }
}
