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

package com.android.phone;

import static android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION;
import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.DelegateRequest;
import android.telephony.ims.ImsException;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsUceAdapter.PublishState;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
import android.telephony.ims.aidl.ISipDelegate;
import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
import android.telephony.ims.aidl.ISipDelegateMessageCallback;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;

import com.android.ims.ImsManager;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.ISipDialogStateCallback;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.services.telephony.rcs.RcsFeatureController;
import com.android.services.telephony.rcs.SipTransportController;
import com.android.services.telephony.rcs.TelephonyRcsService;
import com.android.services.telephony.rcs.UceControllerManager;

import java.util.List;
import java.util.Set;

/**
 * Implementation of the IImsRcsController interface.
 */
public class ImsRcsController extends IImsRcsController.Stub {
    private static final String TAG = "ImsRcsController";

    /** The singleton instance. */
    private static ImsRcsController sInstance;

    private PhoneGlobals mApp;
    private TelephonyRcsService mRcsService;
    private ImsResolver mImsResolver;
    private FeatureFlags mFeatureFlags;
    private PackageManager mPackageManager;
    // set by shell cmd phone src set-device-enabled true/false
    private Boolean mSingleRegistrationOverride;
    private final int mVendorApiLevel;

    /**
     * For apps targeting Android T and above, support the publishing state on APIs, such as
     * {@code RcsUceAdapter#PUBLISH_STATE_PUBLISHING}
     * @hide
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
    public static final long SUPPORT_PUBLISHING_STATE = 202894742;

    /**
     * Initialize the singleton ImsRcsController instance.
     * This is only done once, at startup, from PhoneApp.onCreate().
     */
    static ImsRcsController init(PhoneGlobals app, FeatureFlags featureFlags) {
        synchronized (ImsRcsController.class) {
            if (sInstance == null) {
                sInstance = new ImsRcsController(app, featureFlags);
            } else {
                Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
            }
            return sInstance;
        }
    }

