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


import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.telephony.AccessNetworkConstants;
import android.telephony.BinderCacheManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.flags.Flags;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * A manager for the MmTel (Multimedia Telephony) feature of an IMS network, given an associated
 * subscription.
 *
 * Allows a user to query the IMS MmTel feature information for a subscription, register for
 * registration and MmTel capability status callbacks, as well as query/modify user settings for the
 * associated subscription.
 *
 * Use {@link android.telephony.ims.ImsManager#getImsMmTelManager(int)} to get an instance of this
 * manager.
 */
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
public class ImsMmTelManager implements RegistrationManager {
    private static final String TAG = "ImsMmTelManager";

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = "WIFI_MODE_", value = {
            WIFI_MODE_UNKNOWN,
            WIFI_MODE_WIFI_ONLY,
            WIFI_MODE_CELLULAR_PREFERRED,
            WIFI_MODE_WIFI_PREFERRED
            })
    public @interface WiFiCallingMode {}

    /**
     * Wifi calling mode is unknown. This is for initialization only.
     * @hide
     */
    public static final int WIFI_MODE_UNKNOWN = -1;

    /**
     * Register for IMS over IWLAN if WiFi signal quality is high enough. Do not hand over to LTE
     * registration if signal quality degrades.
     */
    public static final int WIFI_MODE_WIFI_ONLY = 0;

    /**
     * Prefer registering for IMS over LTE if LTE signal quality is high enough.
     */
    public static final int WIFI_MODE_CELLULAR_PREFERRED = 1;

    /**
     * Prefer registering for IMS over IWLAN if possible if WiFi signal quality is high enough.
     */
    public static final int WIFI_MODE_WIFI_PREFERRED = 2;

    /**
     * Callback class for receiving IMS network Registration callback events.
     * @see #registerImsRegistrationCallback(Executor, RegistrationCallback) (RegistrationCallback)
     * @see #unregisterImsRegistrationCallback(RegistrationCallback)
     * @deprecated Use {@link RegistrationManager.RegistrationCallback} instead.
     * @hide
     */
    // Do not add to this class, add to RegistrationManager.RegistrationCallback instead.
    @Deprecated
    @SystemApi
    public static class RegistrationCallback extends RegistrationManager.RegistrationCallback {

        /**
         * Notifies the framework when the IMS Provider is registered to the IMS network.
         *
         * @param imsTransportType the radio access technology.
         */
        @Override
        public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType) {
        }

        /**
         * Notifies the framework when the IMS Provider is trying to register the IMS network.
         *
         * @param imsTransportType the radio access technology.
         */
        @Override
        public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType) {
        }

        /**
         * Notifies the framework when the IMS Provider is deregistered from the IMS network.
         *
         * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
         */
        @Override
        public void onUnregistered(@NonNull ImsReasonInfo info) {
        }

        /**
         * A failure has occurred when trying to handover registration to another technology type.
         *
         * @param imsTransportType The transport type that has failed to handover registration to.
         * @param info A {@link ImsReasonInfo} that identifies the reason for failure.
         */
        @Override
        public void onTechnologyChangeFailed(
                @AccessNetworkConstants.TransportType int imsTransportType,
                @NonNull ImsReasonInfo info) {
        }
    }

    /**
     * Receives IMS capability status updates from the ImsService.
     *
     * @see #registerMmTelCapabilityCallback(Executor, CapabilityCallback) (CapabilityCallback)
     * @see #unregisterMmTelCapabilityCallback(CapabilityCallback)
     */
    public static class CapabilityCallback {

        private static class CapabilityBinder extends IImsCapabilityCallback.Stub {

            private final CapabilityCallback mLocalCallback;
            private Executor mExecutor;

            CapabilityBinder(CapabilityCallback c) {
                mLocalCallback = c;
            }

            @Override
            public void onCapabilitiesStatusChanged(int config) {
                if (mLocalCallback == null) return;

                final long callingIdentity = Binder.clearCallingIdentity();
                try {
                    mExecutor.execute(() -> mLocalCallback.onCapabilitiesStatusChanged(
                            new MmTelFeature.MmTelCapabilities(config)));
                } finally {
                    restoreCallingIdentity(callingIdentity);
                }
            }

            @Override
            public void onQueryCapabilityConfiguration(int capability, int radioTech,
                    boolean isEnabled) {
                // This is not used for public interfaces.
            }

            @Override
            public void onChangeCapabilityConfigurationError(int capability, int radioTech,
                    @ImsFeature.ImsCapabilityError int reason) {
                // This is not used for public interfaces
            }

            private void setExecutor(Executor executor) {
                mExecutor = executor;
            }
        }

        private final CapabilityBinder mBinder = new CapabilityBinder(this);

        /**
         * The status of the feature's capabilities has changed to either available or unavailable.
         * If unavailable, the feature is not able to support the unavailable capability at this
         * time.
         *
         * @param capabilities The new availability of the capabilities.
         */
        public void onCapabilitiesStatusChanged(
                @NonNull MmTelFeature.MmTelCapabilities capabilities) {
        }

        /**@hide*/
        public final IImsCapabilityCallback getBinder() {
            return mBinder;
        }

        /**@hide*/
        // Only exposed as public method for compatibility with deprecated ImsManager APIs.
        // TODO: clean up dependencies and change back to private visibility.
        public final void setExecutor(Executor executor) {
            mBinder.setExecutor(executor);
        }
    }

    private final Context mContext;
    private final int mSubId;
    private final BinderCacheManager<ITelephony> mBinderCache;

    // Cache Telephony Binder interfaces, one cache per process.
    private static final BinderCacheManager<ITelephony> sTelephonyCache =
            new BinderCacheManager<>(ImsMmTelManager::getITelephonyInterface);

    /**
     * Create an instance of {@link ImsMmTelManager} for the subscription id specified.
     *
     * @param subId The ID of the subscription that this ImsMmTelManager will use.
     * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
     *
     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
     *
     * @throws IllegalArgumentException if the subscription is invalid.
     * @deprecated Use {@link android.telephony.ims.ImsManager#getImsMmTelManager(int)} to get an
     * instance of this class.
     * @hide
     */
    @SystemApi
    @Deprecated
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE
    })
    @SuppressLint("ManagerLookup")
    public static @NonNull ImsMmTelManager createForSubscriptionId(int subId) {
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            throw new IllegalArgumentException("Invalid subscription ID");
        }

        return new ImsMmTelManager(subId, sTelephonyCache);
    }

    /**
     * Only visible for testing, use {@link ImsManager#getImsMmTelManager(int)} instead.
     * @hide
     */
    @VisibleForTesting
    public ImsMmTelManager(int subId, BinderCacheManager<ITelephony> binderCache) {
        this(null, subId, binderCache);
    }

    /**
     * Only visible for testing, use {@link ImsManager#getImsMmTelManager(int)} instead.
     * @hide
     */
    @VisibleForTesting
    public ImsMmTelManager(Context context, int subId, BinderCacheManager<ITelephony> binderCache) {
        mContext = context;
        mSubId = subId;
        mBinderCache = binderCache;
    }

    /**
     * Registers a {@link RegistrationCallback} with the system, which will provide registration
     * updates for the subscription specified in {@link ImsManager#getImsMmTelManager(int)}. Use
     * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed
     * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up.
     *
     * When the callback is registered, it will initiate the callback c to be called with the
     * current registration state.
     *
     * @param executor The executor the callback events should be run on.
     * @param c The {@link RegistrationCallback} to be added.
     * @see #unregisterImsRegistrationCallback(RegistrationCallback)
     * @throws IllegalArgumentException if the subscription associated with this callback is not
     * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or
     * {@link CapabilityCallback} callback.
     * @throws ImsException if the subscription associated with this callback is valid, but
     * the {@link ImsService} associated with the subscription is not available. This can happen if
     * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
     * reason.
     * @deprecated Use {@link RegistrationManager#registerImsRegistrationCallback(Executor,
     * RegistrationManager.RegistrationCallback)} instead.
     * @hide
     */
    @Deprecated
    @SystemApi
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public void registerImsRegistrationCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull RegistrationCallback c) throws ImsException {
        if (c == null) {
            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must include a non-null Executor.");
        }
        c.setExecutor(executor);

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new ImsException("Could not find Telephony Service.",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            iTelephony.registerImsRegistrationCallback(mSubId, c.getBinder());
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new ImsException(e.getMessage(), e.errorCode);
            }
        } catch (RemoteException | IllegalStateException e) {
            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }
    }

     /**
     *
     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
     *
     * {@inheritDoc}
     *
     */
    @Override
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public void registerImsRegistrationCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull RegistrationManager.RegistrationCallback c) throws ImsException {
        if (c == null) {
            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must include a non-null Executor.");
        }
        c.setExecutor(executor);

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new ImsException("Could not find Telephony Service.",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            iTelephony.registerImsRegistrationCallback(mSubId, c.getBinder());
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        } catch (RemoteException | IllegalStateException e) {
            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }
    }

    /**
     * Removes an existing {@link RegistrationCallback}.
     *
     * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
     * etc...), this callback will automatically be removed. If this method is called for an
     * inactive subscription, it will result in a no-op.
     *
     * @param c The {@link RegistrationCallback} to be removed.
     * @see SubscriptionManager.OnSubscriptionsChangedListener
     * @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
     * @deprecated Use {@link #unregisterImsRegistrationCallback(
     * RegistrationManager.RegistrationCallback)}.
     * @hide
     */
    @Deprecated
    @SystemApi
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public void unregisterImsRegistrationCallback(@NonNull RegistrationCallback c) {
        if (c == null) {
            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
        }

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.unregisterImsRegistrationCallback(mSubId, c.getBinder());
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

     /**
     *
     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     *{@inheritDoc}
     */
    @Override
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public void unregisterImsRegistrationCallback(
            @NonNull RegistrationManager.RegistrationCallback c) {
        if (c == null) {
            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
        }

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.unregisterImsRegistrationCallback(mSubId, c.getBinder());
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Registers a {@link RegistrationCallback} with the system, which will provide IMS emergency
     * registration updates for the subscription specified in
     * {@link ImsManager#getImsMmTelManager(int)}. Use
     * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed
     * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up.
     *
     * When the callback is registered, it will initiate the callback c to be called with the
     * current emergency registration state.
     * Emergency registration callback is available when there is valid SIM card.
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @param executor The executor the callback events should be run on.
     * @param c The {@link RegistrationCallback} to be added.
     * @see #unregisterImsEmergencyRegistrationCallback
     * @throws ImsException if the subscription associated with this callback is valid, but
     * the {@link ImsService} associated with the subscription is not available. This can happen if
     * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
     * reason.
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
    public void registerImsEmergencyRegistrationCallback(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull RegistrationManager.RegistrationCallback c) throws ImsException {
        if (c == null) {
            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must include a non-null Executor.");
        }
        c.setExecutor(executor);

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new ImsException("Could not find Telephony Service.",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            iTelephony.registerImsEmergencyRegistrationCallback(mSubId, c.getBinder());
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }  catch (IllegalStateException e) {
            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }
    }

    /**
     * Removes an existing {@link RegistrationCallback} for Emergency IMS registration.
     *
     * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
     * etc...), this callback will automatically be removed. If this method is called for an
     * inactive subscription, it will result in a no-op.
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @param c The {@link RegistrationCallback} to be removed.
     * @see android.telephony.SubscriptionManager.OnSubscriptionsChangedListener
     * @see #registerImsEmergencyRegistrationCallback(Executor,
     *                                 RegistrationManager.RegistrationCallback)
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
    public void unregisterImsEmergencyRegistrationCallback(
            @NonNull RegistrationManager.RegistrationCallback c) {
        if (c == null) {
            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
        }

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            Log.w("ImsMmTelManager", "Could not find Telephony Service.");
            return;
        }

        try {
            iTelephony.unregisterImsEmergencyRegistrationCallback(mSubId, c.getBinder());
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * {@inheritDoc}
     * @hide
     */
    @Override
    @SystemApi
    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
            @NonNull @ImsRegistrationState Consumer<Integer> stateCallback) {
        if (stateCallback == null) {
            throw new IllegalArgumentException("Must include a non-null callback.");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must include a non-null Executor.");
        }

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.getImsMmTelRegistrationState(mSubId, new IIntegerConsumer.Stub() {
                @Override
                public void accept(int result) {
                    final long identity = Binder.clearCallingIdentity();
                    try {
                        executor.execute(() -> stateCallback.accept(result));
                    } finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
            });
        } catch (ServiceSpecificException | RemoteException e) {
            Log.w("ImsMmTelManager", "Error getting registration state: " + e);
            executor.execute(() -> stateCallback.accept(REGISTRATION_STATE_NOT_REGISTERED));
        }
    }

    /**
     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     *{@inheritDoc}
     */
    @Override
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
            @NonNull @AccessNetworkConstants.TransportType
                    Consumer<Integer> transportTypeCallback) {
        if (transportTypeCallback == null) {
            throw new IllegalArgumentException("Must include a non-null callback.");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must include a non-null Executor.");
        }

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.getImsMmTelRegistrationTransportType(mSubId,
                    new IIntegerConsumer.Stub() {
                        @Override
                        public void accept(int result) {
                            final long identity = Binder.clearCallingIdentity();
                            try {
                                executor.execute(() -> transportTypeCallback.accept(result));
                            } finally {
                                Binder.restoreCallingIdentity(identity);
                            }
                        }
                    });
        } catch (ServiceSpecificException | RemoteException e) {
            Log.w("ImsMmTelManager", "Error getting transport type: " + e);
            executor.execute(() -> transportTypeCallback.accept(
                    AccessNetworkConstants.TRANSPORT_TYPE_INVALID));
        }
    }

    /**
     * Registers a {@link CapabilityCallback} with the system, which will provide MmTel service
     * availability updates for the subscription specified in
     * {@link ImsManager#getImsMmTelManager(int)}.
     *
     * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
     * subscription changed events and call
     * {@link #unregisterMmTelCapabilityCallback(CapabilityCallback)} to clean up.
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * When the callback is registered, it will initiate the callback c to be called with the
     * current capabilities.
     *
     * @param executor The executor the callback events should be run on.
     * @param c The MmTel {@link CapabilityCallback} to be registered.
     * @see #unregisterMmTelCapabilityCallback(CapabilityCallback)
     * @throws ImsException if the subscription associated with this callback is valid, but
     * the {@code ImsService} associated with the subscription is not available. This can happen if
     * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
     * reason.
     */
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull CapabilityCallback c) throws ImsException {
        if (c == null) {
            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must include a non-null Executor.");
        }
        c.setExecutor(executor);

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new ImsException("Could not find Telephony Service.",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            iTelephony.registerMmTelCapabilityCallback(mSubId, c.getBinder());
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }  catch (IllegalStateException e) {
            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }
    }

    /**
     * Removes an existing MmTel {@link CapabilityCallback}.
     *
     * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
     * etc...), this callback will automatically be removed. If this method is called for an
     * inactive subscription, it will result in a no-op.
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @param c The MmTel {@link CapabilityCallback} to be removed.
     * @see #registerMmTelCapabilityCallback(Executor, CapabilityCallback)
     */
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public void unregisterMmTelCapabilityCallback(@NonNull CapabilityCallback c) {
        if (c == null) {
            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
        }

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            Log.w("ImsMmTelManager", "Could not find Telephony Service.");
            return;
        }
        try {
            iTelephony.unregisterMmTelCapabilityCallback(mSubId, c.getBinder());
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Query the user’s setting for “Advanced Calling” or "Enhanced 4G LTE", which is used to
     * enable MmTel IMS features, depending on the carrier configuration for the current
     * subscription. If this setting is enabled, IMS voice and video telephony over IWLAN/LTE will
     * be enabled as long as the carrier has provisioned these services for the specified
     * subscription. Other IMS services (SMS/UT) are not affected by this user setting and depend on
     * carrier requirements.
     * <p>
     * Note: If the carrier configuration for advanced calling is not editable or hidden, this
     * method will always return the default value.
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_HIDE_ENHANCED_4G_LTE_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @return true if the user's setting for advanced calling is enabled, false otherwise.
     */
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public boolean isAdvancedCallingSettingEnabled() {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.isAdvancedCallingSettingEnabled(mSubId);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Modify the user’s setting for “Advanced Calling” or "Enhanced 4G LTE", which is used to
     * enable MmTel IMS features, depending on the carrier configuration for the current
     * subscription. If this setting is enabled, IMS voice and video telephony over IWLAN/LTE will
     * be enabled as long as the carrier has provisioned these services for the specified
     * subscription. Other IMS services (SMS/UT) are not affected by this user setting and depend on
     * carrier requirements.
     *
     * Modifying this value may also trigger an IMS registration or deregistration, depending on
     * whether or not the new value is enabled or disabled.
     *
     * Note: If the carrier configuration for advanced calling is not editable or hidden, this
     * method will do nothing and will instead always use the default value.
     *
     * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_HIDE_ENHANCED_4G_LTE_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL
     * @see #isAdvancedCallingSettingEnabled()
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @hide
     */
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    @SystemApi
    public void setAdvancedCallingSettingEnabled(boolean isEnabled) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.setAdvancedCallingSettingEnabled(mSubId, isEnabled);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Query the IMS MmTel capability for a given registration technology. This does not
     * necessarily mean that we are registered and the capability is available, but rather the
     * subscription is capable of this service over IMS.
     *
     * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VT_AVAILABLE_BOOL
     * @see android.telephony.CarrierConfigManager#KEY_CARRIER_IMS_GBA_REQUIRED_BOOL
     * @see #isAvailable(int, int)
     *
     * @param imsRegTech The IMS registration technology.
     * @param capability The IMS MmTel capability to query.
     * @return {@code true} if the MmTel IMS capability is capable for this subscription, false
     *         otherwise.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @hide
     */
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    @SystemApi
    public boolean isCapable(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
            @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.isCapable(mSubId, capability, imsRegTech);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Query the availability of an IMS MmTel capability for a given registration technology. If
     * a capability is available, IMS is registered and the service is currently available over IMS.
     *
     * @see #isCapable(int, int)
     *
     * @param imsRegTech The IMS registration technology.
     * @param capability The IMS MmTel capability to query.
     * @return {@code true} if the MmTel IMS capability is available for this subscription, false
     *         otherwise.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public boolean isAvailable(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
            @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.isAvailable(mSubId, capability, imsRegTech);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Query whether or not the requested MmTel capability is supported by the carrier on the
     * specified network transport.
     * <p>
     * This is a configuration option and does not change. The only time this may change is if a
     * new IMS configuration is loaded when there is a
     * {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} broadcast for this subscription.
     * @param capability The capability that is being queried for support on the carrier network.
     * @param transportType The transport type of the capability to check support for.
     * @param executor The executor that the callback will be called with.
     * @param callback A consumer containing a Boolean result specifying whether or not the
     *                 capability is supported on this carrier network for the transport specified.
     * @throws ImsException if the subscription is no longer valid or the IMS service is not
     * available.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public void isSupported(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
            @AccessNetworkConstants.TransportType int transportType,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<Boolean> callback) throws ImsException {
        if (callback == null) {
            throw new IllegalArgumentException("Must include a non-null Consumer.");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must include a non-null Executor.");
        }

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new ImsException("Could not find Telephony Service.",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            iTelephony.isMmTelCapabilitySupported(mSubId, new IIntegerConsumer.Stub() {
                @Override
                public void accept(int result) {
                    final long identity = Binder.clearCallingIdentity();
                    try {
                        executor.execute(() -> callback.accept(result == 1));
                    } finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
            }, capability, transportType);
        } catch (ServiceSpecificException sse) {
            throw new ImsException(sse.getMessage(), sse.errorCode);
        } catch (RemoteException e) {
            e.rethrowAsRuntimeException();
        }
    }

    /**
     * The user's setting for whether or not they have enabled the "Video Calling" setting.
     *
     * <p>
     * Note: If the carrier configuration for advanced calling is not editable or hidden, this
     * method will always return the default value.
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @return true if the user’s “Video Calling” setting is currently enabled.
     */
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    public boolean isVtSettingEnabled() {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.isVtSettingEnabled(mSubId);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Change the user's setting for Video Telephony and enable the Video Telephony capability.
     *
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @see #isVtSettingEnabled()
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    public void setVtSettingEnabled(boolean isEnabled) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.setVtSettingEnabled(mSubId, isEnabled);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * @return true if the user's setting for Voice over WiFi is enabled and false if it is not.
     *
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     */
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public boolean isVoWiFiSettingEnabled() {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.isVoWiFiSettingEnabled(mSubId);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Sets the user's setting for whether or not Voice over WiFi is enabled.
     *
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @param isEnabled true if the user's setting for Voice over WiFi is enabled, false otherwise=
     * @see #isVoWiFiSettingEnabled()
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    public void setVoWiFiSettingEnabled(boolean isEnabled) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.setVoWiFiSettingEnabled(mSubId, isEnabled);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * This configuration is meaningful only on dual sim device.
     * If enabled, this will result in the device setting up IMS of all other
     * active subscriptions over the INTERNET APN of the primary default data subscription
     * when any of those subscriptions are roaming or out of service and if wifi is not available
     * for VoWifi. This feature will be disabled if
     * {@link CarrierConfigManager#KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL} is set to false.
     * <p>Following are the conditions in which system will try to register IMS over
     * cross sim
     * <ul>
     *     <li>Wifi is not available, one SIM is roaming and the default data
     *     SIM is in home network. Then roaming SIM IMS will be registered over INTERNET APN of the
     *     default data subscription </li>
     *     <li>Wifi is not available, one SIM is out of service and the default data
     *     SIM is in home network. Then out of service SIM IMS will be registered over INTERNET
     *     APN of the default data subscription </li>
     * </ul>
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @throws ImsException if the IMS service associated with this subscription is not available or
     * the IMS service is not available.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @return true if the user's setting for Voice over Cross SIM is enabled and false if it is not
     */
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public boolean isCrossSimCallingEnabled() throws ImsException {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new ImsException("Could not find Telephony Service.",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            return iTelephony.isCrossSimCallingEnabledByUser(mSubId);
        } catch (ServiceSpecificException sse) {
            throw new ImsException(sse.getMessage(), sse.errorCode);
        } catch (RemoteException e) {
            e.rethrowAsRuntimeException();
        }
        // Not reachable. Adding return to make compiler happy.
        return false;
    }

    /**
     * Sets the user's setting for whether or not Voice over Cross SIM is enabled.
     * If enabled, this will result in the device setting up IMS of all other
     * active subscriptions over the INTERNET APN of the primary default data subscription
     * when any of those subscriptions are roaming or out of service and if wifi is not available
     * for VoWifi. This feature will be disabled if
     * {@link CarrierConfigManager#KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL} is set to false.
     *
     * <p>Following are the conditions in which system will try to register IMS over
     * cross sim
     * <ul>
     *     <li>Wifi is not available, one SIM is roaming and the default data
     *     SIM is in home network. Then roaming SIM IMS will be registered over INTERNET APN of the
     *     default data subscription </li>
     *     <li>Wifi is not available, one SIM is out of service and the default data
     *     SIM is in home network. Then out of service SIM IMS will be registered over INTERNET
     *     APN of the default data subscription </li>
     * </ul>
     * @throws ImsException if the IMS service associated with this subscription is not available or
     * the IMS service is not available.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @param isEnabled true if the user's setting for Voice over Cross SIM is enabled,
     *                 false otherwise
     * @see #isCrossSimCallingEnabled()
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    public void setCrossSimCallingEnabled(boolean isEnabled) throws ImsException {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new ImsException("Could not find Telephony Service.",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            iTelephony.setCrossSimCallingEnabled(mSubId, isEnabled);
        } catch (ServiceSpecificException sse) {
            throw new ImsException(sse.getMessage(), sse.errorCode);
        } catch (RemoteException e) {
            e.rethrowAsRuntimeException();
        }
    }

    /**
     * Returns the user's voice over WiFi roaming setting associated with the current subscription.
     *
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @return true if the user's setting for Voice over WiFi while roaming is enabled, false
     * if disabled.
     */
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public boolean isVoWiFiRoamingSettingEnabled() {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.isVoWiFiRoamingSettingEnabled(mSubId);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Change the user's setting for Voice over WiFi while roaming.
     *
     * @param isEnabled true if the user's setting for Voice over WiFi while roaming is enabled,
     *     false otherwise.
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @see #isVoWiFiRoamingSettingEnabled()
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    public void setVoWiFiRoamingSettingEnabled(boolean isEnabled) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.setVoWiFiRoamingSettingEnabled(mSubId, isEnabled);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Overrides the Voice over WiFi capability to true for IMS, but do not persist the setting.
     * Typically used during the Voice over WiFi registration process for some carriers.
     *
     * @param isCapable true if the IMS stack should try to register for IMS over IWLAN, false
     *     otherwise.
     * @param mode the Voice over WiFi mode preference to set, which can be one of the following:
     * - {@link #WIFI_MODE_WIFI_ONLY}
     * - {@link #WIFI_MODE_CELLULAR_PREFERRED}
     * - {@link #WIFI_MODE_WIFI_PREFERRED}
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @see #setVoWiFiSettingEnabled(boolean)
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    public void setVoWiFiNonPersistent(boolean isCapable, int mode) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.setVoWiFiNonPersistent(mSubId, isCapable, mode);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Returns the user's voice over WiFi Roaming mode setting associated with the device.
     *
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @return The Voice over WiFi Mode preference set by the user, which can be one of the
     * following:
     * - {@link #WIFI_MODE_WIFI_ONLY}
     * - {@link #WIFI_MODE_CELLULAR_PREFERRED}
     * - {@link #WIFI_MODE_WIFI_PREFERRED}
     */
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public @WiFiCallingMode int getVoWiFiModeSetting() {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.getVoWiFiModeSetting(mSubId);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Set the user's preference for Voice over WiFi calling mode.
     * @param mode The user's preference for the technology to register for IMS over, can be one of
     *    the following:
     * - {@link #WIFI_MODE_WIFI_ONLY}
     * - {@link #WIFI_MODE_CELLULAR_PREFERRED}
     * - {@link #WIFI_MODE_WIFI_PREFERRED}
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @see #getVoWiFiModeSetting()
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    public void setVoWiFiModeSetting(@WiFiCallingMode int mode) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.setVoWiFiModeSetting(mSubId, mode);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Set the user's preference for Voice over WiFi calling mode while the device is roaming on
     * another network.
     *
     * @return The user's preference for the technology to register for IMS over when roaming on
     *     another network, can be one of the following:
     *     - {@link #WIFI_MODE_WIFI_ONLY}
     *     - {@link #WIFI_MODE_CELLULAR_PREFERRED}
     *     - {@link #WIFI_MODE_WIFI_PREFERRED}
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @see #setVoWiFiRoamingSettingEnabled(boolean)
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public @WiFiCallingMode int getVoWiFiRoamingModeSetting() {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.getVoWiFiRoamingModeSetting(mSubId);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Set the user's preference for Voice over WiFi mode while the device is roaming on another
     * network.
     *
     * @param mode The user's preference for the technology to register for IMS over when roaming on
     *     another network, can be one of the following:
     *     - {@link #WIFI_MODE_WIFI_ONLY}
     *     - {@link #WIFI_MODE_CELLULAR_PREFERRED}
     *     - {@link #WIFI_MODE_WIFI_PREFERRED}
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @see #getVoWiFiRoamingModeSetting()
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    public void setVoWiFiRoamingModeSetting(@WiFiCallingMode int mode) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.setVoWiFiRoamingModeSetting(mSubId, mode);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Sets the capability of RTT for IMS calls placed on this subscription.
     *
     * Note: This does not affect the value of
     * {@link android.provider.Settings.Secure#RTT_CALLING_MODE}, which is the global user setting
     * for RTT. That value is enabled/disabled separately by the user through the Accessibility
     * settings.
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @param isEnabled if true RTT should be enabled during calls made on this subscription.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    public void setRttCapabilitySetting(boolean isEnabled) {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            iTelephony.setRttCapabilitySetting(mSubId, isEnabled);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * @return true if TTY over VoLTE is supported
     *
     * <p>This API requires one of the following:
     * <ul>
     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
     *     <li>If the caller is the device or profile owner, the caller holds the
     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
     *     <li>The caller has carrier privileges (see
     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
     *     active subscription.</li>
     * </ul>
     * <p>The profile owner is an app that owns a managed profile on the device; for more details
     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
     * Access by profile owners is deprecated and will be removed in a future release.
     *
     * @throws IllegalArgumentException if the subscription associated with this operation is not
     * active (SIM is not inserted, ESIM inactive) or invalid.
     * @throws UnsupportedOperationException If the device does not have
     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
     * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
     */
    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
    @RequiresPermission(anyOf = {
            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
    public boolean isTtyOverVolteEnabled() {
        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new RuntimeException("Could not find Telephony Service.");
        }

        try {
            return iTelephony.isTtyOverVolteEnabled(mSubId);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
                // Rethrow as runtime error to keep API compatible.
                throw new IllegalArgumentException(e.getMessage());
            } else {
                throw new RuntimeException(e.getMessage());
            }
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    /**
     * Get the status of the MmTel Feature registered on this subscription.
     * @param executor The executor that will be used to call the callback.
     * @param callback A callback containing an Integer describing the current state of the
     *                 MmTel feature, Which will be one of the following:
     *                 {@link ImsFeature#STATE_UNAVAILABLE},
     *                {@link ImsFeature#STATE_INITIALIZING},
     *                {@link ImsFeature#STATE_READY}. Will be called using the executor
     *                 specified when the service state has been retrieved from the IMS service.
     * @throws ImsException if the IMS service associated with this subscription is not available or
     * the IMS service is not available.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public void getFeatureState(@NonNull @CallbackExecutor Executor executor,
            @NonNull @ImsFeature.ImsState Consumer<Integer> callback) throws ImsException {
        if (executor == null) {
            throw new IllegalArgumentException("Must include a non-null Executor.");
        }
        if (callback == null) {
            throw new IllegalArgumentException("Must include a non-null Consumer.");
        }

        ITelephony iTelephony = getITelephony();
        if (iTelephony == null) {
            throw new ImsException("Could not find Telephony Service.",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            iTelephony.getImsMmTelFeatureState(mSubId, new IIntegerConsumer.Stub() {
                @Override
                public void accept(int result) {
                    final long identity = Binder.clearCallingIdentity();
                    try {
                        executor.execute(() -> callback.accept(result));
                    } finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
            });
        } catch (ServiceSpecificException sse) {
            throw new ImsException(sse.getMessage(), sse.errorCode);
        } catch (RemoteException e) {
            e.rethrowAsRuntimeException();
        }
    }

    /**
     * Register a new callback, which is used to notify the registrant of changes to
     * the state of the underlying IMS service that is attached to telephony to
     * implement IMS functionality. If the manager is created for
     * the {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID},
     * this throws an {@link ImsException}.
     *
     * <p>Requires Permission:
     * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE READ_PRECISE_PHONE_STATE}
     * or that the calling app has carrier privileges
     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
     *
     * @param executor the Executor that will be used to call the {@link ImsStateCallback}.
     * @param callback The callback instance being registered.
     * @throws ImsException in the case that the callback can not be registered.
     * See {@link ImsException#getCode} for more information on when this is called.
     */
    @RequiresPermission(anyOf = {Manifest.permission.READ_PRECISE_PHONE_STATE,
            Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
    public void registerImsStateCallback(@NonNull Executor executor,
            @NonNull ImsStateCallback callback) throws ImsException {
        Objects.requireNonNull(callback, "Must include a non-null ImsStateCallback.");
        Objects.requireNonNull(executor, "Must include a non-null Executor.");

        callback.init(executor);
        ITelephony telephony = mBinderCache.listenOnBinder(callback, callback::binderDied);
        if (telephony == null) {
            throw new ImsException("Telephony server is down",
                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }

        try {
            telephony.registerImsStateCallback(
                    mSubId, ImsFeature.FEATURE_MMTEL,
                    callback.getCallbackBinder(), getOpPackageName());
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        } catch (RemoteException | IllegalStateException e) {
            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        }
    }

    /**
     * Unregisters a previously registered callback.
     *
     * @param callback The callback instance to be unregistered.
     */
    public void unregisterImsStateCallback(@NonNull ImsStateCallback callback) {
        Objects.requireNonNull(callback, "Must include a non-null ImsStateCallback.");

        ITelephony telephony = mBinderCache.removeRunnable(callback);
        try {
            if (telephony != null) {
                telephony.unregisterImsStateCallback(callback.getCallbackBinder());
            }
        } catch (RemoteException ignore) {
            // ignore it
        }
    }

    private String getOpPackageName() {
        if (mContext != null) {
            return mContext.getOpPackageName();
        } else {
            return null;
        }
    }

    private ITelephony getITelephony() {
        return mBinderCache.getBinder();
    }

    private static ITelephony getITelephonyInterface() {
        ITelephony binder = ITelephony.Stub.asInterface(
                TelephonyFrameworkInitializer
                        .getTelephonyServiceManager()
                        .getTelephonyServiceRegisterer()
                        .get());
        return binder;
    }

    /**
     * Convert Wi-Fi calling mode to string.
     *
     * @param mode Wi-Fi calling mode.
     * @return The Wi-Fi calling mode in string format.
     *
     * @hide
     */
    @NonNull
    public static String wifiCallingModeToString(@ImsMmTelManager.WiFiCallingMode int mode) {
        switch (mode) {
            case ImsMmTelManager.WIFI_MODE_UNKNOWN: return "UNKNOWN";
            case ImsMmTelManager.WIFI_MODE_WIFI_ONLY: return "WIFI_ONLY";
            case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED: return "CELLULAR_PREFERRED";
            case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED: return "WIFI_PREFERRED";
            default:
                return "UNKNOWN(" + mode + ")";
        }
    }
}
