content: /*
   * 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.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.RequiresPermission;
  import android.annotation.SystemApi;
  import android.content.Context;
  import android.content.pm.IPackageManager;
  import android.content.pm.PackageManager;
  import android.net.Uri;
  import android.os.Binder;
  import android.os.RemoteException;
  import android.os.ServiceManager;
  import android.telephony.AccessNetworkConstants;
  import android.telephony.SubscriptionManager;
  import android.telephony.ims.aidl.IImsCapabilityCallback;
  import android.telephony.ims.aidl.IImsRegistrationCallback;
  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.ITelephony;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.concurrent.Executor;
  
  /**
   * 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.
   *
   * @see #createForSubscriptionId(int)
   * @hide
   */
  @SystemApi
  public class ImsMmTelManager {
  
      private static final String TAG = "ImsMmTelManager";
  
      /**
       * @hide
       */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = "WIFI_MODE_", value = {
              WIFI_MODE_WIFI_ONLY,
              WIFI_MODE_CELLULAR_PREFERRED,
              WIFI_MODE_WIFI_PREFERRED
              })
      public @interface WiFiCallingMode {}
  
      /**
       * 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)
       */
      public static class RegistrationCallback {
  
          private static class RegistrationBinder extends IImsRegistrationCallback.Stub {
  
              // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
              // and WWAN are more accurate constants.
              private static final Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP =
                      new HashMap<Integer, Integer>() {{
                          // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
                          // case, since it is defined.
                          put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, -1);
                          put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
                                  AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
                          put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
                                  AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
                      }};
  
              private final RegistrationCallback mLocalCallback;
              private Executor mExecutor;
  
              RegistrationBinder(RegistrationCallback localCallback) {
                  mLocalCallback = localCallback;
              }
  
              @Override
              public void onRegistered(int imsRadioTech) {
                  if (mLocalCallback == null) return;
  
                  Binder.withCleanCallingIdentity(() -> mExecutor.execute(() ->
                          mLocalCallback.onRegistered(getAccessType(imsRadioTech))));
              }
  
              @Override
              public void onRegistering(int imsRadioTech) {
                  if (mLocalCallback == null) return;
  
                  Binder.withCleanCallingIdentity(() -> mExecutor.execute(() ->
                          mLocalCallback.onRegistering(getAccessType(imsRadioTech))));
              }
  
              @Override
              public void onDeregistered(ImsReasonInfo info) {
                  if (mLocalCallback == null) return;
  
                  Binder.withCleanCallingIdentity(() ->
                          mExecutor.execute(() -> mLocalCallback.onUnregistered(info)));
              }
  
              @Override
              public void onTechnologyChangeFailed(int imsRadioTech, ImsReasonInfo info) {
                  if (mLocalCallback == null) return;
  
                  Binder.withCleanCallingIdentity(() ->
                          mExecutor.execute(() -> mLocalCallback.onTechnologyChangeFailed(
                                  getAccessType(imsRadioTech), info)));
              }
  
              @Override
              public void onSubscriberAssociatedUriChanged(Uri[] uris) {
                  if (mLocalCallback == null) return;
  
                  Binder.withCleanCallingIdentity(() ->
                          mExecutor.execute(() ->
                                  mLocalCallback.onSubscriberAssociatedUriChanged(uris)));
              }
  
              private void setExecutor(Executor executor) {
                  mExecutor = executor;
              }
  
              private static int getAccessType(int regType) {
                  if (!IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regType)) {
                      Log.w("ImsMmTelManager", "RegistrationBinder - invalid regType returned: "
                              + regType);
                      return -1;
                  }
                  return IMS_REG_TO_ACCESS_TYPE_MAP.get(regType);
              }
          }
  
          private final RegistrationBinder mBinder = new RegistrationBinder(this);
  
          /**
           * Notifies the framework when the IMS Provider is registered to the IMS network.
           *
           * @param imsTransportType the radio access technology. Valid values are defined in
           * {@link android.telephony.AccessNetworkConstants.TransportType}.
           */
          public void onRegistered(int imsTransportType) {
          }
  
          /**
           * Notifies the framework when the IMS Provider is trying to register the IMS network.
           *
           * @param imsTransportType the radio access technology. Valid values are defined in
           * {@link android.telephony.AccessNetworkConstants.TransportType}.
           */
          public void onRegistering(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.
           */
          public void onUnregistered(@Nullable ImsReasonInfo info) {
          }
  
          /**
           * A failure has occurred when trying to handover registration to another technology type,
           * defined in {@link android.telephony.AccessNetworkConstants.TransportType}
           *
           * @param imsTransportType The
           *         {@link android.telephony.AccessNetworkConstants.TransportType}
           *         transport type that has failed to handover registration to.
           * @param info A {@link ImsReasonInfo} that identifies the reason for failure.
           */
          public void onTechnologyChangeFailed(int imsTransportType, @Nullable ImsReasonInfo info) {
          }
  
          /**
           * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when
           * it changes. Per RFC3455, an associated URI is a URI that the service provider has
           * allocated to a user for their own usage. A user's phone number is typically one of the
           * associated URIs.
           * @param uris new array of subscriber {@link Uri}s that are associated with this IMS
           *         subscription.
           * @hide
           */
          public void onSubscriberAssociatedUriChanged(@Nullable Uri[] uris) {
          }
  
          /**@hide*/
          public final IImsRegistrationCallback getBinder() {
              return mBinder;
          }
  
          /**@hide*/
          //Only exposed as public for compatibility with deprecated ImsManager APIs.
          public void setExecutor(Executor executor) {
              mBinder.setExecutor(executor);
          }
      }
  
      /**
       * Receives IMS capability status updates from the ImsService. This information is also
       * available via the {@link #isAvailable(int, int)} method below.
       *
       * @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;
  
                  Binder.withCleanCallingIdentity(() ->
                          mExecutor.execute(() -> mLocalCallback.onCapabilitiesStatusChanged(
                                  new MmTelFeature.MmTelCapabilities(config))));
              }
  
              @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.
           *
           * This information can also be queried using the {@link #isAvailable(int, int)} API.
           *
           * @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 int mSubId;
  
      /**
       * 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()
       * @throws IllegalArgumentException if the subscription is invalid.
       */
      public static @NonNull ImsMmTelManager createForSubscriptionId(int subId) {
          if (!SubscriptionManager.isValidSubscriptionId(subId)) {
              throw new IllegalArgumentException("Invalid subscription ID");
          }
  
          return new ImsMmTelManager(subId);
      }
  
      /**
       * Only visible for testing, use {@link #createForSubscriptionId(int)} instead.
       * @hide
       */
      @VisibleForTesting
      public ImsMmTelManager(int subId) {
          mSubId = subId;
      }
  
      /**
       * Registers a {@link RegistrationCallback} with the system, which will provide registration
       * updates for the subscription specified in {@link #createForSubscriptionId(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.
       */
      @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.");
          }
          if (!isImsAvailableOnDevice()) {
              throw new ImsException("IMS not available on device.",
                      ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
          }
          c.setExecutor(executor);
          try {
              getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder());
          } 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)
       * @throws IllegalArgumentException if the subscription ID associated with this callback is
       * invalid.
       */
      @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.");
          }
          try {
              getITelephony().unregisterImsRegistrationCallback(mSubId, c.getBinder());
          } catch (RemoteException e) {
              throw e.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Registers a {@link CapabilityCallback} with the system, which will provide MmTel service
       * availability updates for the subscription specified in
       * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)}
       * can also be used to query this information at any time.
       *
       * 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 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 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.
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_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.");
          }
          if (!isImsAvailableOnDevice()) {
              throw new ImsException("IMS not available on device.",
                      ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
          }
          c.setExecutor(executor);
          try {
              getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder());
          } 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.
       * @param c The MmTel {@link CapabilityCallback} to be removed.
       * @see #registerMmTelCapabilityCallback(Executor, CapabilityCallback)
       * @throws IllegalArgumentException if the subscription ID associated with this callback is
       * invalid.
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public void unregisterMmTelCapabilityCallback(@NonNull CapabilityCallback c) {
          if (c == null) {
              throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
          }
          try {
              getITelephony().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.
       *
       * 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 #setAdvancedCallingSettingEnabled(boolean)
       * @return true if the user's setting for advanced calling is enabled, false otherwise.
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public boolean isAdvancedCallingSettingEnabled() {
          try {
              return getITelephony().isAdvancedCallingSettingEnabled(mSubId);
          } 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()
       */
      @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
      public void setAdvancedCallingSettingEnabled(boolean isEnabled) {
          try {
              getITelephony().setAdvancedCallingSettingEnabled(mSubId, isEnabled);
              return;
          } 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, can be one of the following:
       *         {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE},
       *         {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
       * @param capability The IMS MmTel capability to query, can be one of the following:
       *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE},
       *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO,
       *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT},
       *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}
       * @return {@code true} if the MmTel IMS capability is capable for this subscription, false
       *         otherwise.
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public boolean isCapable(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
              @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) {
          try {
              return getITelephony().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, can be one of the following:
       *         {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE},
       *         {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
       * @param capability The IMS MmTel capability to query, can be one of the following:
       *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE},
       *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO,
       *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT},
       *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}
       * @return {@code true} if the MmTel IMS capability is available for this subscription, false
       *         otherwise.
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public boolean isAvailable(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
              @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) {
          try {
              return getITelephony().isAvailable(mSubId, capability, imsRegTech);
          } catch (RemoteException e) {
              throw e.rethrowAsRuntimeException();
          }
      }
  
      /**
       * The user's setting for whether or not they have enabled the "Video Calling" setting.
       * @return true if the user’s “Video Calling” setting is currently enabled.
       * @see #setVtSettingEnabled(boolean)
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public boolean isVtSettingEnabled() {
          try {
              return getITelephony().isVtSettingEnabled(mSubId);
          } catch (RemoteException e) {
              throw e.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Change the user's setting for Video Telephony and enable the Video Telephony capability.
       * @see #isVtSettingEnabled()
       */
      @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
      public void setVtSettingEnabled(boolean isEnabled) {
          try {
              getITelephony().setVtSettingEnabled(mSubId, isEnabled);
              return;
          } 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.
       * @see #setVoWiFiSettingEnabled(boolean)
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public boolean isVoWiFiSettingEnabled() {
          try {
              return getITelephony().isVoWiFiSettingEnabled(mSubId);
          } catch (RemoteException e) {
              throw e.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Sets the user's setting for whether or not Voice over WiFi is enabled.
       * @param isEnabled true if the user's setting for Voice over WiFi is enabled, false otherwise=
       * @see #isVoWiFiSettingEnabled()
       */
      @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
      public void setVoWiFiSettingEnabled(boolean isEnabled) {
          try {
              getITelephony().setVoWiFiSettingEnabled(mSubId, isEnabled);
              return;
          } catch (RemoteException e) {
              throw e.rethrowAsRuntimeException();
          }
      }
  
      /**
       * @return true if the user's setting for Voice over WiFi while roaming is enabled, false
       * if disabled.
       * @see #setVoWiFiRoamingSettingEnabled(boolean)
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public boolean isVoWiFiRoamingSettingEnabled() {
          try {
              return getITelephony().isVoWiFiRoamingSettingEnabled(mSubId);
          } 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.
       * @see #isVoWiFiRoamingSettingEnabled()
       */
      @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
      public void setVoWiFiRoamingSettingEnabled(boolean isEnabled) {
          try {
              getITelephony().setVoWiFiRoamingSettingEnabled(mSubId, isEnabled);
              return;
          } 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}
       * @see #setVoWiFiSettingEnabled(boolean)
       */
      @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
      public void setVoWiFiNonPersistent(boolean isCapable, int mode) {
          try {
              getITelephony().setVoWiFiNonPersistent(mSubId, isCapable, mode);
              return;
          } catch (RemoteException e) {
              throw e.rethrowAsRuntimeException();
          }
      }
  
      /**
       * @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}
       * @see #setVoWiFiSettingEnabled(boolean)
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public @WiFiCallingMode int getVoWiFiModeSetting() {
          try {
              return getITelephony().getVoWiFiModeSetting(mSubId);
          } 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}
       * @see #getVoWiFiModeSetting()
       */
      @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
      public void setVoWiFiModeSetting(@WiFiCallingMode int mode) {
          try {
              getITelephony().setVoWiFiModeSetting(mSubId, mode);
              return;
          } 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}
       * @see #setVoWiFiRoamingSettingEnabled(boolean)
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      public @WiFiCallingMode int getVoWiFiRoamingModeSetting() {
          try {
              return getITelephony().getVoWiFiRoamingModeSetting(mSubId);
          } 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}
       * @see #getVoWiFiRoamingModeSetting()
       */
      @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
      public void setVoWiFiRoamingModeSetting(@WiFiCallingMode int mode) {
          try {
              getITelephony().setVoWiFiRoamingModeSetting(mSubId, mode);
              return;
          } 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.
       * @param isEnabled if true RTT should be enabled during calls made on this subscription.
       */
      @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
      public void setRttCapabilitySetting(boolean isEnabled) {
          try {
              getITelephony().setRttCapabilitySetting(mSubId, isEnabled);
              return;
          } catch (RemoteException e) {
              throw e.rethrowAsRuntimeException();
          }
      }
  
      /**
       * @return true if TTY over VoLTE is supported
       * @see android.telecom.TelecomManager#getCurrentTtyMode
       * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
       */
      @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
      boolean isTtyOverVolteEnabled() {
          try {
              return getITelephony().isTtyOverVolteEnabled(mSubId);
          } catch (RemoteException e) {
              throw e.rethrowAsRuntimeException();
          }
      }
  
      private static boolean isImsAvailableOnDevice() {
          IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
          if (pm == null) {
              // For some reason package manger is not available.. This will fail internally anyways,
              // so do not throw error and allow.
              return true;
          }
          try {
              return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
          } catch (RemoteException e) {
              // For some reason package manger is not available.. This will fail internally anyways,
              // so do not throw error and allow.
          }
          return true;
      }
  
      private static ITelephony getITelephony() {
          ITelephony binder = ITelephony.Stub.asInterface(
                  ServiceManager.getService(Context.TELEPHONY_SERVICE));
          if (binder == null) {
              throw new RuntimeException("Could not find Telephony Service.");
          }
          return binder;
      }
  }
  