    /** Private constructor; @see init() */
    private ImsRcsController(PhoneGlobals app, FeatureFlags featureFlags) {
        Log.i(TAG, "ImsRcsController");
        mApp = app;
        mFeatureFlags = featureFlags;
        mPackageManager = mApp.getPackageManager();
        TelephonyFrameworkInitializer
                .getTelephonyServiceManager().getTelephonyImsServiceRegisterer().register(this);
        mImsResolver = ImsResolver.getInstance();
        mVendorApiLevel = SystemProperties.getInt(
                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
    }

    /**
     * Register a {@link RegistrationManager.RegistrationCallback} to receive IMS network
     * registration state.
     */
    @Override
    public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
                mApp, subId, "registerImsRegistrationCallback");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "registerImsRegistrationCallback");

        final long token = Binder.clearCallingIdentity();
        try {
            getRcsFeatureController(subId).registerImsRegistrationCallback(subId, callback);
        } catch (ImsException e) {
            Log.e(TAG, "registerImsRegistrationCallback: sudId=" + subId + ", " + e.getMessage());
            throw new ServiceSpecificException(e.getCode());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Removes an existing {@link RegistrationManager.RegistrationCallback}.
     */
    @Override
    public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
                mApp, subId, "unregisterImsRegistrationCallback");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "unregisterImsRegistrationCallback");

        final long token = Binder.clearCallingIdentity();
        try {
            getRcsFeatureController(subId).unregisterImsRegistrationCallback(subId, callback);
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "unregisterImsRegistrationCallback: error=" + e.errorCode);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Get the IMS service registration state for the RcsFeature associated with this sub id.
     */
    @Override
    public void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer) {
        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
                mApp, subId, "getImsRcsRegistrationState");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "getImsRcsRegistrationState");

        final long token = Binder.clearCallingIdentity();
        try {
            getRcsFeatureController(subId).getRegistrationState(regState -> {
                try {
                    consumer.accept((regState == null)
                            ? RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED : regState);
                } catch (RemoteException e) {
                    Log.w(TAG, "getImsRcsRegistrationState: callback is not available.");
                }
            });
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Gets the Transport Type associated with the current IMS RCS registration.
     */
    @Override
    public void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer) {
        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
                mApp, subId, "getImsRcsRegistrationTransportType");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "getImsRcsRegistrationTransportType");

        final long token = Binder.clearCallingIdentity();
        try {
            getRcsFeatureController(subId).getRegistrationTech(regTech -> {
                // Convert registration tech from ImsRegistrationImplBase -> RegistrationManager
                int regTechConverted = (regTech == null)
                        ? ImsRegistrationImplBase.REGISTRATION_TECH_NONE : regTech;
                regTechConverted = RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(
                        regTechConverted);
                try {
                    consumer.accept(regTechConverted);
                } catch (RemoteException e) {
                    Log.w(TAG, "getImsRcsRegistrationTransportType: callback is not available.");
                }
            });
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Register a capability callback which will provide RCS availability updates for the
     * subscription specified.
     *
     * @param subId the subscription ID
     * @param callback The ImsCapabilityCallback to be registered.
     */
    @Override
    public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
        enforceReadPrivilegedPermission("registerRcsAvailabilityCallback");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "registerRcsAvailabilityCallback");

        final long token = Binder.clearCallingIdentity();
        try {
            getRcsFeatureController(subId).registerRcsAvailabilityCallback(subId, callback);
        } catch (ImsException e) {
            Log.e(TAG, "registerRcsAvailabilityCallback: sudId=" + subId + ", " + e.getMessage());
            throw new ServiceSpecificException(e.getCode());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Remove the registered capability callback.
     *
     * @param subId the subscription ID
     * @param callback The ImsCapabilityCallback to be removed.
     */
    @Override
    public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
        enforceReadPrivilegedPermission("unregisterRcsAvailabilityCallback");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "unregisterRcsAvailabilityCallback");

        final long token = Binder.clearCallingIdentity();
        try {
            getRcsFeatureController(subId).unregisterRcsAvailabilityCallback(subId, callback);
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "unregisterRcsAvailabilityCallback: error=" + e.errorCode);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Query for the capability of an IMS RCS service
     *
     * @param subId the subscription ID
     * @param capability the RCS capability to query.
     * @param radioTech the radio technology type that we are querying.
     * @return true if the RCS capability is capable for this subscription, false otherwise.
     */
    @Override
    public boolean isCapable(int subId,
            @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
            @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
        enforceReadPrivilegedPermission("isCapable");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "isCapable");

        final long token = Binder.clearCallingIdentity();
        try {
            return getRcsFeatureController(subId).isCapable(capability, radioTech);
        } catch (ImsException e) {
            Log.e(TAG, "isCapable: sudId=" + subId
                    + ", capability=" + capability + ", " + e.getMessage());
            throw new ServiceSpecificException(e.getCode(), e.getMessage());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Query the availability of an IMS RCS capability.
     *
     * @param subId the subscription ID
     * @param capability the RCS capability to query.
     * @return true if the RCS capability is currently available for the associated subscription,
     * @param radioTech the radio technology type that we are querying.
     * false otherwise.
     */
    @Override
    public boolean isAvailable(int subId,
            @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
            @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
        enforceReadPrivilegedPermission("isAvailable");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "isAvailable");

        final long token = Binder.clearCallingIdentity();
        try {
            return getRcsFeatureController(subId).isAvailable(capability, radioTech);
        } catch (ImsException e) {
            Log.e(TAG, "isAvailable: sudId=" + subId
                    + ", capability=" + capability + ", " + e.getMessage());
            throw new ServiceSpecificException(e.getCode(), e.getMessage());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public void requestCapabilities(int subId, String callingPackage, String callingFeatureId,
            List<Uri> contactNumbers, IRcsUceControllerCallback c) {
        enforceAccessUserCapabilityExchangePermission("requestCapabilities");
        enforceReadContactsPermission("requestCapabilities");

        enforceTelephonyFeatureWithException(callingPackage,
                FEATURE_TELEPHONY_IMS, "requestCapabilities");

        final long token = Binder.clearCallingIdentity();
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                        "This subscription does not support UCE.");
            }
            uceCtrlManager.requestCapabilities(contactNumbers, c);
        } catch (ImsException e) {
            throw new ServiceSpecificException(e.getCode(), e.getMessage());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public void requestAvailability(int subId, String callingPackage,
            String callingFeatureId, Uri contactNumber, IRcsUceControllerCallback c) {
        enforceAccessUserCapabilityExchangePermission("requestAvailability");
        enforceReadContactsPermission("requestAvailability");

        enforceTelephonyFeatureWithException(callingPackage,
                FEATURE_TELEPHONY_IMS, "requestAvailability");

        final long token = Binder.clearCallingIdentity();
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                        "This subscription does not support UCE.");
            }
            uceCtrlManager.requestNetworkAvailability(contactNumber, c);
        } catch (ImsException e) {
            throw new ServiceSpecificException(e.getCode(), e.getMessage());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public @PublishState int getUcePublishState(int subId) {
        enforceReadPrivilegedPermission("getUcePublishState");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "getUcePublishState");

        final int uid = Binder.getCallingUid();
        final long token = Binder.clearCallingIdentity();
        boolean isSupportPublishingState = false;
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                        "This subscription does not support UCE.");
            }
            if (CompatChanges.isChangeEnabled(SUPPORT_PUBLISHING_STATE, uid)) {
                isSupportPublishingState = true;
            }
            return uceCtrlManager.getUcePublishState(isSupportPublishingState);
        } catch (ImsException e) {
            throw new ServiceSpecificException(e.getCode(), e.getMessage());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Add new feature tags to the Set used to calculate the capabilities in PUBLISH.
     */
    // Used for SHELL command only right now.
    public RcsContactUceCapability addUceRegistrationOverrideShell(int subId,
            Set<String> featureTags) throws ImsException {
        // Permission check happening in PhoneInterfaceManager.
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                return null;
            }
            return uceCtrlManager.addUceRegistrationOverride(featureTags);
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        }
    }

    /**
     * Remove existing feature tags to the Set used to calculate the capabilities in PUBLISH.
     */
    // Used for SHELL command only right now.
    public RcsContactUceCapability removeUceRegistrationOverrideShell(int subId,
            Set<String> featureTags) throws ImsException {
        // Permission check happening in PhoneInterfaceManager.
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                return null;
            }
            return uceCtrlManager.removeUceRegistrationOverride(featureTags);
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        }
    }

    /**
     * Clear all overrides in the Set used to calculate the capabilities in PUBLISH.
     */
    // Used for SHELL command only right now.
    public RcsContactUceCapability clearUceRegistrationOverrideShell(int subId)
            throws ImsException {
        try {
            // Permission check happening in PhoneInterfaceManager.
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                return null;
            }
            return uceCtrlManager.clearUceRegistrationOverride();
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        }
    }

    /**
     * @return current RcsContactUceCapability instance that will be used for PUBLISH.
     */
    // Used for SHELL command only right now.
    public RcsContactUceCapability getLatestRcsContactUceCapabilityShell(int subId)
            throws ImsException {
        try {
            // Permission check happening in PhoneInterfaceManager.
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                return null;
            }
            return uceCtrlManager.getLatestRcsContactUceCapability();
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        }
    }

    /**
     * @return the PIDf XML used in the last PUBLISH procedure or "none" if the device is not
     * published. Returns {@code null} if the operation failed due to an error.
     */
    // Used for SHELL command only right now.
    public String getLastUcePidfXmlShell(int subId) throws ImsException {
        try {
            // Permission check happening in PhoneInterfaceManager.
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                return null;
            }
            String pidfXml = uceCtrlManager.getLastPidfXml();
            return pidfXml == null ? "none" : pidfXml;
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        }
    }

    /**
     * Remove UCE requests cannot be sent to the network status.
     * @return true if this command is successful.
     */
    // Used for SHELL command only right now.
    public boolean removeUceRequestDisallowedStatus(int subId) throws ImsException {
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId, true).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                return false;
            }
            return uceCtrlManager.removeUceRequestDisallowedStatus();
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        }
    }

    /**
     * Set the timeout for contact capabilities request.
     */
    // Used for SHELL command only right now.
    public boolean setCapabilitiesRequestTimeout(int subId, long timeoutAfter) throws ImsException {
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId, true).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                return false;
            }
            return uceCtrlManager.setCapabilitiesRequestTimeout(timeoutAfter);
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        }
    }

    @Override
    public void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
        enforceReadPrivilegedPermission("registerUcePublishStateCallback");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "registerUcePublishStateCallback");

        final int uid = Binder.getCallingUid();
        final long token = Binder.clearCallingIdentity();
        boolean isSupportPublishingState = false;
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                        "This subscription does not support UCE.");
            }

            if (CompatChanges.isChangeEnabled(SUPPORT_PUBLISHING_STATE, uid)) {
                isSupportPublishingState = true;
            }
            uceCtrlManager.registerPublishStateCallback(c, isSupportPublishingState);
        } catch (ImsException e) {
            throw new ServiceSpecificException(e.getCode(), e.getMessage());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
        enforceReadPrivilegedPermission("unregisterUcePublishStateCallback");

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "unregisterUcePublishStateCallback");

        final long token = Binder.clearCallingIdentity();
        try {
            UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
                    UceControllerManager.class);
            if (uceCtrlManager == null) {
                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                        "This subscription does not support UCE.");
            }
            uceCtrlManager.unregisterPublishStateCallback(c);
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "unregisterUcePublishStateCallback: error=" + e.errorCode);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public boolean isUceSettingEnabled(int subId, String callingPackage, String callingFeatureId) {
        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                mApp, subId, callingPackage, callingFeatureId, "isUceSettingEnabled")) {
            Log.w(TAG, "isUceSettingEnabled: READ_PHONE_STATE app op disabled when accessing "
                    + "isUceSettingEnabled");
            return false;
        }

        enforceTelephonyFeatureWithException(callingPackage,
                FEATURE_TELEPHONY_IMS, "isUceSettingEnabled");

        final long token = Binder.clearCallingIdentity();
        try {
            return SubscriptionManager.getBooleanSubscriptionProperty(subId,
                    SubscriptionManager.IMS_RCS_UCE_ENABLED, false /*defaultValue*/, mApp);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public void setUceSettingEnabled(int subId, boolean isEnabled) {
        enforceModifyPermission();

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS, "setUceSettingEnabled");

        final long token = Binder.clearCallingIdentity();
        try {
            SubscriptionManager.setSubscriptionProperty(subId,
                    SubscriptionManager.IMS_RCS_UCE_ENABLED, (isEnabled ? "1" : "0"));
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public boolean isSipDelegateSupported(int subId) {
        TelephonyPermissions.enforceAnyPermissionGranted(mApp, Binder.getCallingUid(),
                "isSipDelegateSupported",
                Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION,
                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
        if (!isImsSingleRegistrationSupportedOnDevice()) {
            return false;
        }
        final long token = Binder.clearCallingIdentity();
        try {
            SipTransportController transport = getRcsFeatureController(subId).getFeature(
                    SipTransportController.class);
            if (transport == null) {
                return false;
            }
            return transport.isSupported(subId);
        } catch (ImsException e) {
            throw new ServiceSpecificException(e.getCode(), e.getMessage());
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
                return false;
            }
            throw e;
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public void createSipDelegate(int subId, DelegateRequest request, String packageName,
            ISipDelegateConnectionStateCallback delegateState,
            ISipDelegateMessageCallback delegateMessage) {
        enforceImsSingleRegistrationPermission("createSipDelegate");
        if (!isImsSingleRegistrationSupportedOnDevice()) {
            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                    "SipDelegate creation is only supported for devices supporting IMS single "
                            + "registration");
        }
        if (!UserHandle.getUserHandleForUid(Binder.getCallingUid()).isSystem()) {
            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                    "SipDelegate creation is only available to primary user.");
        }
        try {
            int remoteUid = mApp.getPackageManager().getPackageUid(packageName, 0 /*flags*/);
            if (Binder.getCallingUid() != remoteUid) {
                throw new SecurityException("passed in packageName does not match the caller");
            }
        } catch (PackageManager.NameNotFoundException e) {
            throw new SecurityException("Passed in PackageName can not be found on device");
        }

        final int uid = Binder.getCallingUid();
        final long identity = Binder.clearCallingIdentity();
        SipTransportController transport = getRcsFeatureController(subId).getFeature(
                SipTransportController.class);
        if (transport == null) {
            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                    "This subscription does not support the creation of SIP delegates");
        }
        try {
            transport.createSipDelegate(subId, uid, request, packageName, delegateState,
                    delegateMessage);
        } catch (ImsException e) {
            throw new ServiceSpecificException(e.getCode(), e.getMessage());
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    @Override
    public void destroySipDelegate(int subId, ISipDelegate connection, int reason) {
        // Do not check permissions here - the caller needs to have a connection already from the
        // create method to call this method.
        if (connection == null) {
            return;
        }
        final long identity = Binder.clearCallingIdentity();
        try {
            SipTransportController transport = getRcsFeatureController(subId).getFeature(
                    SipTransportController.class);
            if (transport == null) {
                return;
            }
            transport.destroySipDelegate(subId, connection, reason);
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "destroySipDelegate: error=" + e.errorCode);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    @Override
    public void triggerNetworkRegistration(int subId, ISipDelegate connection, int sipCode,
            String sipReason) {
        enforceImsSingleRegistrationPermission("triggerNetworkRegistration");

        final long identity = Binder.clearCallingIdentity();
        try {
            SipTransportController transport = getRcsFeatureController(subId).getFeature(
                    SipTransportController.class);
            if (transport == null) {
                return;
            }
            transport.triggerFullNetworkRegistration(subId, connection, sipCode, sipReason);
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "triggerNetworkRegistration: error=" + e.errorCode);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Register a state of Sip Dialog callback
     */
    @Override
    public void registerSipDialogStateCallback(int subId, ISipDialogStateCallback cb) {
        enforceReadPrivilegedPermission("registerSipDialogStateCallback");
        if (cb == null) {
            throw new IllegalArgumentException("SipDialogStateCallback is null");
        }
        final long identity = Binder.clearCallingIdentity();
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
        }

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "registerSipDialogStateCallback");

        try {
            SipTransportController transport = getRcsFeatureController(subId).getFeature(
                    SipTransportController.class);
            if (transport == null) {
                throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
                        "This transport does not support the registerSipDialogStateCallback"
                                + " of SIP delegates");
            }
            transport.addCallbackForSipDialogState(subId, cb);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Unregister a state of Sip Dialog callback
     */
    @Override
    public  void unregisterSipDialogStateCallback(int subId, ISipDialogStateCallback cb) {
        enforceReadPrivilegedPermission("unregisterSipDialogStateCallback");
        if (cb == null) {
            throw new IllegalArgumentException("SipDialogStateCallback is null");
        }
        final long identity = Binder.clearCallingIdentity();
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
        }

        enforceTelephonyFeatureWithException(getCurrentPackageName(),
                FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "unregisterSipDialogStateCallback");

        try {
            SipTransportController transport = getRcsFeatureController(subId).getFeature(
                    SipTransportController.class);
            if (transport == null) {
                throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
                        "This transport does not support the unregisterSipDialogStateCallback"
                                + " of SIP delegates");
            }
            transport.removeCallbackForSipDialogState(subId, cb);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Registers for updates to the RcsFeature connection through the IImsServiceFeatureCallback
     * callback.
     */
    @Override
    public void registerRcsFeatureCallback(int slotId, IImsServiceFeatureCallback callback) {
        enforceModifyPermission();

        final long identity = Binder.clearCallingIdentity();
        try {
            if (mImsResolver == null) {
                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                        "Device does not support IMS");
            }
            mImsResolver.listenForFeature(slotId, ImsFeature.FEATURE_RCS, callback);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Unregister a previously registered IImsServiceFeatureCallback associated with an ImsFeature.
     */
    @Override
    public void unregisterImsFeatureCallback(IImsServiceFeatureCallback callback) {
        enforceModifyPermission();

        final long identity = Binder.clearCallingIdentity();
        try {
            if (mImsResolver == null) return;
            mImsResolver.unregisterImsFeatureCallback(callback);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Make sure either called from same process as self (phone) or IPC caller has read privilege.
     *
     * @throws SecurityException if the caller does not have the required permission
     */
    private void enforceReadPrivilegedPermission(String message) {
        mApp.enforceCallingOrSelfPermission(
                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message);
    }

    /**
     * @throws SecurityException if the caller does not have the required
     *     PERFORM_IMS_SINGLE_REGISTRATION permission.
     */
    private void enforceImsSingleRegistrationPermission(String message) {
        mApp.enforceCallingOrSelfPermission(
                Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION, message);
    }

    /**
     * Make sure the caller has the MODIFY_PHONE_STATE permission.
     *
     * @throws SecurityException if the caller does not have the required permission
     */
    private void enforceModifyPermission() {
        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
    }

    /**
     * Make sure the caller has the ACCESS_RCS_USER_CAPABILITY_EXCHANGE permission.
     *
     * @throws SecurityException if the caller does not have the required permission.
     */
    private void enforceAccessUserCapabilityExchangePermission(String message) {
        mApp.enforceCallingOrSelfPermission(
                android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, message);
    }

    /**
     * Make sure the caller has the READ_CONTACTS permission.
     *
     * @throws SecurityException if the caller does not have the required permission.
     */
    private void enforceReadContactsPermission(String message) {
        mApp.enforceCallingOrSelfPermission(
                android.Manifest.permission.READ_CONTACTS, message);
    }

    /**
     * Retrieve RcsFeatureManager instance.
     *
     * @param subId the subscription ID
     * @return The RcsFeatureManager instance
     * @throws ServiceSpecificException if getting RcsFeatureManager instance failed.
     */
    private RcsFeatureController getRcsFeatureController(int subId) {
        return getRcsFeatureController(subId, false /* skipVerifyingConfig */);
    }

    /**
     * Retrieve RcsFeatureManager instance.
     *
     * @param subId the subscription ID
     * @param skipVerifyingConfig If the RCS configuration can be skip.
     * @return The RcsFeatureManager instance
     * @throws ServiceSpecificException if getting RcsFeatureManager instance failed.
     */
    private RcsFeatureController getRcsFeatureController(int subId, boolean skipVerifyingConfig) {
        if (!ImsManager.isImsSupportedOnDevice(mApp)) {
            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                    "IMS is not available on device.");
        }
        if (mRcsService == null) {
            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                    "IMS is not available on device.");
        }
        Phone phone = PhoneGlobals.getPhone(subId);
        if (phone == null) {
            throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
                    "Invalid subscription Id: " + subId);
        }
        int slotId = phone.getPhoneId();
        if (!skipVerifyingConfig) {
            verifyImsRcsConfiguredOrThrow(slotId);
            verifyRcsSubIdActiveOrThrow(slotId, subId);
        }
        RcsFeatureController c = mRcsService.getFeatureController(slotId);
        if (c == null) {
            // If we hit this case, we have verified that TelephonyRcsService has processed any
            // subId changes for the associated slot and applied configs. In this case, the configs
            // do not have the RCS feature enabled.
            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                    "The requested operation is not supported for subId " + subId);
        }
        if (!skipVerifyingConfig && c.getAssociatedSubId() != subId) {
            // If we hit this case, the ImsFeature has not finished setting up the RCS feature yet
            // or the RCS feature has crashed and is being set up again.
            Log.w(TAG, "getRcsFeatureController: service unavailable on slot " + slotId
                    + " for subId " + subId);
            throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
                    "The ImsService is not currently available for subid " + subId
                            + ", please try again");
        }
        return c;
    }

    /**
     * Ensure the TelephonyRcsService is tracking the supplied subId for the supplied slotId and has
     * set up the stack.
     */
    private void verifyRcsSubIdActiveOrThrow(int slotId, int subId) {
        if (mRcsService.verifyActiveSubId(slotId, subId)) return;

        Log.w(TAG, "verifyRcsSubIdActiveOrThrow: verify failed, service not set up yet on "
                + "slot " + slotId + " for subId " + subId);
        throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
                "ImsService set up in progress for subId " + subId
                        + ", please try again");
    }

    /**
     * Throw an ImsException if the IMS resolver does not have an ImsService configured for RCS
     * for the given slot ID or no ImsResolver instance has been created.
     * @param slotId The slot ID that the IMS service is created for.
     * @throws ServiceSpecificException If there is no ImsService configured for this slot.
     */
    private void verifyImsRcsConfiguredOrThrow(int slotId) {
        if (mImsResolver == null
                || !mImsResolver.isImsServiceConfiguredForFeature(slotId, ImsFeature.FEATURE_RCS)) {
            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                    "This subscription does not support RCS");
        }
    }

    private boolean isImsSingleRegistrationSupportedOnDevice() {
        return mSingleRegistrationOverride != null ? mSingleRegistrationOverride
                : mApp.getPackageManager().hasSystemFeature(
                        PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
    }

    /**
     * Get the current calling package name.
     * @return the current calling package name
     */
    @Nullable
    private String getCurrentPackageName() {
        if (mPackageManager == null) return null;
        String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
        return (callingUids == null) ? null : callingUids[0];
    }

    /**
     * Make sure the device has required telephony feature
     *
     * @throws UnsupportedOperationException if the device does not have required telephony feature
     */
    private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
            @NonNull String telephonyFeature, @NonNull String methodName) {
        if (callingPackage == null || mPackageManager == null) {
            return;
        }

        if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
                || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
                Binder.getCallingUserHandle())
                || mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
            // Skip to check associated telephony feature,
            // if compatibility change is not enabled for the current process or
            // the SDK version of vendor partition is less than Android V.
            return;
        }

        if (!mPackageManager.hasSystemFeature(telephonyFeature)) {
            throw new UnsupportedOperationException(
                    methodName + " is unsupported without " + telephonyFeature);
        }
    }

    void setRcsService(TelephonyRcsService rcsService) {
        mRcsService = rcsService;
    }

    /**
     * Override device RCS single registration support check for CTS testing or remove override
     * if the Boolean is set to null.
     */
    void setDeviceSingleRegistrationSupportOverride(Boolean deviceOverrideValue) {
        mSingleRegistrationOverride = deviceOverrideValue;
    }
}
