content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  
  package com.android.ims;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.Context;
  import android.os.Handler;
  import android.os.HandlerExecutor;
  import android.os.IBinder;
  import android.os.IInterface;
  import android.os.Looper;
  import android.os.Message;
  import android.os.RemoteCallbackList;
  import android.os.RemoteException;
  import android.telephony.Rlog;
  import android.telephony.SubscriptionInfo;
  import android.telephony.SubscriptionManager;
  import android.telephony.TelephonyManager;
  import android.telephony.ims.ImsCallProfile;
  import android.telephony.ims.aidl.IImsCapabilityCallback;
  import android.telephony.ims.aidl.IImsConfig;
  import android.telephony.ims.aidl.IImsConfigCallback;
  import android.telephony.ims.aidl.IImsMmTelFeature;
  import android.telephony.ims.aidl.IImsRegistration;
  import android.telephony.ims.aidl.IImsRegistrationCallback;
  import android.telephony.ims.aidl.IImsSmsListener;
  import android.telephony.ims.feature.CapabilityChangeRequest;
  import android.telephony.ims.feature.ImsFeature;
  import android.telephony.ims.feature.MmTelFeature;
  import android.telephony.ims.stub.ImsRegistrationImplBase;
  import android.telephony.ims.stub.ImsSmsImplBase;
  import android.util.ArraySet;
  import android.util.Log;
  import android.util.SparseArray;
  
  import com.android.ims.internal.IImsCallSession;
  import com.android.ims.internal.IImsEcbm;
  import com.android.ims.internal.IImsMultiEndpoint;
  import com.android.ims.internal.IImsServiceFeatureCallback;
  import com.android.ims.internal.IImsUt;
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.Set;
  import java.util.concurrent.Executor;
  import java.util.stream.Collectors;
  
  /**
   * A container of the IImsServiceController binder, which implements all of the ImsFeatures that
   * the platform currently supports: MMTel and RCS.
   * @hide
   */
  
  public class MmTelFeatureConnection {
      protected static final String TAG = "MmTelFeatureConnection";
  
      // Manages callbacks to the associated MmTelFeature in mMmTelFeatureConnection.
      @VisibleForTesting
      public static abstract class CallbackAdapterManager<T extends IInterface> {
          private static final String TAG = "CallbackAdapterManager";
  
          private final Context mContext;
          private final Object mLock;
          // Map of sub id -> List<callbacks> for sub id linked callbacks.
          private final SparseArray<Set<T>> mCallbackSubscriptionMap = new SparseArray<>();
          // List of all active callbacks to ImsService
          private final RemoteCallbackList<T> mRemoteCallbacks = new RemoteCallbackList<>();
          @VisibleForTesting
          public SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
  
          public CallbackAdapterManager(Context context, Object lock) {
              mContext = context;
              mLock = lock;
              if (Looper.myLooper() == null) {
                  Looper.prepare();
              }
              // Must be created after Looper.prepare() is called, or else we will get an exception.
              mSubChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener() {
                  @Override
                  public void onSubscriptionsChanged() {
                      SubscriptionManager manager = mContext.getSystemService(
                              SubscriptionManager.class);
                      if (manager == null) {
                          Log.w(TAG, "onSubscriptionsChanged: could not find SubscriptionManager.");
                          return;
                      }
                      List<SubscriptionInfo> subInfos = manager.getActiveSubscriptionInfoList(false);
                      if (subInfos == null) {
                          subInfos = Collections.emptyList();
                      }
                      Set<Integer> newSubIds = subInfos.stream()
                              .map(SubscriptionInfo::getSubscriptionId)
                              .collect(Collectors.toSet());
                      synchronized (mLock) {
                          Set<Integer> storedSubIds = new ArraySet<>(mCallbackSubscriptionMap.size());
                          for (int keyIndex = 0; keyIndex < mCallbackSubscriptionMap.size();
                                  keyIndex++) {
                              storedSubIds.add(mCallbackSubscriptionMap.keyAt(keyIndex));
                          }
                          // Get the set of sub ids that are in storedSubIds that are not in newSubIds.
                          // This is the set of sub ids that need to be removed.
                          storedSubIds.removeAll(newSubIds);
                          for (Integer subId : storedSubIds) {
                              removeCallbacksForSubscription(subId);
                          }
                      }
                  }
              };
  
          }
  
          // Add a callback to the MmTelFeature associated with this manager (independent of the)
          // current subscription.
          public final void addCallback(T localCallback) {
              synchronized (mLock) {
                  // Skip registering to callback subscription map here, because we are registering
                  // for the slot, independent of subscription (deprecated behavior).
                  // Throws a IllegalStateException if this registration fails.
                  registerCallback(localCallback);
                  Log.i(TAG, "Local callback added: " + localCallback);
                  mRemoteCallbacks.register(localCallback);
              }
          }
  
          // Add a callback to be associated with a subscription. If that subscription is removed,
          // remove the callback and notify the callback that the subscription has been removed.
          public final void addCallbackForSubscription(T localCallback, int subId) {
              if (!SubscriptionManager.isValidSubscriptionId(subId)) {
                  return;
              }
              synchronized (mLock) {
                  addCallback(localCallback);
                  linkCallbackToSubscription(localCallback, subId);
              }
          }
  
          // Removes a callback associated with the MmTelFeature.
          public final void removeCallback(T localCallback) {
              Log.i(TAG, "Local callback removed: " + localCallback);
              synchronized (mLock) {
                  if (mRemoteCallbacks.unregister(localCallback)) {
                      // Will only occur if we have record of this callback in mRemoteCallbacks.
                      unregisterCallback(localCallback);
                  }
              }
          }
  
          // Remove an existing callback that has been linked to a subscription.
          public final void removeCallbackForSubscription(T localCallback, int subId) {
              if (!SubscriptionManager.isValidSubscriptionId(subId)) {
                  return;
              }
              synchronized (mLock) {
                  removeCallback(localCallback);
                  unlinkCallbackFromSubscription(localCallback, subId);
              }
          }
  
          // Links a callback to be tracked by a subscription. If it goes away, emove.
          private void linkCallbackToSubscription(T callback, int subId) {
              synchronized (mLock) {
                  if (mCallbackSubscriptionMap.size() == 0) {
                      // we are about to add the first entry to the map, register for subscriptions
                      //changed listener.
                      registerForSubscriptionsChanged();
                  }
                  Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
                  if (callbacksPerSub == null) {
                      // the callback list has not been created yet for this subscription.
                      callbacksPerSub = new ArraySet<>();
                      mCallbackSubscriptionMap.put(subId, callbacksPerSub);
                  }
                  callbacksPerSub.add(callback);
              }
          }
  
          // Unlink the callback from the associated subscription.
          private void unlinkCallbackFromSubscription(T callback, int subId) {
              synchronized (mLock) {
                  Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
                  if (callbacksPerSub != null) {
                      callbacksPerSub.remove(callback);
                      if (callbacksPerSub.isEmpty()) {
                          mCallbackSubscriptionMap.remove(subId);
                      }
                  }
                  if (mCallbackSubscriptionMap.size() == 0) {
                      unregisterForSubscriptionsChanged();
                  }
              }
          }
  
          // Removes all of the callbacks that have been registered to the subscription specified.
          // This happens when Telephony sends an indication that the subscriptions have changed.
          private void removeCallbacksForSubscription(int subId) {
              if (!SubscriptionManager.isValidSubscriptionId(subId)) {
                  return;
              }
              synchronized (mLock) {
                  Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
                  if (callbacksPerSub == null) {
                      // no callbacks registered for this subscription.
                      return;
                  }
                  // clear all registered callbacks in the subscription map for this subscription.
                  mCallbackSubscriptionMap.remove(subId);
                  for (T callback : callbacksPerSub) {
                      removeCallback(callback);
                  }
                  // If there are no more callbacks being tracked, remove subscriptions changed
                  // listener.
                  if (mCallbackSubscriptionMap.size() == 0) {
                      unregisterForSubscriptionsChanged();
                  }
              }
          }
  
          // Clear the Subscription -> Callback map because the ImsService connection is no longer
          // current.
          private void clearCallbacksForAllSubscriptions() {
              synchronized (mLock) {
                  List<Integer> keys = new ArrayList<>();
                  for (int keyIndex = 0; keyIndex < mCallbackSubscriptionMap.size(); keyIndex++) {
                      keys.add(mCallbackSubscriptionMap.keyAt(keyIndex));
                  }
                  keys.forEach(this::removeCallbacksForSubscription);
              }
          }
  
          private void registerForSubscriptionsChanged() {
              SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
              if (manager != null) {
                  manager.addOnSubscriptionsChangedListener(mSubChangedListener);
              } else {
                  Log.w(TAG, "registerForSubscriptionsChanged: could not find SubscriptionManager.");
              }
          }
  
          private void unregisterForSubscriptionsChanged() {
              SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
              if (manager != null) {
              manager.removeOnSubscriptionsChangedListener(mSubChangedListener);
              } else {
                  Log.w(TAG, "unregisterForSubscriptionsChanged: could not find"
                          + " SubscriptionManager.");
              }
          }
  
          // The ImsService these callbacks are registered to has become unavailable or crashed, or
          // the ImsResolver has switched to a new ImsService. In these cases, clean up all existing
          // callbacks.
          public final void close() {
              synchronized (mLock) {
                  final int lastCallbackIndex = mRemoteCallbacks.getRegisteredCallbackCount() - 1;
                  for(int ii = lastCallbackIndex; ii >= 0; ii --) {
                      T callbackItem = mRemoteCallbacks.getRegisteredCallbackItem(ii);
                      unregisterCallback(callbackItem);
                      mRemoteCallbacks.unregister(callbackItem);
                  }
                  clearCallbacksForAllSubscriptions();
                  Log.i(TAG, "Closing connection and clearing callbacks");
              }
          }
  
          // A callback has been registered. Register that callback with the MmTelFeature.
          public abstract void registerCallback(T localCallback);
  
          // A callback has been removed, unregister that callback with the MmTelFeature.
          public abstract void unregisterCallback(T localCallback);
      }
  
      private class ImsRegistrationCallbackAdapter extends
              CallbackAdapterManager<IImsRegistrationCallback> {
  
          public ImsRegistrationCallbackAdapter(Context context, Object lock) {
              super(context, lock);
          }
  
          @Override
          public void registerCallback(IImsRegistrationCallback localCallback) {
              IImsRegistration imsRegistration = getRegistration();
              if (imsRegistration != null) {
                  try {
                      imsRegistration.addRegistrationCallback(localCallback);
                  } catch (RemoteException e) {
                      throw new IllegalStateException("ImsRegistrationCallbackAdapter: MmTelFeature"
                              + " binder is dead.");
                  }
              } else {
                  Log.e(TAG, "ImsRegistrationCallbackAdapter: ImsRegistration is null");
                  throw new IllegalStateException("ImsRegistrationCallbackAdapter: MmTelFeature is"
                          + "not available!");
              }
          }
  
          @Override
          public void unregisterCallback(IImsRegistrationCallback localCallback) {
              IImsRegistration imsRegistration = getRegistration();
              if (imsRegistration != null) {
                  try {
                      imsRegistration.removeRegistrationCallback(localCallback);
                  } catch (RemoteException e) {
                      Log.w(TAG, "ImsRegistrationCallbackAdapter - unregisterCallback: couldn't"
                              + " remove registration callback");
                  }
              } else {
                  Log.e(TAG, "ImsRegistrationCallbackAdapter: ImsRegistration is null");
              }
          }
      }
  
      private class CapabilityCallbackManager extends CallbackAdapterManager<IImsCapabilityCallback> {
  
          public CapabilityCallbackManager(Context context, Object lock) {
              super(context, lock);
          }
  
          @Override
          public void registerCallback(IImsCapabilityCallback localCallback) {
              IImsMmTelFeature binder;
              synchronized (mLock) {
                  try {
                      checkServiceIsReady();
                      binder = getServiceInterface(mBinder);
                  } catch (RemoteException e) {
                      throw new IllegalStateException("CapabilityCallbackManager - MmTelFeature"
                              + " binder is dead.");
                  }
              }
              if (binder != null) {
                  try {
                  binder.addCapabilityCallback(localCallback);
                  } catch (RemoteException e) {
                      throw new IllegalStateException(" CapabilityCallbackManager - MmTelFeature"
                              + " binder is null.");
                  }
              } else {
                  Log.w(TAG, "CapabilityCallbackManager, register: Couldn't get binder");
                  throw new IllegalStateException("CapabilityCallbackManager: MmTelFeature is"
                          + " not available!");
              }
          }
  
          @Override
          public void unregisterCallback(IImsCapabilityCallback localCallback) {
              IImsMmTelFeature binder;
              synchronized (mLock) {
                  try {
                      checkServiceIsReady();
                      binder = getServiceInterface(mBinder);
                  } catch (RemoteException e) {
                      // binder is null
                      Log.w(TAG, "CapabilityCallbackManager, unregister: couldn't get binder.");
                      return;
                  }
              }
              if (binder != null) {
                  try {
                      binder.removeCapabilityCallback(localCallback);
                  } catch (RemoteException e) {
                      Log.w(TAG, "CapabilityCallbackManager, unregister: Binder is dead.");
                  }
              } else {
                  Log.w(TAG, "CapabilityCallbackManager, unregister: binder is null.");
              }
          }
      }
  
      private class ProvisioningCallbackManager extends CallbackAdapterManager<IImsConfigCallback> {
          public ProvisioningCallbackManager (Context context, Object lock) {
              super(context, lock);
          }
  
          @Override
          public void registerCallback(IImsConfigCallback localCallback) {
              IImsConfig binder = getConfigInterface();
              if (binder == null) {
                  // Config interface is not currently available.
                  Log.w(TAG, "ProvisioningCallbackManager - couldn't register, binder is null.");
                  throw new IllegalStateException("ImsConfig is not available!");
              }
              try {
                  binder.addImsConfigCallback(localCallback);
              }catch (RemoteException e) {
                  throw new IllegalStateException("ImsService is not available!");
              }
          }
  
          @Override
          public void unregisterCallback(IImsConfigCallback localCallback) {
              IImsConfig binder = getConfigInterface();
              if (binder == null) {
                  Log.w(TAG, "ProvisioningCallbackManager - couldn't unregister, binder is null.");
                  return;
              }
              try {
                  binder.removeImsConfigCallback(localCallback);
              } catch (RemoteException e) {
                  Log.w(TAG, "ProvisioningCallbackManager - couldn't unregister, binder is dead.");
              }
          }
      }
  
      protected final int mSlotId;
      protected IBinder mBinder;
      private Context mContext;
      private Executor mExecutor;
  
      // We are assuming the feature is available when started.
      private volatile boolean mIsAvailable = true;
      // ImsFeature Status from the ImsService. Cached.
      private Integer mFeatureStateCached = null;
      private IFeatureUpdate mStatusCallback;
      private final Object mLock = new Object();
      // Updated by IImsServiceFeatureCallback when FEATURE_EMERGENCY_MMTEL is sent.
      private boolean mSupportsEmergencyCalling = false;
      private static boolean sImsSupportedOnDevice = true;
  
      // Cache the Registration and Config interfaces as long as the MmTel feature is connected. If
      // it becomes disconnected, invalidate.
      private IImsRegistration mRegistrationBinder;
      private IImsConfig mConfigBinder;
  
      private final IBinder.DeathRecipient mDeathRecipient = () -> {
          Log.w(TAG, "DeathRecipient triggered, binder died.");
          if (mContext != null && Looper.getMainLooper() != null) {
              // Move this signal to the main thread, notifying ImsManager of the Binder
              // death on another thread may lead to deadlocks.
              mContext.getMainExecutor().execute(this::onRemovedOrDied);
              return;
          }
          // No choice - execute on the current Binder thread.
          onRemovedOrDied();
      };
  
      private final ImsRegistrationCallbackAdapter mRegistrationCallbackManager;
      private final CapabilityCallbackManager mCapabilityCallbackManager;
      private final ProvisioningCallbackManager mProvisioningCallbackManager;
  
      public static @NonNull MmTelFeatureConnection create(Context context , int slotId) {
          MmTelFeatureConnection serviceProxy = new MmTelFeatureConnection(context, slotId);
          if (!ImsManager.isImsSupportedOnDevice(context)) {
              // Return empty service proxy in the case that IMS is not supported.
              sImsSupportedOnDevice = false;
              return serviceProxy;
          }
  
          TelephonyManager tm  = getTelephonyManager(context);
          if (tm == null) {
              Rlog.w(TAG, "create: TelephonyManager is null!");
              // Binder can be unset in this case because it will be torn down/recreated as part of
              // a retry mechanism until the serviceProxy binder is set successfully.
              return serviceProxy;
          }
  
          IImsMmTelFeature binder = tm.getImsMmTelFeatureAndListen(slotId,
                  serviceProxy.getListener());
          if (binder != null) {
              serviceProxy.setBinder(binder.asBinder());
              // Trigger the cache to be updated for feature status.
              serviceProxy.getFeatureState();
          } else {
              Rlog.w(TAG, "create: binder is null! Slot Id: " + slotId);
          }
          return serviceProxy;
      }
  
      public static TelephonyManager getTelephonyManager(Context context) {
          return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
      }
  
      public interface IFeatureUpdate {
          /**
           * Called when the ImsFeature has changed its state. Use
           * {@link ImsFeature#getFeatureState()} to get the new state.
           */
          void notifyStateChanged();
  
          /**
           * Called when the ImsFeature has become unavailable due to the binder switching or app
           * crashing. A new ImsServiceProxy should be requested for that feature.
           */
          void notifyUnavailable();
      }
  
      private final IImsServiceFeatureCallback mListenerBinder =
              new IImsServiceFeatureCallback.Stub() {
  
          @Override
          public void imsFeatureCreated(int slotId, int feature) {
                  mExecutor.execute(() -> {
                  // The feature has been enabled. This happens when the feature is first created and
                  // may happen when the feature is re-enabled.
                  synchronized (mLock) {
                      if(mSlotId != slotId) {
                          return;
                      }
                      switch (feature) {
                          case ImsFeature.FEATURE_MMTEL: {
                              if (!mIsAvailable) {
                                  Log.i(TAG, "MmTel enabled on slotId: " + slotId);
                                  mIsAvailable = true;
                              }
                              break;
                          }
                          case ImsFeature.FEATURE_EMERGENCY_MMTEL: {
                              mSupportsEmergencyCalling = true;
                              Log.i(TAG, "Emergency calling enabled on slotId: " + slotId);
                              break;
                          }
                      }
                  }
              });
          }
  
          @Override
          public void imsFeatureRemoved(int slotId, int feature) {
              mExecutor.execute(() -> {
                  synchronized (mLock) {
                      if (mSlotId != slotId) {
                          return;
                      }
                      switch (feature) {
                          case ImsFeature.FEATURE_MMTEL: {
                              Log.i(TAG, "MmTel removed on slotId: " + slotId);
                              onRemovedOrDied();
                              break;
                          }
                          case ImsFeature.FEATURE_EMERGENCY_MMTEL: {
                              mSupportsEmergencyCalling = false;
                              Log.i(TAG, "Emergency calling disabled on slotId: " + slotId);
                              break;
                          }
                      }
                  }
              });
          }
  
          @Override
          public void imsStatusChanged(int slotId, int feature, int status) {
              mExecutor.execute(() -> {
                  synchronized (mLock) {
                      Log.i(TAG, "imsStatusChanged: slot: " + slotId + " feature: " + feature +
                              " status: " + status);
                      if (mSlotId == slotId && feature == ImsFeature.FEATURE_MMTEL) {
                          mFeatureStateCached = status;
                          if (mStatusCallback != null) {
                              mStatusCallback.notifyStateChanged();
                          }
                      }
                  }
              });
          }
      };
  
      public MmTelFeatureConnection(Context context, int slotId) {
          mSlotId = slotId;
          mContext = context;
          // Callbacks should be scheduled on the main thread.
          if (context.getMainLooper() != null) {
              mExecutor = context.getMainExecutor();
          } else {
              // Fallback to the current thread.
              if (Looper.myLooper() == null) {
                  Looper.prepare();
              }
              mExecutor = new HandlerExecutor(new Handler(Looper.myLooper()));
          }
          mRegistrationCallbackManager = new ImsRegistrationCallbackAdapter(context, mLock);
          mCapabilityCallbackManager = new CapabilityCallbackManager(context, mLock);
          mProvisioningCallbackManager = new ProvisioningCallbackManager(context, mLock);
      }
  
      /**
       * Called when the MmTelFeature has either been removed by Telephony or crashed.
       */
      private void onRemovedOrDied() {
          synchronized (mLock) {
              mRegistrationCallbackManager.close();
              mCapabilityCallbackManager.close();
              mProvisioningCallbackManager.close();
              if (mIsAvailable) {
                  mIsAvailable = false;
                  // invalidate caches.
                  mRegistrationBinder = null;
                  mConfigBinder = null;
                  if (mBinder != null) {
                      mBinder.unlinkToDeath(mDeathRecipient, 0);
                  }
                  if (mStatusCallback != null) {
                      mStatusCallback.notifyUnavailable();
                  }
              }
          }
      }
  
      private @Nullable IImsRegistration getRegistration() {
          synchronized (mLock) {
              // null if cache is invalid;
              if (mRegistrationBinder != null) {
                  return mRegistrationBinder;
              }
          }
          TelephonyManager tm = getTelephonyManager(mContext);
          // We don't want to synchronize on a binder call to another process.
          IImsRegistration regBinder = tm != null
                  ? tm.getImsRegistration(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
          synchronized (mLock) {
              // mRegistrationBinder may have changed while we tried to get the registration
              // interface.
              if (mRegistrationBinder == null) {
                  mRegistrationBinder = regBinder;
              }
          }
          return mRegistrationBinder;
      }
  
      private IImsConfig getConfig() {
          synchronized (mLock) {
              // null if cache is invalid;
              if (mConfigBinder != null) {
                  return mConfigBinder;
              }
          }
          TelephonyManager tm = getTelephonyManager(mContext);
          IImsConfig configBinder = tm != null
                  ? tm.getImsConfig(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
          synchronized (mLock) {
              // mConfigBinder may have changed while we tried to get the config interface.
              if (mConfigBinder == null) {
                  mConfigBinder = configBinder;
              }
          }
          return mConfigBinder;
      }
  
      public boolean isEmergencyMmTelAvailable() {
          return mSupportsEmergencyCalling;
      }
  
      public IImsServiceFeatureCallback getListener() {
          return mListenerBinder;
      }
  
      public void setBinder(IBinder binder) {
          synchronized (mLock) {
              mBinder = binder;
              try {
                  if (mBinder != null) {
                      mBinder.linkToDeath(mDeathRecipient, 0);
                  }
              } catch (RemoteException e) {
                  // No need to do anything if the binder is already dead.
              }
          }
      }
  
      /**
       * Opens the connection to the {@link MmTelFeature} and establishes a listener back to the
       * framework. Calling this method multiple times will reset the listener attached to the
       * {@link MmTelFeature}.
       * @param listener A {@link MmTelFeature.Listener} that will be used by the {@link MmTelFeature}
       * to notify the framework of updates.
       */
      public void openConnection(MmTelFeature.Listener listener) throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).setListener(listener);
          }
      }
  
      public void closeConnection() {
          mRegistrationCallbackManager.close();
          mCapabilityCallbackManager.close();
          mProvisioningCallbackManager.close();
          try {
              synchronized (mLock) {
                  if (isBinderAlive()) {
                      getServiceInterface(mBinder).setListener(null);
                  }
              }
          } catch (RemoteException e) {
              Log.w(TAG, "closeConnection: couldn't remove listener!");
          }
      }
  
      public void addRegistrationCallback(IImsRegistrationCallback callback) {
          mRegistrationCallbackManager.addCallback(callback);
      }
  
      public void addRegistrationCallbackForSubscription(IImsRegistrationCallback callback,
              int subId) {
          mRegistrationCallbackManager.addCallbackForSubscription(callback , subId);
      }
  
      public void removeRegistrationCallback(IImsRegistrationCallback callback) {
          mRegistrationCallbackManager.removeCallback(callback);
      }
  
      public void removeRegistrationCallbackForSubscription(IImsRegistrationCallback callback,
              int subId) {
          mRegistrationCallbackManager.removeCallbackForSubscription(callback, subId);
      }
  
      public void addCapabilityCallback(IImsCapabilityCallback callback) {
          mCapabilityCallbackManager.addCallback(callback);
      }
  
      public void addCapabilityCallbackForSubscription(IImsCapabilityCallback callback,
              int subId) {
          mCapabilityCallbackManager.addCallbackForSubscription(callback, subId);
      }
  
      public void removeCapabilityCallback(IImsCapabilityCallback callback) {
          mCapabilityCallbackManager.removeCallback(callback);
      }
  
      public void removeCapabilityCallbackForSubscription(IImsCapabilityCallback callback,
              int subId) {
          mCapabilityCallbackManager.removeCallbackForSubscription(callback , subId);
      }
  
      public void addProvisioningCallbackForSubscription(IImsConfigCallback callback,
              int subId) {
          mProvisioningCallbackManager.addCallbackForSubscription(callback, subId);
      }
  
      public void removeProvisioningCallbackForSubscription(IImsConfigCallback callback,
              int subId) {
          mProvisioningCallbackManager.removeCallbackForSubscription(callback , subId);
      }
  
      public void changeEnabledCapabilities(CapabilityChangeRequest request,
              IImsCapabilityCallback callback) throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).changeCapabilitiesConfiguration(request, callback);
          }
      }
  
      public void queryEnabledCapabilities(int capability, int radioTech,
              IImsCapabilityCallback callback) throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).queryCapabilityConfiguration(capability, radioTech,
                      callback);
          }
      }
  
      public MmTelFeature.MmTelCapabilities queryCapabilityStatus() throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              return new MmTelFeature.MmTelCapabilities(
                      getServiceInterface(mBinder).queryCapabilityStatus());
          }
      }
  
      public ImsCallProfile createCallProfile(int callServiceType, int callType)
              throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              return getServiceInterface(mBinder).createCallProfile(callServiceType, callType);
          }
      }
  
      public IImsCallSession createCallSession(ImsCallProfile profile)
              throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              return getServiceInterface(mBinder).createCallSession(profile);
          }
      }
  
      public IImsUt getUtInterface() throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              return getServiceInterface(mBinder).getUtInterface();
          }
      }
  
      public IImsConfig getConfigInterface() {
          return getConfig();
      }
  
      public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech()
              throws RemoteException {
          IImsRegistration registration = getRegistration();
          if (registration != null) {
                  return registration.getRegistrationTechnology();
          } else {
              return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
          }
      }
  
      public IImsEcbm getEcbmInterface() throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              return getServiceInterface(mBinder).getEcbmInterface();
          }
      }
  
      public void setUiTTYMode(int uiTtyMode, Message onComplete)
              throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).setUiTtyMode(uiTtyMode, onComplete);
          }
      }
  
      public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              return getServiceInterface(mBinder).getMultiEndpointInterface();
          }
      }
  
      public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
              byte[] pdu) throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).sendSms(token, messageRef, format, smsc, isRetry,
                      pdu);
          }
      }
  
      public void acknowledgeSms(int token, int messageRef,
              @ImsSmsImplBase.SendStatusResult int result) throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).acknowledgeSms(token, messageRef, result);
          }
      }
  
      public void acknowledgeSmsReport(int token, int messageRef,
              @ImsSmsImplBase.StatusReportResult int result) throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).acknowledgeSmsReport(token, messageRef, result);
          }
      }
  
      public String getSmsFormat() throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              return getServiceInterface(mBinder).getSmsFormat();
          }
      }
  
      public void onSmsReady() throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).onSmsReady();
          }
      }
  
      public void setSmsListener(IImsSmsListener listener) throws RemoteException {
          synchronized (mLock) {
              checkServiceIsReady();
              getServiceInterface(mBinder).setSmsListener(listener);
          }
      }
  
      public @MmTelFeature.ProcessCallResult int shouldProcessCall(boolean isEmergency,
              String[] numbers) throws RemoteException {
          if (isEmergency && !isEmergencyMmTelAvailable()) {
              // Don't query the ImsService if emergency calling is not available on the ImsService.
              Log.i(TAG, "MmTel does not support emergency over IMS, fallback to CS.");
              return MmTelFeature.PROCESS_CALL_CSFB;
          }
          synchronized (mLock) {
              checkServiceIsReady();
              return getServiceInterface(mBinder).shouldProcessCall(numbers);
          }
      }
  
      /**
       * @return an integer describing the current Feature Status, defined in
       * {@link ImsFeature.ImsState}.
       */
      public int getFeatureState() {
          synchronized (mLock) {
              if (isBinderAlive() && mFeatureStateCached != null) {
                  return mFeatureStateCached;
              }
          }
          // Don't synchronize on Binder call.
          Integer status = retrieveFeatureState();
          synchronized (mLock) {
              if (status == null) {
                  return ImsFeature.STATE_UNAVAILABLE;
              }
              // Cache only non-null value for feature status.
              mFeatureStateCached = status;
          }
          Log.i(TAG, "getFeatureState - returning " + status);
          return status;
      }
  
      /**
       * Internal method used to retrieve the feature status from the corresponding ImsService.
       */
      private Integer retrieveFeatureState() {
          if (mBinder != null) {
              try {
                  return getServiceInterface(mBinder).getFeatureState();
              } catch (RemoteException e) {
                  // Status check failed, don't update cache
              }
          }
          return null;
      }
  
      /**
       * @param c Callback that will fire when the feature status has changed.
       */
      public void setStatusCallback(IFeatureUpdate c) {
          mStatusCallback = c;
      }
  
      /**
       * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
       * method returns false, it doesn't mean that the Binder connection is not available (use
       * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
       * at this time.
       *
       * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
       * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}.
       */
      public boolean isBinderReady() {
          return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY;
      }
  
      /**
       * @return false if the binder connection is no longer alive.
       */
      public boolean isBinderAlive() {
          return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
      }
  
      private void checkServiceIsReady() throws RemoteException {
          if (!sImsSupportedOnDevice) {
              throw new RemoteException("IMS is not supported on this device.");
          }
          if (!isBinderReady()) {
              throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
          }
      }
  
      private IImsMmTelFeature getServiceInterface(IBinder b) {
          return IImsMmTelFeature.Stub.asInterface(b);
      }
  }
  