/*
 * Copyright (C) 2018 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.feature;

import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.Bundle;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsCallSessionListener;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsService;
import android.telephony.ims.MediaQualityStatus;
import android.telephony.ims.MediaThreshold;
import android.telephony.ims.RtpHeaderExtensionType;
import android.telephony.ims.SrvccCall;
import android.telephony.ims.aidl.IImsCallSessionListener;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsMmTelFeature;
import android.telephony.ims.aidl.IImsMmTelListener;
import android.telephony.ims.aidl.IImsSmsListener;
import android.telephony.ims.aidl.IImsTrafficSessionCallback;
import android.telephony.ims.aidl.ISrvccStartedCallback;
import android.telephony.ims.stub.ImsCallSessionImplBase;
import android.telephony.ims.stub.ImsEcbmImplBase;
import android.telephony.ims.stub.ImsMultiEndpointImplBase;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.telephony.ims.stub.ImsSmsImplBase;
import android.telephony.ims.stub.ImsUtImplBase;
import android.util.ArraySet;
import android.util.Log;

import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsUt;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.server.telecom.flags.Flags;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.HashMap;
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.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * Base implementation for Voice and SMS (IR-92) and Video (IR-94) IMS support.
 *
 * Any class wishing to use MmTelFeature should extend this class and implement all methods that the
 * service supports.
 */
public class MmTelFeature extends ImsFeature {

    private static final String LOG_TAG = "MmTelFeature";
    private Executor mExecutor;
    private ImsSmsImplBase mSmsImpl;

    private HashMap<ImsTrafficSessionCallback, ImsTrafficSessionCallbackWrapper> mTrafficCallbacks =
            new HashMap<>();
    /**
     * Creates a new MmTelFeature using the Executor set in {@link ImsService#getExecutor}
     * @hide
     */
    @SystemApi
    public MmTelFeature() {
    }

    /**
     * Create a new MmTelFeature using the Executor specified for methods being called by the
     * framework.
     * @param executor The executor for the framework to use when executing the methods overridden
     * by the implementation of MmTelFeature.
     * @hide
     */
    @SystemApi
    public MmTelFeature(@NonNull Executor executor) {
        super();
        mExecutor = executor;
    }

    private final IImsMmTelFeature mImsMMTelBinder = new IImsMmTelFeature.Stub() {

        @Override
        public void setListener(IImsMmTelListener l) {
            executeMethodAsyncNoException(() -> MmTelFeature.this.setListener(l), "setListener");
        }

        @Override
        public int getFeatureState() throws RemoteException {
            return executeMethodAsyncForResult(() -> MmTelFeature.this.getFeatureState(),
                    "getFeatureState");
        }

        @Override
        public ImsCallProfile createCallProfile(int callSessionType, int callType)
                throws RemoteException {
            return executeMethodAsyncForResult(() -> MmTelFeature.this.createCallProfile(
                    callSessionType, callType), "createCallProfile");
        }

        @Override
        public void changeOfferedRtpHeaderExtensionTypes(List<RtpHeaderExtensionType> types)
                throws RemoteException {
            executeMethodAsync(() -> MmTelFeature.this.changeOfferedRtpHeaderExtensionTypes(
                    new ArraySet<>(types)), "changeOfferedRtpHeaderExtensionTypes");
        }

        @Override
        public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException {
            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
            IImsCallSession result = executeMethodAsyncForResult(() -> {
                try {
                    return createCallSessionInterface(profile);
                } catch (RemoteException e) {
                    exceptionRef.set(e);
                    return null;
                }
            }, "createCallSession");

            if (exceptionRef.get() != null) {
                throw exceptionRef.get();
            }

            return result;
        }

        @Override
        public int shouldProcessCall(String[] numbers) {
            Integer result = executeMethodAsyncForResultNoException(() ->
                    MmTelFeature.this.shouldProcessCall(numbers), "shouldProcessCall");
            if (result != null) {
                return result.intValue();
            } else {
                return PROCESS_CALL_CSFB;
            }
        }

        @Override
        public IImsUt getUtInterface() throws RemoteException {
            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
            IImsUt result = executeMethodAsyncForResult(() -> {
                try {
                    return MmTelFeature.this.getUtInterface();
                } catch (RemoteException e) {
                    exceptionRef.set(e);
                    return null;
                }
            }, "getUtInterface");

            if (exceptionRef.get() != null) {
                throw exceptionRef.get();
            }

            return result;
        }

        @Override
        public IImsEcbm getEcbmInterface() throws RemoteException {
            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
            IImsEcbm result = executeMethodAsyncForResult(() -> {
                try {
                    return MmTelFeature.this.getEcbmInterface();
                } catch (RemoteException e) {
                    exceptionRef.set(e);
                    return null;
                }
            }, "getEcbmInterface");

            if (exceptionRef.get() != null) {
                throw exceptionRef.get();
            }

            return result;
        }

        @Override
        public void setUiTtyMode(int uiTtyMode, Message onCompleteMessage) throws RemoteException {
            executeMethodAsync(() -> MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage),
                    "setUiTtyMode");
        }

        @Override
        public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
            IImsMultiEndpoint result = executeMethodAsyncForResult(() -> {
                try {
                    return MmTelFeature.this.getMultiEndpointInterface();
                } catch (RemoteException e) {
                    exceptionRef.set(e);
                    return null;
                }
            }, "getMultiEndpointInterface");

            if (exceptionRef.get() != null) {
                throw exceptionRef.get();
            }

