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.internal.telephony.ims;
  
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.ServiceConnection;
  import android.content.pm.IPackageManager;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.os.IBinder;
  import android.os.IInterface;
  import android.os.RemoteException;
  import android.os.ServiceManager;
  import android.telephony.ims.ImsService;
  import android.telephony.ims.aidl.IImsConfig;
  import android.telephony.ims.aidl.IImsMmTelFeature;
  import android.telephony.ims.aidl.IImsRcsFeature;
  import android.telephony.ims.aidl.IImsRegistration;
  import android.telephony.ims.aidl.IImsServiceController;
  import android.telephony.ims.feature.ImsFeature;
  import android.telephony.ims.stub.ImsFeatureConfiguration;
  import android.util.Log;
  
  import com.android.ims.internal.IImsFeatureStatusCallback;
  import com.android.ims.internal.IImsServiceFeatureCallback;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.telephony.ExponentialBackoff;
  
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  
  /**
   * Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the
   * ImsService will support.
   *
   * When the ImsService is first bound, {@link ImsService#createMmTelFeature(int)} and
   * {@link ImsService#createRcsFeature(int)} will be called
   * on each feature that the service supports. For each ImsFeature that is created,
   * {@link ImsServiceControllerCallbacks#imsServiceFeatureCreated} will be called to notify the
   * listener that the ImsService now supports that feature.
   *
   * When {@link #changeImsServiceFeatures} is called with a set of features that is different from
   * the original set, create*Feature and {@link IImsServiceController#removeImsFeature} will be
   * called for each feature that is created/removed.
   */
  public class ImsServiceController {
  
      class ImsServiceConnection implements ServiceConnection {
  
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              mBackoff.stop();
              synchronized (mLock) {
                  mIsBound = true;
                  mIsBinding = false;
                  Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
                          + service);
                  if (service != null) {
                      try {
                          setServiceController(service);
                          notifyImsServiceReady();
                          // create all associated features in the ImsService
                          for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
                              addImsServiceFeature(i);
                          }
                      } catch (RemoteException e) {
                          mIsBound = false;
                          mIsBinding = false;
                          // Remote exception means that the binder already died.
                          cleanupConnection();
                          startDelayedRebindToService();
                          Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
                                  + e.getMessage());
                      }
                  }
              }
          }
  
          @Override
          public void onServiceDisconnected(ComponentName name) {
              synchronized (mLock) {
                  mIsBinding = false;
              }
              cleanupConnection();
              Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting...");
              // Service disconnected, but we are still technically bound. Waiting for reconnect.
          }
  
          @Override
          public void onBindingDied(ComponentName name) {
              synchronized (mLock) {
                  mIsBinding = false;
                  mIsBound = false;
              }
              cleanupConnection();
              Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
              startDelayedRebindToService();
          }
  
          private void cleanupConnection() {
              cleanupAllFeatures();
              cleanUpService();
          }
      }
  
      private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
          @Override
          public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
              if (mCallbacks == null) {
                  return;
              }
              mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
          }
      };
  
      /**
       * Defines callbacks that are used by the ImsServiceController to notify when an ImsService
       * has created or removed a new feature as well as the associated ImsServiceController.
       */
      public interface ImsServiceControllerCallbacks {
          /**
           * Called by ImsServiceController when a new MMTEL or RCS feature has been created.
           */
          void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller);
          /**
           * Called by ImsServiceController when a new MMTEL or RCS feature has been removed.
           */
          void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller);
  
          /**
           * Called by the ImsServiceController when the ImsService has notified the framework that
           * its features have changed.
           */
          void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
                  ImsServiceController controller);
      }
  
      /**
       * Returns the currently defined rebind retry timeout. Used for testing.
       */
      @VisibleForTesting
      public interface RebindRetry {
          /**
           * Returns a long in ms indicating how long the ImsServiceController should wait before
           * rebinding for the first time.
           */
          long getStartDelay();
  
          /**
           * Returns a long in ms indicating the maximum time the ImsServiceController should wait
           * before rebinding.
           */
          long getMaximumDelay();
      }
  
      private static final String LOG_TAG = "ImsServiceController";
      private static final int REBIND_START_DELAY_MS = 2 * 1000; // 2 seconds
      private static final int REBIND_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute
      private final ComponentName mComponentName;
      private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
      private final IPackageManager mPackageManager;
      private ImsServiceControllerCallbacks mCallbacks;
      private ExponentialBackoff mBackoff;
  
      private boolean mIsBound = false;
      private boolean mIsBinding = false;
      // Set of a pair of slotId->feature
      private HashSet<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
      // Binder interfaces to the features set in mImsFeatures;
      private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
      private IImsServiceController mIImsServiceController;
      private ImsServiceConnection mImsServiceConnection;
      private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = ConcurrentHashMap.newKeySet();
      // Only added or removed, never accessed on purpose.
      private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
  
      protected final Object mLock = new Object();
      protected final Context mContext;
  
      private class ImsFeatureContainer {
          public int slotId;
          public int featureType;
          private IInterface mBinder;
  
          ImsFeatureContainer(int slotId, int featureType, IInterface binder) {
              this.slotId = slotId;
              this.featureType = featureType;
              this.mBinder = binder;
          }
  
          // Casts the IInterface into the binder class we are looking for.
          public <T extends IInterface> T resolve(Class<T> className) {
              return className.cast(mBinder);
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (o == null || getClass() != o.getClass()) return false;
  
              ImsFeatureContainer that = (ImsFeatureContainer) o;
  
              if (slotId != that.slotId) return false;
              if (featureType != that.featureType) return false;
              return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null;
          }
  
          @Override
          public int hashCode() {
              int result = slotId;
              result = 31 * result + featureType;
              result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0);
              return result;
          }
      }
  
      /**
       * Container class for the IImsFeatureStatusCallback callback implementation. This class is
       * never used directly, but we need to keep track of the IImsFeatureStatusCallback
       * implementations explicitly.
       */
      private class ImsFeatureStatusCallback {
          private int mSlotId;
          private int mFeatureType;
  
          private final IImsFeatureStatusCallback mCallback = new IImsFeatureStatusCallback.Stub() {
  
              @Override
              public void notifyImsFeatureStatus(int featureStatus) throws RemoteException {
                  Log.i(LOG_TAG, "notifyImsFeatureStatus: slot=" + mSlotId + ", feature="
                          + mFeatureType + ", status=" + featureStatus);
                  sendImsFeatureStatusChanged(mSlotId, mFeatureType, featureStatus);
              }
          };
  
          ImsFeatureStatusCallback(int slotId, int featureType) {
              mSlotId = slotId;
              mFeatureType = featureType;
          }
  
          public IImsFeatureStatusCallback getCallback() {
              return mCallback;
          }
      }
  
      // Retry the bind to the ImsService that has died after mRebindRetry timeout.
      private Runnable mRestartImsServiceRunnable = new Runnable() {
          @Override
          public void run() {
              synchronized (mLock) {
                  if (mIsBound) {
                      return;
                  }
                  bind(mImsFeatures);
              }
          }
      };
  
      private RebindRetry mRebindRetry = new RebindRetry() {
          @Override
          public long getStartDelay() {
              return REBIND_START_DELAY_MS;
          }
  
          @Override
          public long getMaximumDelay() {
              return REBIND_MAXIMUM_DELAY_MS;
          }
      };
  
      public ImsServiceController(Context context, ComponentName componentName,
              ImsServiceControllerCallbacks callbacks) {
          mContext = context;
          mComponentName = componentName;
          mCallbacks = callbacks;
          mHandlerThread.start();
          mBackoff = new ExponentialBackoff(
                  mRebindRetry.getStartDelay(),
                  mRebindRetry.getMaximumDelay(),
                  2, /* multiplier */
                  mHandlerThread.getLooper(),
                  mRestartImsServiceRunnable);
          mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
      }
  
      @VisibleForTesting
      // Creating a new HandlerThread and background handler for each test causes a segfault, so for
      // testing, use a handler supplied by the testing system.
      public ImsServiceController(Context context, ComponentName componentName,
              ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry) {
          mContext = context;
          mComponentName = componentName;
          mCallbacks = callbacks;
          mBackoff = new ExponentialBackoff(
                  rebindRetry.getStartDelay(),
                  rebindRetry.getMaximumDelay(),
                  2, /* multiplier */
                  handler,
                  mRestartImsServiceRunnable);
          mPackageManager = null;
      }
  
      /**
       * Sends request to bind to ImsService designated by the {@link ComponentName} with the feature
       * set imsFeatureSet.
       *
       * @param imsFeatureSet a Set of Pairs that designate the slotId->featureId that need to be
       *                      created once the service is bound.
       * @return {@link true} if the service is in the process of being bound, {@link false} if it
       * has failed.
       */
      public boolean bind(HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet) {
          synchronized (mLock) {
              if (!mIsBound && !mIsBinding) {
                  mIsBinding = true;
                  mImsFeatures = imsFeatureSet;
                  grantPermissionsToService();
                  Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent(
                          mComponentName);
                  mImsServiceConnection = new ImsServiceConnection();
                  int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                          | Context.BIND_IMPORTANT;
                  Log.i(LOG_TAG, "Binding ImsService:" + mComponentName);
                  try {
                      boolean bindSucceeded = startBindToService(imsServiceIntent,
                              mImsServiceConnection, serviceFlags);
                      if (!bindSucceeded) {
                          mIsBinding = false;
                          mBackoff.notifyFailed();
                      }
                      return bindSucceeded;
                  } catch (Exception e) {
                      mBackoff.notifyFailed();
                      Log.e(LOG_TAG, "Error binding (" + mComponentName + ") with exception: "
                              + e.getMessage() + ", rebinding in " + mBackoff.getCurrentDelay()
                              + " ms");
                      return false;
                  }
              } else {
                  return false;
              }
          }
      }
  
      /**
       * Starts the bind to the ImsService. Overridden by subclasses that need to access the service
       * in a different fashion.
       */
      protected boolean startBindToService(Intent intent, ImsServiceConnection connection,
              int flags) {
          return mContext.bindService(intent, connection, flags);
      }
  
      /**
       * Calls {@link IImsServiceController#removeImsFeature} on all features that the
       * ImsService supports and then unbinds the service.
       */
      public void unbind() throws RemoteException {
          synchronized (mLock) {
              mBackoff.stop();
              if (mImsServiceConnection == null) {
                  return;
              }
              // Clean up all features
              changeImsServiceFeatures(new HashSet<>());
              removeImsServiceFeatureCallbacks();
              Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
              mContext.unbindService(mImsServiceConnection);
              cleanUpService();
          }
      }
  
      /**
       * For every feature that is added, the service calls the associated create. For every
       * ImsFeature that is removed, {@link IImsServiceController#removeImsFeature} is called.
       */
      public void changeImsServiceFeatures(
              HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
              throws RemoteException {
          synchronized (mLock) {
              Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
                      + "ImsService: " + mComponentName);
              HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
                      new HashSet<>(mImsFeatures);
              // Set features first in case we lose binding and need to rebind later.
              mImsFeatures = newImsFeatures;
              if (mIsBound) {
                  // add features to service.
                  HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures =
                          new HashSet<>(mImsFeatures);
                  newFeatures.removeAll(oldImsFeatures);
                  for (ImsFeatureConfiguration.FeatureSlotPair i : newFeatures) {
                      addImsServiceFeature(i);
                  }
                  // remove old features
                  HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldFeatures =
                          new HashSet<>(oldImsFeatures);
                  oldFeatures.removeAll(mImsFeatures);
                  for (ImsFeatureConfiguration.FeatureSlotPair i : oldFeatures) {
                      removeImsServiceFeature(i);
                  }
              }
          }
      }
  
      @VisibleForTesting
      public IImsServiceController getImsServiceController() {
          return mIImsServiceController;
      }
  
      @VisibleForTesting
      public long getRebindDelay() {
          return mBackoff.getCurrentDelay();
      }
  
      @VisibleForTesting
      public void stopBackoffTimerForTesting() {
          mBackoff.stop();
      }
  
      public ComponentName getComponentName() {
          return mComponentName;
      }
  
      /**
       * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle.
       */
      public void addImsServiceFeatureCallback(IImsServiceFeatureCallback callback) {
          mImsStatusCallbacks.add(callback);
          synchronized (mLock) {
              if (mImsFeatures == null || mImsFeatures.isEmpty()) {
                  return;
              }
              // notify the new status callback of the features that are available.
              try {
                  for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
                      callback.imsFeatureCreated(i.slotId, i.featureType);
                  }
              } catch (RemoteException e) {
                  Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback");
              }
          }
      }
  
      public void enableIms(int slotId) {
          try {
              synchronized (mLock) {
                  if (isServiceControllerAvailable()) {
                      mIImsServiceController.enableIms(slotId);
                  }
              }
          } catch (RemoteException e) {
              Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage());
          }
      }
  
      public void disableIms(int slotId) {
          try {
              synchronized (mLock) {
                  if (isServiceControllerAvailable()) {
                      mIImsServiceController.disableIms(slotId);
                  }
              }
          } catch (RemoteException e) {
              Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage());
          }
      }
  
      /**
       * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
       * Used for normal calling.
       */
      public IImsMmTelFeature getMmTelFeature(int slotId) {
          synchronized (mLock) {
              ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.FEATURE_MMTEL);
              if (f == null) {
                  Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId);
                  return null;
              }
              return f.resolve(IImsMmTelFeature.class);
          }
      }
  
      /**
       * Return the {@Link RcsFeature} binder on the slot associated with the slotId.
       */
      public IImsRcsFeature getRcsFeature(int slotId) {
          synchronized (mLock) {
              ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.FEATURE_RCS);
              if (f == null) {
                  Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId);
                  return null;
              }
              return f.resolve(IImsRcsFeature.class);
          }
      }
  
      /**
       * @return the IImsRegistration that corresponds to the slot id specified.
       */
      public IImsRegistration getRegistration(int slotId) throws RemoteException {
          synchronized (mLock) {
              return isServiceControllerAvailable()
                      ? mIImsServiceController.getRegistration(slotId) : null;
          }
      }
  
      /**
       * @return the IImsConfig that corresponds to the slot id specified.
       */
      public IImsConfig getConfig(int slotId) throws RemoteException {
          synchronized (mLock) {
              return isServiceControllerAvailable() ? mIImsServiceController.getConfig(slotId) : null;
          }
      }
  
      /**
       * notify the ImsService that the ImsService is ready for feature creation.
       */
      protected void notifyImsServiceReady() throws RemoteException {
          synchronized (mLock) {
              if (isServiceControllerAvailable()) {
                  Log.d(LOG_TAG, "notifyImsServiceReady");
                  mIImsServiceController.setListener(mFeatureChangedListener);
                  mIImsServiceController.notifyImsServiceReadyForFeatureCreation();
              }
          }
      }
  
      protected String getServiceInterface() {
          return ImsService.SERVICE_INTERFACE;
      }
  
      /**
       * Sets the IImsServiceController instance. Overridden by compat layers to set compatibility
       * versions of this service controller.
       */
      protected void setServiceController(IBinder serviceController) {
          mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController);
      }
  
      /**
       * @return true if the controller is currently bound.
       */
      public boolean isBound() {
          synchronized (mLock) {
              return mIsBound;
          }
      }
  
      /**
       * Check to see if the service controller is available, overridden for compat versions,
       * @return true if available, false otherwise;
       */
      protected boolean isServiceControllerAvailable() {
          return mIImsServiceController != null;
      }
  
      @VisibleForTesting
      public void removeImsServiceFeatureCallbacks() {
              mImsStatusCallbacks.clear();
      }
  
      // Only add a new rebind if there are no pending rebinds waiting.
      private void startDelayedRebindToService() {
          mBackoff.start();
      }
  
      // Grant runtime permissions to ImsService. PackageManager ensures that the ImsService is
      // system/signed before granting permissions.
      private void grantPermissionsToService() {
          Log.i(LOG_TAG, "Granting Runtime permissions to:" + getComponentName());
          String[] pkgToGrant = {mComponentName.getPackageName()};
          try {
              if (mPackageManager != null) {
                  mPackageManager.grantDefaultPermissionsToEnabledImsServices(pkgToGrant,
                          mContext.getUserId());
              }
          } catch (RemoteException e) {
              Log.w(LOG_TAG, "Unable to grant permissions, binder died.");
          }
      }
  
      private void sendImsFeatureCreatedCallback(int slot, int feature) {
          for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
                  i.hasNext(); ) {
              IImsServiceFeatureCallback callbacks = i.next();
              try {
                  callbacks.imsFeatureCreated(slot, feature);
              } catch (RemoteException e) {
                  // binder died, remove callback.
                  Log.w(LOG_TAG, "sendImsFeatureCreatedCallback: Binder died, removing "
                          + "callback. Exception:" + e.getMessage());
                  i.remove();
              }
          }
      }
  
      private void sendImsFeatureRemovedCallback(int slot, int feature) {
          for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
                  i.hasNext(); ) {
              IImsServiceFeatureCallback callbacks = i.next();
              try {
                  callbacks.imsFeatureRemoved(slot, feature);
              } catch (RemoteException e) {
                  // binder died, remove callback.
                  Log.w(LOG_TAG, "sendImsFeatureRemovedCallback: Binder died, removing "
                          + "callback. Exception:" + e.getMessage());
                  i.remove();
              }
          }
      }
  
      private void sendImsFeatureStatusChanged(int slot, int feature, int status) {
          for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
                  i.hasNext(); ) {
              IImsServiceFeatureCallback callbacks = i.next();
              try {
                  callbacks.imsStatusChanged(slot, feature, status);
              } catch (RemoteException e) {
                  // binder died, remove callback.
                  Log.w(LOG_TAG, "sendImsFeatureStatusChanged: Binder died, removing "
                          + "callback. Exception:" + e.getMessage());
                  i.remove();
              }
          }
      }
  
      // This method should only be called when synchronized on mLock
      private void addImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair)
              throws RemoteException {
          if (!isServiceControllerAvailable() || mCallbacks == null) {
              Log.w(LOG_TAG, "addImsServiceFeature called with null values.");
              return;
          }
          if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
              ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.slotId,
                      featurePair.featureType);
              mFeatureStatusCallbacks.add(c);
              IInterface f = createImsFeature(featurePair.slotId, featurePair.featureType,
                      c.getCallback());
              addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f);
              // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
              mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this);
          } else {
              // Don't update ImsService for emergency MMTEL feature.
              Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId);
          }
          // Send callback to ImsServiceProxy to change supported ImsFeatures including emergency
          // MMTEL state.
          sendImsFeatureCreatedCallback(featurePair.slotId, featurePair.featureType);
      }
  
      // This method should only be called when synchronized on mLock
      private void removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair) {
          if (!isServiceControllerAvailable() || mCallbacks == null) {
              Log.w(LOG_TAG, "removeImsServiceFeature called with null values.");
              return;
          }
          if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
              ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c ->
                      c.mSlotId == featurePair.slotId && c.mFeatureType == featurePair.featureType)
                      .findFirst().orElse(null);
              // Remove status callbacks from list.
              if (callbackToRemove != null) {
                  mFeatureStatusCallbacks.remove(callbackToRemove);
              }
              removeImsFeatureBinder(featurePair.slotId, featurePair.featureType);
              // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
              mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this);
              try {
                  removeImsFeature(featurePair.slotId, featurePair.featureType,
                          (callbackToRemove != null ? callbackToRemove.getCallback() : null));
              } catch (RemoteException e) {
                  // The connection to this ImsService doesn't exist. This may happen if the service
                  // has died and we are removing features.
                  Log.i(LOG_TAG, "Couldn't remove feature {" + featurePair.featureType
                          + "}, connection is down: " + e.getMessage());
              }
          } else {
              // Don't update ImsService for emergency MMTEL feature.
              Log.i(LOG_TAG, "doesn't support emergency calling on slot " + featurePair.slotId);
          }
          // Send callback to ImsServiceProxy to change supported ImsFeatures
          // Ensure that ImsServiceProxy callback occurs after ImsResolver callback. If an
          // ImsManager requests the ImsService while it is being removed in ImsResolver, this
          // callback will clean it up after.
          sendImsFeatureRemovedCallback(featurePair.slotId, featurePair.featureType);
      }
  
      // This method should only be called when already synchronized on mLock.
      // overridden by compat layer to create features
      protected IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
              throws RemoteException {
          switch (featureType) {
              case ImsFeature.FEATURE_MMTEL: {
                  return mIImsServiceController.createMmTelFeature(slotId, c);
              }
              case ImsFeature.FEATURE_RCS: {
                  return mIImsServiceController.createRcsFeature(slotId, c);
              }
              default:
                  return null;
          }
      }
  
      // overridden by compat layer to remove features
      protected void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
              throws RemoteException {
          mIImsServiceController.removeImsFeature(slotId, featureType, c);
      }
  
      // This method should only be called when synchronized on mLock
      private void addImsFeatureBinder(int slotId, int featureType, IInterface b) {
          mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b));
      }
  
      // This method should only be called when synchronized on mLock
      private void removeImsFeatureBinder(int slotId, int featureType) {
          ImsFeatureContainer container = mImsFeatureBinders.stream()
                  .filter(f-> (f.slotId == slotId && f.featureType == featureType))
                  .findFirst().orElse(null);
          if (container != null) {
              mImsFeatureBinders.remove(container);
          }
      }
  
      private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) {
          return mImsFeatureBinders.stream()
                  .filter(f-> (f.slotId == slotId && f.featureType == featureType))
                  .findFirst().orElse(null);
      }
  
      private void cleanupAllFeatures() {
          synchronized (mLock) {
              // Remove all features and clean up all associated Binders.
              for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
                  removeImsServiceFeature(i);
              }
              // remove all MmTelFeatureConnection callbacks, since we have already sent removed
              // callback.
              removeImsServiceFeatureCallbacks();
          }
      }
  
      private void cleanUpService() {
          synchronized (mLock) {
              mImsServiceConnection = null;
              setServiceController(null);
          }
      }
  }
  