            return result;
        }

        @Override
        public int queryCapabilityStatus() {
            Integer result = executeMethodAsyncForResultNoException(() -> MmTelFeature.this
                    .queryCapabilityStatus().mCapabilities, "queryCapabilityStatus");

            if (result != null) {
                return result.intValue();
            } else {
                return 0;
            }
        }

        @Override
        public void addCapabilityCallback(IImsCapabilityCallback c) {
            executeMethodAsyncNoException(() -> MmTelFeature.this
                    .addCapabilityCallback(c), "addCapabilityCallback");
        }

        @Override
        public void removeCapabilityCallback(IImsCapabilityCallback c) {
            executeMethodAsyncNoException(() -> MmTelFeature.this
                    .removeCapabilityCallback(c), "removeCapabilityCallback");
        }

        @Override
        public void changeCapabilitiesConfiguration(CapabilityChangeRequest request,
                IImsCapabilityCallback c) {
            executeMethodAsyncNoException(() -> MmTelFeature.this
                            .requestChangeEnabledCapabilities(request, c),
                    "changeCapabilitiesConfiguration");
        }

        @Override
        public void queryCapabilityConfiguration(int capability, int radioTech,
                IImsCapabilityCallback c) {
            executeMethodAsyncNoException(() -> queryCapabilityConfigurationInternal(
                    capability, radioTech, c), "queryCapabilityConfiguration");
        }

        @Override
        public void setMediaQualityThreshold(@MediaQualityStatus.MediaSessionType int sessionType,
                MediaThreshold mediaThreshold) {
            if (mediaThreshold != null) {
                executeMethodAsyncNoException(() -> setMediaThreshold(sessionType, mediaThreshold),
                        "setMediaQualityThreshold");
            } else {
                executeMethodAsyncNoException(() -> clearMediaThreshold(sessionType),
                        "clearMediaQualityThreshold");
            }
        }

        @Override
        public MediaQualityStatus queryMediaQualityStatus(
                @MediaQualityStatus.MediaSessionType int sessionType)
                throws RemoteException {
            return executeMethodAsyncForResult(() -> MmTelFeature.this.queryMediaQualityStatus(
                    sessionType), "queryMediaQualityStatus");
        }

        @Override
        public void setSmsListener(IImsSmsListener l) {
            executeMethodAsyncNoException(() -> MmTelFeature.this.setSmsListener(l),
                    "setSmsListener", getImsSmsImpl().getExecutor());
        }

        @Override
        public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
                byte[] pdu) {
            executeMethodAsyncNoException(() -> MmTelFeature.this
                    .sendSms(token, messageRef, format, smsc, retry, pdu), "sendSms",
                    getImsSmsImpl().getExecutor());
        }

        @Override
        public void onMemoryAvailable(int token) {
            executeMethodAsyncNoException(() -> MmTelFeature.this
                    .onMemoryAvailable(token), "onMemoryAvailable", getImsSmsImpl().getExecutor());
        }

        @Override
        public void acknowledgeSms(int token, int messageRef, int result) {
            executeMethodAsyncNoException(() -> MmTelFeature.this
                    .acknowledgeSms(token, messageRef, result), "acknowledgeSms",
                    getImsSmsImpl().getExecutor());
        }

        @Override
        public void acknowledgeSmsWithPdu(int token, int messageRef, int result, byte[] pdu) {
            executeMethodAsyncNoException(() -> MmTelFeature.this
                    .acknowledgeSms(token, messageRef, result, pdu), "acknowledgeSms",
                    getImsSmsImpl().getExecutor());
        }

        @Override
        public void acknowledgeSmsReport(int token, int messageRef, int result) {
            executeMethodAsyncNoException(() -> MmTelFeature.this
                    .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport",
                    getImsSmsImpl().getExecutor());
        }

        @Override
        public String getSmsFormat() {
            return executeMethodAsyncForResultNoException(() -> MmTelFeature.this
                    .getSmsFormat(), "getSmsFormat", getImsSmsImpl().getExecutor());
        }

        @Override
        public void onSmsReady() {
            executeMethodAsyncNoException(() -> MmTelFeature.this.onSmsReady(),
                    "onSmsReady", getImsSmsImpl().getExecutor());
        }

        @Override
        public void notifySrvccStarted(final ISrvccStartedCallback cb) {
            executeMethodAsyncNoException(
                    () -> MmTelFeature.this.notifySrvccStarted(
                            (profiles) -> {
                                try {
                                    cb.onSrvccCallNotified(profiles);
                                } catch (Exception e) {
                                    Log.e(LOG_TAG, "onSrvccCallNotified e=" + e);
                                }
                            }),
                    "notifySrvccStarted");
        }

        @Override
        public void notifySrvccCompleted() {
            executeMethodAsyncNoException(
                    () -> MmTelFeature.this.notifySrvccCompleted(), "notifySrvccCompleted");
        }

        @Override
        public void notifySrvccFailed() {
            executeMethodAsyncNoException(
                    () -> MmTelFeature.this.notifySrvccFailed(), "notifySrvccFailed");
        }

        @Override
        public void notifySrvccCanceled() {
            executeMethodAsyncNoException(
                    () -> MmTelFeature.this.notifySrvccCanceled(), "notifySrvccCanceled");
        }

        @Override
        public void setTerminalBasedCallWaitingStatus(boolean enabled) throws RemoteException {
            synchronized (mLock) {
                try {
                    MmTelFeature.this.setTerminalBasedCallWaitingStatus(enabled);
                } catch (ServiceSpecificException se) {
                    throw new ServiceSpecificException(se.errorCode, se.getMessage());
                } catch (Exception e) {
                    throw new RemoteException(e.getMessage());
                }
            }
        }

        // 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) throws RemoteException {
            try {
                CompletableFuture.runAsync(
                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
            } catch (CancellationException | CompletionException e) {
                Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
                        + e.getMessage());
                throw new RemoteException(e.getMessage());
            }
        }

        private void executeMethodAsyncNoException(Runnable r, String errorLogName) {
            try {
                CompletableFuture.runAsync(
                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
            } catch (CancellationException | CompletionException e) {
                Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
                        + e.getMessage());
            }
        }

        private void executeMethodAsyncNoException(Runnable r, String errorLogName,
                Executor executor) {
            try {
                CompletableFuture.runAsync(
                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
            } catch (CancellationException | CompletionException e) {
                Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
                        + e.getMessage());
            }
        }

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

        private <T> T executeMethodAsyncForResultNoException(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, "MmTelFeature Binder - " + errorLogName + " exception: "
                        + e.getMessage());
                return null;
            }
        }

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

    /**
     * Contains the capabilities defined and supported by a MmTelFeature in the form of a Bitmask.
     * The capabilities that are used in MmTelFeature are defined as
     * {@link MmTelCapabilities#CAPABILITY_TYPE_VOICE},
     * {@link MmTelCapabilities#CAPABILITY_TYPE_VIDEO},
     * {@link MmTelCapabilities#CAPABILITY_TYPE_UT},
     * {@link MmTelCapabilities#CAPABILITY_TYPE_SMS}, and
     * {@link MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER}.
     *
     * The capabilities of this MmTelFeature will be set by the framework.
     */
    public static class MmTelCapabilities extends Capabilities {

        /**
         * Create a new empty {@link MmTelCapabilities} instance.
         * @see #addCapabilities(int)
         * @see #removeCapabilities(int)
         * @hide
         */
        @SystemApi
        public MmTelCapabilities() {
            super();
        }

        /**@deprecated Use {@link MmTelCapabilities} to construct a new instance instead.
         * @hide
         */
        @Deprecated
        @SystemApi
        public MmTelCapabilities(Capabilities c) {
            mCapabilities = c.mCapabilities;
        }

        /**
         * Create a new {link @MmTelCapabilities} instance with the provided capabilities.
         * @param capabilities The capabilities that are supported for MmTel in the form of a
         *                     bitfield.
         * @hide
         */
        @SystemApi
        public MmTelCapabilities(@MmTelCapability int capabilities) {
            super(capabilities);
        }

        /** @hide */
        @IntDef(flag = true,
                value = {
                        CAPABILITY_TYPE_VOICE,
                        CAPABILITY_TYPE_VIDEO,
                        CAPABILITY_TYPE_UT,
                        CAPABILITY_TYPE_SMS,
                        CAPABILITY_TYPE_CALL_COMPOSER,
                        CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY
                })
        @Retention(RetentionPolicy.SOURCE)
        public @interface MmTelCapability {}

        /**
         * Undefined capability type for initialization
         * This is used to check the upper range of MmTel capability
         * @hide
         */
        public static final int CAPABILITY_TYPE_NONE = 0;

        /**
         * This MmTelFeature supports Voice calling (IR.92)
         */
        public static final int CAPABILITY_TYPE_VOICE = 1 << 0;

        /**
         * This MmTelFeature supports Video (IR.94)
         */
        public static final int CAPABILITY_TYPE_VIDEO = 1 << 1;

        /**
         * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92)
         */
        public static final int CAPABILITY_TYPE_UT = 1 << 2;

        /**
         * This MmTelFeature supports SMS (IR.92)
         */
        public static final int CAPABILITY_TYPE_SMS = 1 << 3;

        /**
         * This MmTelFeature supports Call Composer (section 2.4 of RC.20). This is the superset
         * Call Composer, meaning that all subset types of Call Composers must be enabled when this
         * capability is enabled
         */
        public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4;


        /**
         * This MmTelFeature supports Business-only Call Composer. This is a subset of
         * {@code CAPABILITY_TYPE_CALL_COMPOSER} that only supports business related
         * information for calling (e.g. information to signal if the call is a business call) in
         * the SIP header.  When enabling {@code CAPABILITY_TYPE_CALL_COMPOSER}, the
         * {@code CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY} capability must also be enabled.
         */
        @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
        public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 1 << 5;

        /**
         * This is used to check the upper range of MmTel capability
         * @hide
         */
        public static final int CAPABILITY_TYPE_MAX =
                CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY + 1;

        /**
         * @hide
         */
        @Override
        @SystemApi
        public final void addCapabilities(@MmTelCapability int capabilities) {
            super.addCapabilities(capabilities);
        }

        /**
         * @hide
         */
        @Override
        @SystemApi
        public final void removeCapabilities(@MmTelCapability int capability) {
            super.removeCapabilities(capability);
        }

        /**
         * @param capabilities a bitmask of one or more capabilities.
         *
         * @return true if all queried capabilities are true, otherwise false.
         */
        @Override
        public final boolean isCapable(@MmTelCapability int capabilities) {
            return super.isCapable(capabilities);
        }

        /**
         * @hide
         */
        @NonNull
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder("MmTel Capabilities - [");
            builder.append("Voice: ");
            builder.append(isCapable(CAPABILITY_TYPE_VOICE));
            builder.append(" Video: ");
            builder.append(isCapable(CAPABILITY_TYPE_VIDEO));
            builder.append(" UT: ");
            builder.append(isCapable(CAPABILITY_TYPE_UT));
            builder.append(" SMS: ");
            builder.append(isCapable(CAPABILITY_TYPE_SMS));
            builder.append(" CALL_COMPOSER: ");
            builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER));
            builder.append(" BUSINESS_COMPOSER_ONLY: ");
            builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY));
            builder.append("]");
            return builder.toString();
        }
    }

    /**
     * Listener that the framework implements for communication from the MmTelFeature.
     * @hide
     */
    public static class Listener extends IImsMmTelListener.Stub {

        /**
         * Called when the IMS provider receives an incoming call.
         * @param c The {@link ImsCallSession} associated with the new call.
         * @param callId The call ID of the session of the new incoming call.
         * @param extras A bundle containing extra parameters related to the call. See
         * {@link #EXTRA_IS_UNKNOWN_CALL} and {@link #EXTRA_IS_USSD} above.
         * @return the listener to listen to the session events. An {@link ImsCallSession} can only
         *         hold one listener at a time. see {@link ImsCallSessionListener}.
         *         If this method returns {@code null}, then the call could not be placed.
         * @hide
         */
        @Override
        @Nullable
        public IImsCallSessionListener onIncomingCall(IImsCallSession c,
                String callId, Bundle extras) {
            return null;
        }

        /**
         * Called when the IMS provider implicitly rejects an incoming call during setup.
         * @param callProfile An {@link ImsCallProfile} with the call details.
         * @param reason The {@link ImsReasonInfo} reason for call rejection.
         * @hide
         */
        @Override
        public void onRejectedCall(ImsCallProfile callProfile, ImsReasonInfo reason) {

        }

        /**
         * Updates the Listener when the voice message count for IMS has changed.
         * @param count an integer representing the new message count.
         * @hide
         */
        @Override
        public void onVoiceMessageCountUpdate(int count) {

        }

        /**
         * Called to set the audio handler for this connection.
         * @param imsAudioHandler an {@link ImsAudioHandler} used to handle the audio
         *        for this IMS call.
         * @hide
         */
        @Override
        public void onAudioModeIsVoipChanged(int imsAudioHandler) {

        }

        /**
         * Called when the IMS triggers EPS fallback procedure.
         *
         * @param reason specifies the reason that causes EPS fallback.
         * @hide
         */
        @Override
        public void onTriggerEpsFallback(@EpsFallbackReason int reason) {

        }

        /**
         * Called when the IMS notifies the upcoming traffic type to the radio.
         *
         * @param token A nonce to identify the request
         * @param trafficType The {@link ImsTrafficType} type for IMS traffic.
         * @param accessNetworkType The {@link AccessNetworkConstants#RadioAccessNetworkType}
         *        type of the radio access network.
         * @param trafficDirection Indicates whether traffic is originated by mobile originated or
         *        mobile terminated use case eg. MO/MT call/SMS etc.
         * @param callback The callback to receive the result.
         * @hide
         */
        @Override
        public void onStartImsTrafficSession(int token,
                @ImsTrafficType int trafficType,
                @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
                @ImsTrafficDirection int trafficDirection,
                IImsTrafficSessionCallback callback) {

        }

        /**
         * Called when the IMS notifies the traffic type has been stopped.
         *
         * @param token A nonce registered with {@link #onStartImsTrafficSession}.
         * @param accessNetworkType The {@link AccessNetworkConstants#RadioAccessNetworkType}
         *        type of the radio access network.
         * @hide
         */
        @Override
        public void onModifyImsTrafficSession(int token,
                @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType) {

        }

        /**
         * Called when the IMS notifies the traffic type has been stopped.
         *
         * @param token A nonce registered with {@link #onStartImsTrafficSession}.
         * @hide
         */
        @Override
        public void onStopImsTrafficSession(int token) {

        }

        /**
         * Called when the IMS provider notifies {@link MediaQualityStatus}.
         *
         * @param status media quality status currently measured.
         * @hide
         */
        @Override
        public void onMediaQualityStatusChanged(MediaQualityStatus status) {

        }
    }

    /**
     * A wrapper class of {@link ImsTrafficSessionCallback}.
     * @hide
     */
    public static class ImsTrafficSessionCallbackWrapper {
        public static final int INVALID_TOKEN = -1;

        private static final int MAX_TOKEN = 0x10000;

        private static final AtomicInteger sTokenGenerator = new AtomicInteger();

        /** Callback to receive the response */
        private IImsTrafficSessionCallbackStub mCallback = null;
        /** Identifier to distinguish each IMS traffic request */
        private int mToken = INVALID_TOKEN;

        private ImsTrafficSessionCallback mImsTrafficSessionCallback;

        private ImsTrafficSessionCallbackWrapper(ImsTrafficSessionCallback callback) {
            mImsTrafficSessionCallback = callback;
        }

        /**
         * Updates the callback.
         *
         * The mToken should be kept since it is used to identify the traffic notified to the modem
         * until calling {@link MmtelFEature#stopImsTrafficSession}.
         */
        final void update(@NonNull @CallbackExecutor Executor executor) {
            if (executor == null) {
                throw new IllegalArgumentException(
                        "ImsTrafficSessionCallback Executor must be non-null");
            }

            if (mCallback == null) {
                // initial start of Ims traffic.
                mCallback = new IImsTrafficSessionCallbackStub(
                        mImsTrafficSessionCallback, executor);
                mToken = generateToken();
            } else {
                // handover between cellular and Wi-Fi
                mCallback.update(executor);
            }
        }

        /**
         * Using a static class and weak reference here to avoid memory leak caused by the
         * {@link IImsTrafficSessionCallback.Stub} callback retaining references to the outside
         * {@link ImsTrafficSessionCallback}.
         */
        private static class IImsTrafficSessionCallbackStub
                extends IImsTrafficSessionCallback.Stub {
            private WeakReference<ImsTrafficSessionCallback> mImsTrafficSessionCallbackWeakRef;
            private Executor mExecutor;

            IImsTrafficSessionCallbackStub(ImsTrafficSessionCallback imsTrafficCallback,
                    Executor executor) {
                mImsTrafficSessionCallbackWeakRef =
                        new WeakReference<ImsTrafficSessionCallback>(imsTrafficCallback);
                mExecutor = executor;
            }

            void update(Executor executor) {
                mExecutor = executor;
            }

            @Override
            public void onReady() {
                ImsTrafficSessionCallback callback = mImsTrafficSessionCallbackWeakRef.get();
                if (callback == null) return;

                Binder.withCleanCallingIdentity(
                        () -> mExecutor.execute(() -> callback.onReady()));
            }

            @Override
            public void onError(ConnectionFailureInfo info) {
                ImsTrafficSessionCallback callback = mImsTrafficSessionCallbackWeakRef.get();
                if (callback == null) return;

                Binder.withCleanCallingIdentity(
                        () -> mExecutor.execute(() -> callback.onError(info)));
            }
        }

        /**
         * Returns the callback binder.
         */
        final IImsTrafficSessionCallbackStub getCallbackBinder() {
            return mCallback;
        }

        /**
         * Returns the token.
         */
        final int getToken() {
            return mToken;
        }

        /**
         * Resets the members.
         * It's called by {@link MmTelFeature#stopImsTrafficSession}.
         */
        final void reset() {
            mCallback = null;
            mToken = INVALID_TOKEN;
        }

        private static int generateToken() {
            int token = sTokenGenerator.incrementAndGet();
            if (token == MAX_TOKEN) sTokenGenerator.set(0);
            return token;
        }
    }

    /**
     * To be returned by {@link #shouldProcessCall(String[])} when the ImsService should process the
     * outgoing call as IMS.
     * @hide
     */
    @SystemApi
    public static final int PROCESS_CALL_IMS = 0;
    /**
     * To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should
     * not process the outgoing call as IMS and should instead use circuit switch.
     * @hide
     */
    @SystemApi
    public static final int PROCESS_CALL_CSFB = 1;

    /** @hide */
    @IntDef(flag = true,
            value = {
                    PROCESS_CALL_IMS,
                    PROCESS_CALL_CSFB
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ProcessCallResult {}

    /**
     * If the flag is present and true, it indicates that the incoming call is for USSD.
     * <p>
     * This is an optional boolean flag.
     * @hide
     */
    @SystemApi
    public static final String EXTRA_IS_USSD = "android.telephony.ims.feature.extra.IS_USSD";

    /**
     * If this flag is present and true, this call is marked as an unknown dialing call instead
     * of an incoming call. An example of such a call is a call that is originated by sending
     * commands (like AT commands) directly to the modem without Android involvement or dialing
     * calls appearing over IMS when the modem does a silent redial from circuit-switched to IMS in
     * certain situations.
     * <p>
     * This is an optional boolean flag.
     * @hide
     */
    @SystemApi
    public static final String EXTRA_IS_UNKNOWN_CALL =
            "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";

    /** @hide */
    @IntDef(
        prefix = "AUDIO_HANDLER_",
        value = {
            AUDIO_HANDLER_ANDROID,
            AUDIO_HANDLER_BASEBAND
        })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ImsAudioHandler {}

    /**
    * Audio Handler - Android
    * @hide
    */
    @SystemApi
    public static final int AUDIO_HANDLER_ANDROID = 0;

    /**
    * Audio Handler - Baseband
    * @hide
    */
    @SystemApi
    public static final int AUDIO_HANDLER_BASEBAND = 1;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
        prefix = "EPS_FALLBACK_REASON_",
        value = {
            EPS_FALLBACK_REASON_INVALID,
            EPS_FALLBACK_REASON_NO_NETWORK_TRIGGER,
            EPS_FALLBACK_REASON_NO_NETWORK_RESPONSE,
        })
    public @interface EpsFallbackReason {}

    /**
     * Default value. Internal use only.
     * This value should not be used to trigger EPS fallback.
     * @hide
     */
    public static final int EPS_FALLBACK_REASON_INVALID = -1;

    /**
     * If the network only supports the EPS fallback in 5G NR SA for voice calling and the EPS
     * Fallback procedure by the network during the call setup is not triggered, UE initiated
     * fallback will be triggered with this reason. The modem shall locally release the 5G NR
     * SA RRC connection and acquire the LTE network and perform a tracking area update
     * procedure. After the EPS fallback procedure is completed, the call setup for voice will
     * be established if there is no problem.
     *
     * @hide
     */
    public static final int EPS_FALLBACK_REASON_NO_NETWORK_TRIGGER = 1;

    /**
     * If the UE doesn't receive any response for SIP INVITE within a certain timeout in 5G NR
     * SA for MO voice calling, the device determines that voice call is not available in 5G and
     * terminates all active SIP dialogs and SIP requests and enters IMS non-registered state.
     * In that case, UE initiated fallback will be triggered with this reason. The modem shall
     * reset modem's data buffer of IMS PDU to prevent the ghost call. After the EPS fallback
     * procedure is completed, VoLTE call could be tried if there is no problem.
     *
     * @hide
     */
    public static final int EPS_FALLBACK_REASON_NO_NETWORK_RESPONSE = 2;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
        prefix = "IMS_TRAFFIC_TYPE_",
        value = {
            IMS_TRAFFIC_TYPE_NONE,
            IMS_TRAFFIC_TYPE_EMERGENCY,
            IMS_TRAFFIC_TYPE_EMERGENCY_SMS,
            IMS_TRAFFIC_TYPE_VOICE,
            IMS_TRAFFIC_TYPE_VIDEO,
            IMS_TRAFFIC_TYPE_SMS,
            IMS_TRAFFIC_TYPE_REGISTRATION,
            IMS_TRAFFIC_TYPE_UT_XCAP
        })
    public @interface ImsTrafficType {}

    /**
     * Default value for initialization. Internal use only.
     * @hide
     */
    public static final int IMS_TRAFFIC_TYPE_NONE = -1;
    /**
     * Emergency call
     * @hide
     */
    public static final int IMS_TRAFFIC_TYPE_EMERGENCY = 0;
    /**
     * Emergency SMS
     * @hide
     */
    public static final int IMS_TRAFFIC_TYPE_EMERGENCY_SMS = 1;
    /**
     * Voice call
     * @hide
     */
    public static final int IMS_TRAFFIC_TYPE_VOICE = 2;
    /**
     * Video call
     * @hide
     */
    public static final int IMS_TRAFFIC_TYPE_VIDEO = 3;
    /**
     * SMS over IMS
     * @hide
     */
    public static final int IMS_TRAFFIC_TYPE_SMS = 4;
    /**
     * IMS registration and subscription for reg event package (signaling)
     * @hide
     */
    public static final int IMS_TRAFFIC_TYPE_REGISTRATION = 5;
    /**
     * Ut/XCAP (XML Configuration Access Protocol)
     * @hide
     */
    public static final int IMS_TRAFFIC_TYPE_UT_XCAP = 6;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
            prefix = { "IMS_TRAFFIC_DIRECTION_" },
            value = {IMS_TRAFFIC_DIRECTION_INCOMING, IMS_TRAFFIC_DIRECTION_OUTGOING})
    public @interface ImsTrafficDirection {}

    /**
     * Indicates that the traffic is an incoming traffic.
     * @hide
     */
    public static final int IMS_TRAFFIC_DIRECTION_INCOMING = 0;
    /**
     * Indicates that the traffic is an outgoing traffic.
     * @hide
     */
    public static final int IMS_TRAFFIC_DIRECTION_OUTGOING = 1;

    private IImsMmTelListener mListener;

    /**
     * @param listener A {@link Listener} used when the MmTelFeature receives an incoming call and
     *     notifies the framework.
     */
    private void setListener(IImsMmTelListener listener) {
        synchronized (mLock) {
            mListener = listener;
            if (mListener != null) {
                onFeatureReady();
            }
        }
    }

    /**
     * @return the listener associated with this MmTelFeature. May be null if it has not been set
     * by the framework yet.
     */
    private IImsMmTelListener getListener() {
        synchronized (mLock) {
            return mListener;
        }
    }

    /**
     * The current capability status that this MmTelFeature has defined is available. This
     * configuration will be used by the platform to figure out which capabilities are CURRENTLY
     * available to be used.
     *
     * Should be a subset of the capabilities that are enabled by the framework in
     * {@link #changeEnabledCapabilities}.
     * @return A copy of the current MmTelFeature capability status.
     * @hide
     */
    @Override
    @SystemApi
    public @NonNull final MmTelCapabilities queryCapabilityStatus() {
        return new MmTelCapabilities(super.queryCapabilityStatus());
    }

    /**
     * Notify the framework that the status of the Capabilities has changed. Even though the
     * MmTelFeature capability may be enabled by the framework, the status may be disabled due to
     * the feature being unavailable from the network.
     * @param c The current capability status of the MmTelFeature. If a capability is disabled, then
     * the status of that capability is disabled. This can happen if the network does not currently
     * support the capability that is enabled. A capability that is disabled by the framework (via
     * {@link #changeEnabledCapabilities}) should also show the status as disabled.
     * @hide
     */
    @SystemApi
    public final void notifyCapabilitiesStatusChanged(@NonNull MmTelCapabilities c) {
        if (c == null) {
            throw new IllegalArgumentException("MmTelCapabilities must be non-null!");
        }
        super.notifyCapabilitiesStatusChanged(c);
    }

    /**
     * Notify the framework that the measured media quality has crossed a threshold set by {@link
     * MmTelFeature#setMediaThreshold}
     *
     * @param status current media quality status measured.
     * @hide
     */
    @SystemApi
    public final void notifyMediaQualityStatusChanged(
            @NonNull MediaQualityStatus status) {
        if (status == null) {
            throw new IllegalArgumentException(
                    "MediaQualityStatus must be non-null!");
        }
        Log.i(LOG_TAG, "notifyMediaQualityStatusChanged " + status);
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        try {
            listener.onMediaQualityStatusChanged(status);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Notify the framework of an incoming call.
     * @param c The {@link ImsCallSessionImplBase} of the new incoming call.
     * @param extras A bundle containing extra parameters related to the call. See
     * {@link #EXTRA_IS_UNKNOWN_CALL} and {@link #EXTRA_IS_USSD} above.
     * @hide
     *
     * @deprecated use {@link #notifyIncomingCall(ImsCallSessionImplBase, String, Bundle)} instead
     */
    @Deprecated
    @SystemApi
    public final void notifyIncomingCall(@NonNull ImsCallSessionImplBase c,
            @NonNull Bundle extras) {
        if (c == null || extras == null) {
            throw new IllegalArgumentException("ImsCallSessionImplBase and Bundle can not be "
                    + "null.");
        }
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        try {
            c.setDefaultExecutor(MmTelFeature.this.mExecutor);
            listener.onIncomingCall(c.getServiceImpl(), null, extras);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Notify the framework of an incoming call.
     * @param c The {@link ImsCallSessionImplBase} of the new incoming call.
     * @param callId The call ID of the session of the new incoming call.
     * @param extras A bundle containing extra parameters related to the call. See
     * {@link #EXTRA_IS_UNKNOWN_CALL} and {@link #EXTRA_IS_USSD} above.
     * @return The listener used by the framework to listen to call session events created
     *         from the ImsService.
     *         If this method returns {@code null}, then the call could not be placed.
     * @hide
     */
    @SystemApi
    @Nullable
    public final ImsCallSessionListener notifyIncomingCall(
            @NonNull ImsCallSessionImplBase c, @NonNull String callId, @NonNull Bundle extras) {
        if (c == null || callId == null || extras == null) {
            throw new IllegalArgumentException("ImsCallSessionImplBase, callId, and Bundle can "
                    + "not be null.");
        }
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        try {
            c.setDefaultExecutor(MmTelFeature.this.mExecutor);
            IImsCallSessionListener isl =
                    listener.onIncomingCall(c.getServiceImpl(), callId, extras);
            if (isl != null) {
                ImsCallSessionListener iCSL = new ImsCallSessionListener(isl);
                iCSL.setDefaultExecutor(MmTelFeature.this.mExecutor);
                return iCSL;
            } else {
                return null;
            }
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Notify the framework that a call has been implicitly rejected by this MmTelFeature
     * during call setup.
     * @param callProfile The {@link ImsCallProfile} IMS call profile with details.
     *        This can be null if no call information is available for the rejected call.
     * @param reason The {@link ImsReasonInfo} call rejection reason.
     * @hide
     */
    @SystemApi
    public final void notifyRejectedCall(@NonNull ImsCallProfile callProfile,
            @NonNull ImsReasonInfo reason) {
        if (callProfile == null || reason == null) {
            throw new IllegalArgumentException("ImsCallProfile and ImsReasonInfo must not be "
                    + "null.");
        }
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        try {
            listener.onRejectedCall(callProfile, reason);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     *
     * @hide
     */
    public final void notifyIncomingCallSession(IImsCallSession c, Bundle extras) {
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        try {
            listener.onIncomingCall(c, null, extras);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Notify the framework of a change in the Voice Message count.
     * @link count the new Voice Message count.
     * @hide
     */
    @SystemApi
    public final void notifyVoiceMessageCountUpdate(int count) {
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        try {
            listener.onVoiceMessageCountUpdate(count);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Sets the audio handler for this connection. The vendor IMS stack will invoke this API
     * to inform Telephony/Telecom layers about which audio handlers i.e. either Android or Modem
     * shall be used for handling the IMS call audio.
     *
     * @param imsAudioHandler {@link MmTelFeature#ImsAudioHandler} used to handle the audio
     *        for this IMS call.
     * @hide
     */
    @SystemApi
    public final void setCallAudioHandler(@ImsAudioHandler int imsAudioHandler) {
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        try {
            listener.onAudioModeIsVoipChanged(imsAudioHandler);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Triggers the EPS fallback procedure.
     *
     * @param reason specifies the reason that causes EPS fallback.
     * @hide
     */
    public final void triggerEpsFallback(@EpsFallbackReason int reason) {
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        try {
            listener.onTriggerEpsFallback(reason);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Starts a new IMS traffic session with the framework.
     *
     * This API notifies the NAS and RRC layers of the modem that IMS traffic of type
     * {@link ImsTrafficType} is starting for the IMS session represented by a
     * {@link ImsTrafficSessionCallback}. The {@link ImsTrafficSessionCallback}
     * will notify the caller when IMS traffic is ready to start via the
     * {@link ImsTrafficSessionCallback#onReady()} callback. If there was an error starting
     * IMS traffic for the specified traffic type, {@link ImsTrafficSessionCallback#onError()} will
     * be called, which will also notify the caller of the reason of the failure.
     *
     * If there is a handover that changes the {@link AccessNetworkConstants#RadioAccessNetworkType}
     * of this IMS traffic session, then {@link #modifyImsTrafficSession} should be called. This is
     * used, for example, when a WiFi <-> cellular handover occurs.
     *
     * Once the IMS traffic session is finished, {@link #stopImsTrafficSession} must be called.
     *
     * Note: This API will be used to prioritize RF resources in case of DSDS. The service priority
     * is EMERGENCY > EMERGENCY SMS > VOICE > VIDEO > SMS > REGISTRATION > Ut/XCAP. RF
     * shall be prioritized to the subscription which handles the higher priority service.
     * When both subscriptions are handling the same type of service, then RF shall be
     * prioritized to the voice preferred sub.
     *
     * @param trafficType The {@link ImsTrafficType} type for IMS traffic.
     * @param accessNetworkType The {@link AccessNetworkConstants#RadioAccessNetworkType} type of
     *        the radio access network.
     * @param trafficDirection Indicates whether traffic is originated by mobile originated or
     *        mobile terminated use case eg. MO/MT call/SMS etc.
     * @param executor The Executor that will be used to call the {@link ImsTrafficSessionCallback}.
     * @param callback The session representing the IMS Session associated with a specific
     *        trafficType. This callback instance should only be used for the specified traffic type
     *        until {@link #stopImsTrafficSession} is called.
     *
     * @see modifyImsTrafficSession
     * @see stopImsTrafficSession
     *
     * @hide
     */
    public final void startImsTrafficSession(@ImsTrafficType int trafficType,
            @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
            @ImsTrafficDirection int trafficDirection,
            @NonNull Executor executor, @NonNull ImsTrafficSessionCallback callback) {
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        // TODO: retrieve from the callback list
        ImsTrafficSessionCallbackWrapper callbackWrapper = mTrafficCallbacks.get(callback);
        if (callbackWrapper == null) {
            callbackWrapper = new ImsTrafficSessionCallbackWrapper(callback);
            mTrafficCallbacks.put(callback, callbackWrapper);
        }
        try {
            callbackWrapper.update(executor);
            listener.onStartImsTrafficSession(callbackWrapper.getToken(),
                    trafficType, accessNetworkType, trafficDirection,
                    callbackWrapper.getCallbackBinder());
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Modifies an existing IMS traffic session represented by the associated
     * {@link ImsTrafficSessionCallback}.
     *
     * The {@link ImsTrafficSessionCallback} will notify the caller when IMS traffic is ready to
     * start after modification using the {@link ImsTrafficSessionCallback#onReady()} callback.
     * If there was an error modifying IMS traffic for the new radio access network type type,
     * {@link ImsTrafficSessionCallback#onError()} will be called, which will also notify the
     * caller of the reason of the failure.
     *
     * @param accessNetworkType The {@link AccessNetworkConstants#RadioAccessNetworkType} type of
     *        the radio access network.
     * @param callback The callback registered with {@link #startImsTrafficSession}.
     *
     * @see startImsTrafficSession
     * @see stopImsTrafficSession
     *
     * @hide
     */
    public final void modifyImsTrafficSession(
            @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
            @NonNull ImsTrafficSessionCallback callback) {
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        ImsTrafficSessionCallbackWrapper callbackWrapper = mTrafficCallbacks.get(callback);
        if (callbackWrapper == null) {
            // should not reach here.
            throw new IllegalStateException("Unknown ImsTrafficSessionCallback instance.");
        }
        try {
            listener.onModifyImsTrafficSession(callbackWrapper.getToken(), accessNetworkType);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Notifies the framework that the IMS traffic session represented by the associated
     * {@link ImsTrafficSessionCallback} has ended.
     *
     * @param callback The callback registered with {@link #startImsTrafficSession}.
     *
     * @see startImsTrafficSession
     * @see modifyImsTrafficSession
     *
     * @hide
     */
    public final void stopImsTrafficSession(@NonNull ImsTrafficSessionCallback callback) {
        IImsMmTelListener listener = getListener();
        if (listener == null) {
            throw new IllegalStateException("Session is not available.");
        }
        ImsTrafficSessionCallbackWrapper callbackWrapper = mTrafficCallbacks.get(callback);
        if (callbackWrapper == null) {
            // should not reach here.
            throw new IllegalStateException("Unknown ImsTrafficSessionCallback instance.");
        }
        try {
            listener.onStopImsTrafficSession(callbackWrapper.getToken());
            callbackWrapper.reset();
            mTrafficCallbacks.remove(callback);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Provides the MmTelFeature with the ability to return the framework Capability Configuration
     * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and
     * includes a capability A to enable or disable, this method should return the correct enabled
     * status for capability A.
     * @param capability The capability that we are querying the configuration for.
     * @return true if the capability is enabled, false otherwise.
     * @hide
     */
    @Override
    @SystemApi
    public boolean queryCapabilityConfiguration(@MmTelCapabilities.MmTelCapability int capability,
            @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
        // Base implementation - Override to provide functionality
        return false;
    }

    /**
     * The MmTelFeature should override this method to handle the enabling/disabling of
     * MmTel Features, defined in {@link MmTelCapabilities.MmTelCapability}. The framework assumes
     * the {@link CapabilityChangeRequest} was processed successfully. If a subset of capabilities
     * could not be set to their new values,
     * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} must be called
     * individually for each capability whose processing resulted in an error.
     *
     * Enabling/Disabling a capability here indicates that the capability should be registered or
     * deregistered (depending on the capability change) and become available or unavailable to
     * the framework.
     * @hide
     */
    @Override
    @SystemApi
    public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request,
            @NonNull CapabilityCallbackProxy c) {
        // Base implementation, no-op
    }

    /**
     * Called by the framework to pass {@link MediaThreshold}. The MmTelFeature should override this
     * method to get Media quality threshold. This will pass the consolidated threshold values from
     * Telephony framework. IMS provider needs to monitor media quality of active call and notify
     * media quality {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the measured
     * media quality crosses at least one of {@link MediaThreshold} set by this.
     *
     * @param mediaSessionType media session type for this Threshold info.
     * @param mediaThreshold media threshold information
     * @hide
     */
    @SystemApi
    public void setMediaThreshold(
            @MediaQualityStatus.MediaSessionType int mediaSessionType,
            @NonNull MediaThreshold mediaThreshold) {
        // Base Implementation - Should be overridden.
        Log.d(LOG_TAG, "setMediaThreshold is not supported." + mediaThreshold);
    }

    /**
     * The MmTelFeature should override this method to clear Media quality thresholds that were
     * registered and stop media quality status updates.
     *
     * @param mediaSessionType media session type
     * @hide
     */
    @SystemApi
    public void clearMediaThreshold(@MediaQualityStatus.MediaSessionType int mediaSessionType) {
        // Base Implementation - Should be overridden.
        Log.d(LOG_TAG, "clearMediaThreshold is not supported." + mediaSessionType);
    }

    /**
     * IMS provider should override this method to return currently measured media quality status.
     *
     * <p/>
     * If media quality status is not yet measured after call is active, it needs to notify media
     * quality status {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the first
     * measurement is done.
     *
     * @param mediaSessionType media session type
     * @return Current media quality status. It could be null if media quality status is not
     *         measured yet or {@link MediaThreshold} was not set corresponding to the media session
     *         type.
     *
     * @hide
     */
    @SystemApi
    @Nullable
    public MediaQualityStatus queryMediaQualityStatus(
            @MediaQualityStatus.MediaSessionType int mediaSessionType) {
        // Base Implementation - Should be overridden.
        Log.d(LOG_TAG, "queryMediaQualityStatus is not supported." + mediaSessionType);
        return null;
    }

    /**
     * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
     *
     * @param callSessionType a service type that is specified in {@link ImsCallProfile}
     *        {@link ImsCallProfile#SERVICE_TYPE_NONE}
     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
     * @param callType a call type that is specified in {@link ImsCallProfile}
     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
     *        {@link ImsCallProfile#CALL_TYPE_VT}
     *        {@link ImsCallProfile#CALL_TYPE_VT_TX}
     *        {@link ImsCallProfile#CALL_TYPE_VT_RX}
     *        {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
     *        {@link ImsCallProfile#CALL_TYPE_VS}
     *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
     *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
     * @return a {@link ImsCallProfile} object
     * @hide
     */
    @SystemApi
    public @Nullable ImsCallProfile createCallProfile(int callSessionType, int callType) {
        // Base Implementation - Should be overridden
        return null;
    }

    /**
     * Called by the framework to report a change to the RTP header extension types which should be
     * offered during SDP negotiation (see RFC8285 for more information).
     * <p>
     * The {@link ImsService} should report the RTP header extensions which were accepted during
     * SDP negotiation using {@link ImsCallProfile#setAcceptedRtpHeaderExtensionTypes(Set)}.
     *
     * @param extensionTypes The RTP header extensions the framework wishes to offer during
     *                       outgoing and incoming call setup.  An empty list indicates that there
     *                       are no framework defined RTP header extension types to offer.
     * @hide
     */
    @SystemApi
    public void changeOfferedRtpHeaderExtensionTypes(
            @NonNull Set<RtpHeaderExtensionType> extensionTypes) {
        // Base implementation - should be overridden if RTP header extension handling is supported.
    }

    /**
     * @hide
     */
    public IImsCallSession createCallSessionInterface(ImsCallProfile profile)
            throws RemoteException {
        ImsCallSessionImplBase s = MmTelFeature.this.createCallSession(profile);
        if (s != null) {
            s.setDefaultExecutor(mExecutor);
            return s.getServiceImpl();
        } else {
            return null;
        }
    }

    /**
     * Creates an {@link ImsCallSession} with the specified call profile.
     * Use other methods, if applicable, instead of interacting with
     * {@link ImsCallSession} directly.
     *
     * @param profile a call profile to make the call
     * @hide
     */
    @SystemApi
    public @Nullable ImsCallSessionImplBase createCallSession(@NonNull ImsCallProfile profile) {
        // Base Implementation - Should be overridden
        return null;
    }

    /**
     * Called by the framework to determine if the outgoing call, designated by the outgoing
     * {@link String}s, should be processed as an IMS call or CSFB call. If this method's
     * functionality is not overridden, the platform will process every call as IMS as long as the
     * MmTelFeature reports that the {@link MmTelCapabilities#CAPABILITY_TYPE_VOICE} capability is
     * available.
     * @param numbers An array of {@link String}s that will be used for placing the call. There can
     *         be multiple {@link String}s listed in the case when we want to place an outgoing
     *         call as a conference.
     * @return a {@link ProcessCallResult} to the framework, which will be used to determine if the
     *        call will be placed over IMS or via CSFB.
     * @hide
     */
    @SystemApi
    public @ProcessCallResult int shouldProcessCall(@NonNull String[] numbers) {
        return PROCESS_CALL_IMS;
    }

    /**
     *
     * @hide
     */
    protected IImsUt getUtInterface() throws RemoteException {
        ImsUtImplBase utImpl = getUt();
        if (utImpl != null) {
            utImpl.setDefaultExecutor(mExecutor);
            return utImpl.getInterface();
        } else {
            return null;
        }
    }

    /**
     * @hide
     */
    protected IImsEcbm getEcbmInterface() throws RemoteException {
        ImsEcbmImplBase ecbmImpl = getEcbm();
        if (ecbmImpl != null) {
            ecbmImpl.setDefaultExecutor(mExecutor);
            return ecbmImpl.getImsEcbm();
        } else {
            return null;
        }
    }

    /**
     * @hide
     */
    public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
        ImsMultiEndpointImplBase multiendpointImpl = getMultiEndpoint();
        if (multiendpointImpl != null) {
            multiendpointImpl.setDefaultExecutor(mExecutor);
            return multiendpointImpl.getIImsMultiEndpoint();
        } else {
            return null;
        }
    }

    /**
     * @hide
     */
    public @NonNull ImsSmsImplBase getImsSmsImpl() {
        synchronized (mLock) {
            if (mSmsImpl == null) {
                mSmsImpl = getSmsImplementation();
                mSmsImpl.setDefaultExecutor(mExecutor);
            }
            return mSmsImpl;
        }
    }

    /**
     * @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service
     * configuration.
     * @hide
     */
    @SystemApi
    public @NonNull ImsUtImplBase getUt() {
        // Base Implementation - Should be overridden
        return new ImsUtImplBase();
    }

    /**
     * @return The {@link ImsEcbmImplBase} Emergency call-back mode interface for emergency VoLTE
     * calls that support it.
     * @hide
     */
    @SystemApi
    public @NonNull ImsEcbmImplBase getEcbm() {
        // Base Implementation - Should be overridden
        return new ImsEcbmImplBase();
    }

    /**
     * @return The {@link ImsMultiEndpointImplBase} implementation for implementing Dialog event
     * package processing for multi-endpoint.
     * @hide
     */
    @SystemApi
    public @NonNull ImsMultiEndpointImplBase getMultiEndpoint() {
        // Base Implementation - Should be overridden
        return new ImsMultiEndpointImplBase();
    }

    /**
     * Sets the current UI TTY mode for the MmTelFeature.
     * @param mode An integer containing the new UI TTY Mode, can consist of
     *         {@link TelecomManager#TTY_MODE_OFF},
     *         {@link TelecomManager#TTY_MODE_FULL},
     *         {@link TelecomManager#TTY_MODE_HCO},
     *         {@link TelecomManager#TTY_MODE_VCO}
     * @param onCompleteMessage If non-null, this MmTelFeature should call this {@link Message} when
     *         the operation is complete by using the associated {@link android.os.Messenger} in
     *         {@link Message#replyTo}. For example:
     * {@code
     *     // Set UI TTY Mode and other operations...
     *     try {
     *         // Notify framework that the mode was changed.
     *         Messenger uiMessenger = onCompleteMessage.replyTo;
     *         uiMessenger.send(onCompleteMessage);
     *     } catch (RemoteException e) {
     *         // Remote side is dead
     *     }
     * }
     * @hide
     */
    @SystemApi
    public void setUiTtyMode(int mode, @Nullable Message onCompleteMessage) {
        // Base Implementation - Should be overridden
    }

    /**
     * Notifies the MmTelFeature of the enablement status of terminal based call waiting
     *
     * If the terminal based call waiting is provisioned,
     * IMS controls the enablement of terminal based call waiting which is defined
     * in 3GPP TS 24.615.
     *
     * @param enabled user setting controlling whether or not call waiting is enabled.
     *
     * @hide
     */
    @SystemApi
    public void setTerminalBasedCallWaitingStatus(boolean enabled) {
        // Base Implementation - Should be overridden by IMS service
        throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                "Not implemented on device.");
    }

    /**
     * Notifies the MmTelFeature that the network has initiated an SRVCC (Single radio voice
     * call continuity) for all IMS calls. When the network initiates an SRVCC, calls from
     * the LTE domain are handed over to the legacy circuit switched domain. The modem requires
     * knowledge of ongoing calls in the IMS domain in order to complete the SRVCC operation.
     * <p>
     * @param consumer The callback used to notify the framework of the list of IMS calls and their
     * state at the time of the SRVCC.
     *
     * @hide
     */
    @SystemApi
    public void notifySrvccStarted(@NonNull Consumer<List<SrvccCall>> consumer) {
        // Base Implementation - Should be overridden by IMS service
    }

    /**
     * Notifies the MmTelFeature that the SRVCC is completed and the calls have been moved
     * over to the circuit-switched domain.
     * {@link android.telephony.CarrierConfigManager.ImsVoice#KEY_SRVCC_TYPE_INT_ARRAY}
     * specifies the calls can be moved. Other calls will be disconnected.
     * <p>
     * The MmTelFeature may now release all resources related to the IMS calls.
     *
     * @hide
     */
    @SystemApi
    public void notifySrvccCompleted() {
        // Base Implementation - Should be overridden by IMS service
    }

    /**
     * Notifies the MmTelFeature that the SRVCC has failed.
     *
     * The handover can fail by encountering a failure at the radio level
     * or temporary MSC server internal errors in handover procedure.
     * Refer to 3GPP TS 23.216 section 8 Handover Failure.
     * <p>
     * IMS service will recover and continue calls over IMS.
     * Per TS 24.237 12.2.4.2, UE shall send SIP UPDATE request containing the reason-text
     * set to "failure to transition to CS domain".
     *
     * @hide
     */
    @SystemApi
    public void notifySrvccFailed() {
        // Base Implementation - Should be overridden by IMS service
    }

    /**
     * Notifies the MmTelFeature that the SRVCC has been canceled.
     *
     * Since the state of network can be changed, the network can decide to terminate
     * the handover procedure before its completion and to return to its state before the handover
     * procedure was triggered.
     * Refer to 3GPP TS 23.216 section 8.1.3 Handover Cancellation.
     *
     * <p>
     * IMS service will recover and continue calls over IMS.
     * Per TS 24.237 12.2.4.2, UE shall send SIP UPDATE request containing the reason-text
     * set to "handover canceled".
     *
     * @hide
     */
    @SystemApi
    public void notifySrvccCanceled() {
        // Base Implementation - Should be overridden by IMS service
    }

    private void setSmsListener(IImsSmsListener listener) {
        getImsSmsImpl().registerSmsListener(listener);
    }

    private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
            byte[] pdu) {
        getImsSmsImpl().sendSms(token, messageRef, format, smsc, isRetry, pdu);
    }

    private void onMemoryAvailable(int token) {
        getImsSmsImpl().onMemoryAvailable(token);
    }

    private void acknowledgeSms(int token, int messageRef,
            @ImsSmsImplBase.DeliverStatusResult int result) {
        getImsSmsImpl().acknowledgeSms(token, messageRef, result);
    }

    private void acknowledgeSms(int token, int messageRef,
            @ImsSmsImplBase.DeliverStatusResult int result, byte[] pdu) {
        getImsSmsImpl().acknowledgeSms(token, messageRef, result, pdu);
    }

    private void acknowledgeSmsReport(int token, int messageRef,
            @ImsSmsImplBase.StatusReportResult int result) {
        getImsSmsImpl().acknowledgeSmsReport(token, messageRef, result);
    }

    private void onSmsReady() {
        getImsSmsImpl().onReady();
    }

    /**
     * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default
     * non-functional implementation is returned.
     *
     * @return an instance of {@link ImsSmsImplBase} which should be implemented by the IMS
     * Provider.
     * @hide
     */
    @SystemApi
    public @NonNull ImsSmsImplBase getSmsImplementation() {
        return new ImsSmsImplBase();
    }

    private String getSmsFormat() {
        return getImsSmsImpl().getSmsFormat();
    }

    /**
     * {@inheritDoc}
     * @hide
     */
    @Override
    @SystemApi
    public void onFeatureRemoved() {
        // Base Implementation - Should be overridden
    }

    /**
     * {@inheritDoc}
     * @hide
     */
    @Override
    @SystemApi
    public void onFeatureReady() {
        // Base Implementation - Should be overridden
    }

    /**
     * @hide
     */
    @Override
    public final IImsMmTelFeature getBinder() {
        return mImsMMTelBinder;
    }

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