Home | History | Annotate | Download | only in ims
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.telephony.ims;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.content.pm.IPackageManager;
     24 import android.os.Handler;
     25 import android.os.HandlerThread;
     26 import android.os.IBinder;
     27 import android.os.IInterface;
     28 import android.os.RemoteException;
     29 import android.os.ServiceManager;
     30 import android.telephony.ims.ImsService;
     31 import android.telephony.ims.aidl.IImsConfig;
     32 import android.telephony.ims.aidl.IImsMmTelFeature;
     33 import android.telephony.ims.aidl.IImsRcsFeature;
     34 import android.telephony.ims.aidl.IImsRegistration;
     35 import android.telephony.ims.aidl.IImsServiceController;
     36 import android.telephony.ims.feature.ImsFeature;
     37 import android.telephony.ims.stub.ImsFeatureConfiguration;
     38 import android.util.Log;
     39 
     40 import com.android.ims.internal.IImsFeatureStatusCallback;
     41 import com.android.ims.internal.IImsServiceFeatureCallback;
     42 import com.android.internal.annotations.VisibleForTesting;
     43 import com.android.internal.telephony.ExponentialBackoff;
     44 
     45 import java.util.HashSet;
     46 import java.util.Iterator;
     47 import java.util.Set;
     48 import java.util.concurrent.ConcurrentHashMap;
     49 
     50 /**
     51  * Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the
     52  * ImsService will support.
     53  *
     54  * When the ImsService is first bound, {@link ImsService#createMmTelFeature(int)} and
     55  * {@link ImsService#createRcsFeature(int)} will be called
     56  * on each feature that the service supports. For each ImsFeature that is created,
     57  * {@link ImsServiceControllerCallbacks#imsServiceFeatureCreated} will be called to notify the
     58  * listener that the ImsService now supports that feature.
     59  *
     60  * When {@link #changeImsServiceFeatures} is called with a set of features that is different from
     61  * the original set, create and {@link IImsServiceController#removeImsFeature} will be called for
     62  * each feature that is created/removed.
     63  */
     64 public class ImsServiceController {
     65 
     66     class ImsDeathRecipient implements IBinder.DeathRecipient {
     67 
     68         private ComponentName mComponentName;
     69 
     70         ImsDeathRecipient(ComponentName name) {
     71             mComponentName = name;
     72         }
     73 
     74         @Override
     75         public void binderDied() {
     76             Log.e(LOG_TAG, "ImsService(" + mComponentName + ") died. Restarting...");
     77             synchronized (mLock) {
     78                 mIsBinding = false;
     79                 mIsBound = false;
     80             }
     81             notifyAllFeaturesRemoved();
     82             cleanUpService();
     83             startDelayedRebindToService();
     84         }
     85     }
     86 
     87     class ImsServiceConnection implements ServiceConnection {
     88 
     89         @Override
     90         public void onServiceConnected(ComponentName name, IBinder service) {
     91             mBackoff.stop();
     92             synchronized (mLock) {
     93                 mIsBound = true;
     94                 mIsBinding = false;
     95                 Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
     96                         + service);
     97                 if (service != null) {
     98                     mImsDeathRecipient = new ImsDeathRecipient(name);
     99                     try {
    100                         service.linkToDeath(mImsDeathRecipient, 0);
    101                         mImsServiceControllerBinder = service;
    102                         setServiceController(service);
    103                         notifyImsServiceReady();
    104                         // create all associated features in the ImsService
    105                         for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
    106                             addImsServiceFeature(i);
    107                         }
    108                     } catch (RemoteException e) {
    109                         mIsBound = false;
    110                         mIsBinding = false;
    111                         // Remote exception means that the binder already died.
    112                         if (mImsDeathRecipient != null) {
    113                             mImsDeathRecipient.binderDied();
    114                         }
    115                         Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
    116                                 + e.getMessage());
    117                     }
    118                 }
    119             }
    120         }
    121 
    122         @Override
    123         public void onServiceDisconnected(ComponentName name) {
    124             synchronized (mLock) {
    125                 mIsBinding = false;
    126             }
    127             cleanupConnection();
    128             Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting...");
    129             // Service disconnected, but we are still technically bound. Waiting for reconnect.
    130         }
    131 
    132         @Override
    133         public void onBindingDied(ComponentName name) {
    134             synchronized (mLock) {
    135                 mIsBinding = false;
    136                 mIsBound = false;
    137             }
    138             cleanupConnection();
    139             Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
    140             startDelayedRebindToService();
    141         }
    142 
    143         private void cleanupConnection() {
    144             if (isServiceControllerAvailable()) {
    145                 mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0);
    146             }
    147             notifyAllFeaturesRemoved();
    148             cleanUpService();
    149         }
    150     }
    151 
    152     private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
    153         @Override
    154         public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
    155             if (mCallbacks == null) {
    156                 return;
    157             }
    158             mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
    159         }
    160     };
    161 
    162     /**
    163      * Defines callbacks that are used by the ImsServiceController to notify when an ImsService
    164      * has created or removed a new feature as well as the associated ImsServiceController.
    165      */
    166     public interface ImsServiceControllerCallbacks {
    167         /**
    168          * Called by ImsServiceController when a new MMTEL or RCS feature has been created.
    169          */
    170         void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller);
    171         /**
    172          * Called by ImsServiceController when a new MMTEL or RCS feature has been removed.
    173          */
    174         void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller);
    175 
    176         /**
    177          * Called by the ImsServiceController when the ImsService has notified the framework that
    178          * its features have changed.
    179          */
    180         void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
    181                 ImsServiceController controller);
    182     }
    183 
    184     /**
    185      * Returns the currently defined rebind retry timeout. Used for testing.
    186      */
    187     @VisibleForTesting
    188     public interface RebindRetry {
    189         /**
    190          * Returns a long in ms indicating how long the ImsServiceController should wait before
    191          * rebinding for the first time.
    192          */
    193         long getStartDelay();
    194 
    195         /**
    196          * Returns a long in ms indicating the maximum time the ImsServiceController should wait
    197          * before rebinding.
    198          */
    199         long getMaximumDelay();
    200     }
    201 
    202     private static final String LOG_TAG = "ImsServiceController";
    203     private static final int REBIND_START_DELAY_MS = 2 * 1000; // 2 seconds
    204     private static final int REBIND_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute
    205     private final ComponentName mComponentName;
    206     private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
    207     private final IPackageManager mPackageManager;
    208     private ImsServiceControllerCallbacks mCallbacks;
    209     private ExponentialBackoff mBackoff;
    210 
    211     private boolean mIsBound = false;
    212     private boolean mIsBinding = false;
    213     // Set of a pair of slotId->feature
    214     private HashSet<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
    215     // Binder interfaces to the features set in mImsFeatures;
    216     private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
    217     private IImsServiceController mIImsServiceController;
    218     private IBinder mImsServiceControllerBinder;
    219     private ImsServiceConnection mImsServiceConnection;
    220     private ImsDeathRecipient mImsDeathRecipient;
    221     private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = ConcurrentHashMap.newKeySet();
    222     // Only added or removed, never accessed on purpose.
    223     private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
    224 
    225     protected final Object mLock = new Object();
    226     protected final Context mContext;
    227 
    228     private class ImsFeatureContainer {
    229         public int slotId;
    230         public int featureType;
    231         private IInterface mBinder;
    232 
    233         ImsFeatureContainer(int slotId, int featureType, IInterface binder) {
    234             this.slotId = slotId;
    235             this.featureType = featureType;
    236             this.mBinder = binder;
    237         }
    238 
    239         // Casts the IInterface into the binder class we are looking for.
    240         public <T extends IInterface> T resolve(Class<T> className) {
    241             return className.cast(mBinder);
    242         }
    243 
    244         @Override
    245         public boolean equals(Object o) {
    246             if (this == o) return true;
    247             if (o == null || getClass() != o.getClass()) return false;
    248 
    249             ImsFeatureContainer that = (ImsFeatureContainer) o;
    250 
    251             if (slotId != that.slotId) return false;
    252             if (featureType != that.featureType) return false;
    253             return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null;
    254         }
    255 
    256         @Override
    257         public int hashCode() {
    258             int result = slotId;
    259             result = 31 * result + featureType;
    260             result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0);
    261             return result;
    262         }
    263     }
    264 
    265     /**
    266      * Container class for the IImsFeatureStatusCallback callback implementation. This class is
    267      * never used directly, but we need to keep track of the IImsFeatureStatusCallback
    268      * implementations explicitly.
    269      */
    270     private class ImsFeatureStatusCallback {
    271         private int mSlotId;
    272         private int mFeatureType;
    273 
    274         private final IImsFeatureStatusCallback mCallback = new IImsFeatureStatusCallback.Stub() {
    275 
    276             @Override
    277             public void notifyImsFeatureStatus(int featureStatus) throws RemoteException {
    278                 Log.i(LOG_TAG, "notifyImsFeatureStatus: slot=" + mSlotId + ", feature="
    279                         + mFeatureType + ", status=" + featureStatus);
    280                 sendImsFeatureStatusChanged(mSlotId, mFeatureType, featureStatus);
    281             }
    282         };
    283 
    284         ImsFeatureStatusCallback(int slotId, int featureType) {
    285             mSlotId = slotId;
    286             mFeatureType = featureType;
    287         }
    288 
    289         public IImsFeatureStatusCallback getCallback() {
    290             return mCallback;
    291         }
    292     }
    293 
    294     // Retry the bind to the ImsService that has died after mRebindRetry timeout.
    295     private Runnable mRestartImsServiceRunnable = new Runnable() {
    296         @Override
    297         public void run() {
    298             synchronized (mLock) {
    299                 if (mIsBound) {
    300                     return;
    301                 }
    302                 bind(mImsFeatures);
    303             }
    304         }
    305     };
    306 
    307     private RebindRetry mRebindRetry = new RebindRetry() {
    308         @Override
    309         public long getStartDelay() {
    310             return REBIND_START_DELAY_MS;
    311         }
    312 
    313         @Override
    314         public long getMaximumDelay() {
    315             return REBIND_MAXIMUM_DELAY_MS;
    316         }
    317     };
    318 
    319     public ImsServiceController(Context context, ComponentName componentName,
    320             ImsServiceControllerCallbacks callbacks) {
    321         mContext = context;
    322         mComponentName = componentName;
    323         mCallbacks = callbacks;
    324         mHandlerThread.start();
    325         mBackoff = new ExponentialBackoff(
    326                 mRebindRetry.getStartDelay(),
    327                 mRebindRetry.getMaximumDelay(),
    328                 2, /* multiplier */
    329                 mHandlerThread.getLooper(),
    330                 mRestartImsServiceRunnable);
    331         mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
    332     }
    333 
    334     @VisibleForTesting
    335     // Creating a new HandlerThread and background handler for each test causes a segfault, so for
    336     // testing, use a handler supplied by the testing system.
    337     public ImsServiceController(Context context, ComponentName componentName,
    338             ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry) {
    339         mContext = context;
    340         mComponentName = componentName;
    341         mCallbacks = callbacks;
    342         mBackoff = new ExponentialBackoff(
    343                 rebindRetry.getStartDelay(),
    344                 rebindRetry.getMaximumDelay(),
    345                 2, /* multiplier */
    346                 handler,
    347                 mRestartImsServiceRunnable);
    348         mPackageManager = null;
    349     }
    350 
    351     /**
    352      * Sends request to bind to ImsService designated by the {@link ComponentName} with the feature
    353      * set imsFeatureSet.
    354      *
    355      * @param imsFeatureSet a Set of Pairs that designate the slotId->featureId that need to be
    356      *                      created once the service is bound.
    357      * @return {@link true} if the service is in the process of being bound, {@link false} if it
    358      * has failed.
    359      */
    360     public boolean bind(HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet) {
    361         synchronized (mLock) {
    362             if (!mIsBound && !mIsBinding) {
    363                 mIsBinding = true;
    364                 mImsFeatures = imsFeatureSet;
    365                 grantPermissionsToService();
    366                 Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent(
    367                         mComponentName);
    368                 mImsServiceConnection = new ImsServiceConnection();
    369                 int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
    370                         | Context.BIND_IMPORTANT;
    371                 Log.i(LOG_TAG, "Binding ImsService:" + mComponentName);
    372                 try {
    373                     boolean bindSucceeded = startBindToService(imsServiceIntent,
    374                             mImsServiceConnection, serviceFlags);
    375                     if (!bindSucceeded) {
    376                         mBackoff.notifyFailed();
    377                     }
    378                     return bindSucceeded;
    379                 } catch (Exception e) {
    380                     mBackoff.notifyFailed();
    381                     Log.e(LOG_TAG, "Error binding (" + mComponentName + ") with exception: "
    382                             + e.getMessage() + ", rebinding in " + mBackoff.getCurrentDelay()
    383                             + " ms");
    384                     return false;
    385                 }
    386             } else {
    387                 return false;
    388             }
    389         }
    390     }
    391 
    392     /**
    393      * Starts the bind to the ImsService. Overridden by subclasses that need to access the service
    394      * in a different fashion.
    395      */
    396     protected boolean startBindToService(Intent intent, ImsServiceConnection connection,
    397             int flags) {
    398         return mContext.bindService(intent, connection, flags);
    399     }
    400 
    401     /**
    402      * Calls {@link IImsServiceController#removeImsFeature} on all features that the
    403      * ImsService supports and then unbinds the service.
    404      */
    405     public void unbind() throws RemoteException {
    406         synchronized (mLock) {
    407             mBackoff.stop();
    408             if (mImsServiceConnection == null || mImsDeathRecipient == null) {
    409                 return;
    410             }
    411             // Clean up all features
    412             changeImsServiceFeatures(new HashSet<>());
    413             removeImsServiceFeatureCallbacks();
    414             mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0);
    415             Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
    416             mContext.unbindService(mImsServiceConnection);
    417             cleanUpService();
    418         }
    419     }
    420 
    421     /**
    422      * For every feature that is added, the service calls the associated create. For every
    423      * ImsFeature that is removed, {@link IImsServiceController#removeImsFeature} is called.
    424      */
    425     public void changeImsServiceFeatures(
    426             HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
    427             throws RemoteException {
    428         synchronized (mLock) {
    429             Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
    430                     + "ImsService: " + mComponentName);
    431             HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
    432                     new HashSet<>(mImsFeatures);
    433             // Set features first in case we lose binding and need to rebind later.
    434             mImsFeatures = newImsFeatures;
    435             if (mIsBound) {
    436                 // add features to service.
    437                 HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures =
    438                         new HashSet<>(mImsFeatures);
    439                 newFeatures.removeAll(oldImsFeatures);
    440                 for (ImsFeatureConfiguration.FeatureSlotPair i : newFeatures) {
    441                     addImsServiceFeature(i);
    442                 }
    443                 // remove old features
    444                 HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldFeatures =
    445                         new HashSet<>(oldImsFeatures);
    446                 oldFeatures.removeAll(mImsFeatures);
    447                 for (ImsFeatureConfiguration.FeatureSlotPair i : oldFeatures) {
    448                     removeImsServiceFeature(i);
    449                 }
    450             }
    451         }
    452     }
    453 
    454     @VisibleForTesting
    455     public IImsServiceController getImsServiceController() {
    456         return mIImsServiceController;
    457     }
    458 
    459     @VisibleForTesting
    460     public IBinder getImsServiceControllerBinder() {
    461         return mImsServiceControllerBinder;
    462     }
    463 
    464     @VisibleForTesting
    465     public long getRebindDelay() {
    466         return mBackoff.getCurrentDelay();
    467     }
    468 
    469     public ComponentName getComponentName() {
    470         return mComponentName;
    471     }
    472 
    473     /**
    474      * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle.
    475      */
    476     public void addImsServiceFeatureCallback(IImsServiceFeatureCallback callback) {
    477         mImsStatusCallbacks.add(callback);
    478         synchronized (mLock) {
    479             if (mImsFeatures == null || mImsFeatures.isEmpty()) {
    480                 return;
    481             }
    482             // notify the new status callback of the features that are available.
    483             try {
    484                 for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
    485                     callback.imsFeatureCreated(i.slotId, i.featureType);
    486                 }
    487             } catch (RemoteException e) {
    488                 Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback");
    489             }
    490         }
    491     }
    492 
    493     public void enableIms(int slotId) {
    494         try {
    495             synchronized (mLock) {
    496                 if (isServiceControllerAvailable()) {
    497                     mIImsServiceController.enableIms(slotId);
    498                 }
    499             }
    500         } catch (RemoteException e) {
    501             Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage());
    502         }
    503     }
    504 
    505     public void disableIms(int slotId) {
    506         try {
    507             synchronized (mLock) {
    508                 if (isServiceControllerAvailable()) {
    509                     mIImsServiceController.disableIms(slotId);
    510                 }
    511             }
    512         } catch (RemoteException e) {
    513             Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage());
    514         }
    515     }
    516 
    517     /**
    518      * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
    519      * Used for normal calling.
    520      */
    521     public IImsMmTelFeature getMmTelFeature(int slotId) {
    522         synchronized (mLock) {
    523             ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.FEATURE_MMTEL);
    524             if (f == null) {
    525                 Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId);
    526                 return null;
    527             }
    528             return f.resolve(IImsMmTelFeature.class);
    529         }
    530     }
    531 
    532     /**
    533      * Return the {@Link RcsFeature} binder on the slot associated with the slotId.
    534      */
    535     public IImsRcsFeature getRcsFeature(int slotId) {
    536         synchronized (mLock) {
    537             ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.FEATURE_RCS);
    538             if (f == null) {
    539                 Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId);
    540                 return null;
    541             }
    542             return f.resolve(IImsRcsFeature.class);
    543         }
    544     }
    545 
    546     /**
    547      * @return the IImsRegistration that corresponds to the slot id specified.
    548      */
    549     public IImsRegistration getRegistration(int slotId) throws RemoteException {
    550         synchronized (mLock) {
    551             return isServiceControllerAvailable()
    552                     ? mIImsServiceController.getRegistration(slotId) : null;
    553         }
    554     }
    555 
    556     /**
    557      * @return the IImsConfig that corresponds to the slot id specified.
    558      */
    559     public IImsConfig getConfig(int slotId) throws RemoteException {
    560         synchronized (mLock) {
    561             return isServiceControllerAvailable() ? mIImsServiceController.getConfig(slotId) : null;
    562         }
    563     }
    564 
    565     /**
    566      * notify the ImsService that the ImsService is ready for feature creation.
    567      */
    568     protected void notifyImsServiceReady() throws RemoteException {
    569         synchronized (mLock) {
    570             if (isServiceControllerAvailable()) {
    571                 Log.d(LOG_TAG, "notifyImsServiceReady");
    572                 mIImsServiceController.setListener(mFeatureChangedListener);
    573                 mIImsServiceController.notifyImsServiceReadyForFeatureCreation();
    574             }
    575         }
    576     }
    577 
    578     protected String getServiceInterface() {
    579         return ImsService.SERVICE_INTERFACE;
    580     }
    581 
    582     /**
    583      * Sets the IImsServiceController instance. Overridden by compat layers to set compatibility
    584      * versions of this service controller.
    585      */
    586     protected void setServiceController(IBinder serviceController) {
    587         mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController);
    588     }
    589 
    590     /**
    591      * @return true if the controller is currently bound.
    592      */
    593     public boolean isBound() {
    594         synchronized (mLock) {
    595             return mIsBound;
    596         }
    597     }
    598 
    599     /**
    600      * Check to see if the service controller is available, overridden for compat versions,
    601      * @return true if available, false otherwise;
    602      */
    603     protected boolean isServiceControllerAvailable() {
    604         return mIImsServiceController != null;
    605     }
    606 
    607     @VisibleForTesting
    608     public void removeImsServiceFeatureCallbacks() {
    609             mImsStatusCallbacks.clear();
    610     }
    611 
    612     // Only add a new rebind if there are no pending rebinds waiting.
    613     private void startDelayedRebindToService() {
    614         mBackoff.start();
    615     }
    616 
    617     // Grant runtime permissions to ImsService. PackageManager ensures that the ImsService is
    618     // system/signed before granting permissions.
    619     private void grantPermissionsToService() {
    620         Log.i(LOG_TAG, "Granting Runtime permissions to:" + getComponentName());
    621         String[] pkgToGrant = {mComponentName.getPackageName()};
    622         try {
    623             if (mPackageManager != null) {
    624                 mPackageManager.grantDefaultPermissionsToEnabledImsServices(pkgToGrant,
    625                         mContext.getUserId());
    626             }
    627         } catch (RemoteException e) {
    628             Log.w(LOG_TAG, "Unable to grant permissions, binder died.");
    629         }
    630     }
    631 
    632     private void sendImsFeatureCreatedCallback(int slot, int feature) {
    633         for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
    634                 i.hasNext(); ) {
    635             IImsServiceFeatureCallback callbacks = i.next();
    636             try {
    637                 callbacks.imsFeatureCreated(slot, feature);
    638             } catch (RemoteException e) {
    639                 // binder died, remove callback.
    640                 Log.w(LOG_TAG, "sendImsFeatureCreatedCallback: Binder died, removing "
    641                         + "callback. Exception:" + e.getMessage());
    642                 i.remove();
    643             }
    644         }
    645     }
    646 
    647     private void sendImsFeatureRemovedCallback(int slot, int feature) {
    648         for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
    649                 i.hasNext(); ) {
    650             IImsServiceFeatureCallback callbacks = i.next();
    651             try {
    652                 callbacks.imsFeatureRemoved(slot, feature);
    653             } catch (RemoteException e) {
    654                 // binder died, remove callback.
    655                 Log.w(LOG_TAG, "sendImsFeatureRemovedCallback: Binder died, removing "
    656                         + "callback. Exception:" + e.getMessage());
    657                 i.remove();
    658             }
    659         }
    660     }
    661 
    662     private void sendImsFeatureStatusChanged(int slot, int feature, int status) {
    663         for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
    664                 i.hasNext(); ) {
    665             IImsServiceFeatureCallback callbacks = i.next();
    666             try {
    667                 callbacks.imsStatusChanged(slot, feature, status);
    668             } catch (RemoteException e) {
    669                 // binder died, remove callback.
    670                 Log.w(LOG_TAG, "sendImsFeatureStatusChanged: Binder died, removing "
    671                         + "callback. Exception:" + e.getMessage());
    672                 i.remove();
    673             }
    674         }
    675     }
    676 
    677     // This method should only be called when synchronized on mLock
    678     private void addImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair)
    679             throws RemoteException {
    680         if (!isServiceControllerAvailable() || mCallbacks == null) {
    681             Log.w(LOG_TAG, "addImsServiceFeature called with null values.");
    682             return;
    683         }
    684         if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
    685             ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.slotId,
    686                     featurePair.featureType);
    687             mFeatureStatusCallbacks.add(c);
    688             IInterface f = createImsFeature(featurePair.slotId, featurePair.featureType,
    689                     c.getCallback());
    690             addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f);
    691             // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
    692             mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this);
    693         } else {
    694             // Don't update ImsService for emergency MMTEL feature.
    695             Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId);
    696         }
    697         // Send callback to ImsServiceProxy to change supported ImsFeatures including emergency
    698         // MMTEL state.
    699         sendImsFeatureCreatedCallback(featurePair.slotId, featurePair.featureType);
    700     }
    701 
    702     // This method should only be called when synchronized on mLock
    703     private void removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair)
    704             throws RemoteException {
    705         if (!isServiceControllerAvailable() || mCallbacks == null) {
    706             Log.w(LOG_TAG, "removeImsServiceFeature called with null values.");
    707             return;
    708         }
    709         if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
    710             ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c ->
    711                     c.mSlotId == featurePair.slotId && c.mFeatureType == featurePair.featureType)
    712                     .findFirst().orElse(null);
    713             // Remove status callbacks from list.
    714             if (callbackToRemove != null) {
    715                 mFeatureStatusCallbacks.remove(callbackToRemove);
    716             }
    717             removeImsFeature(featurePair.slotId, featurePair.featureType,
    718                     (callbackToRemove != null ? callbackToRemove.getCallback() : null));
    719             removeImsFeatureBinder(featurePair.slotId, featurePair.featureType);
    720             // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
    721             mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this);
    722         } else {
    723             // Don't update ImsService for emergency MMTEL feature.
    724             Log.i(LOG_TAG, "doesn't support emergency calling on slot " + featurePair.slotId);
    725         }
    726         // Send callback to ImsServiceProxy to change supported ImsFeatures
    727         // Ensure that ImsServiceProxy callback occurs after ImsResolver callback. If an
    728         // ImsManager requests the ImsService while it is being removed in ImsResolver, this
    729         // callback will clean it up after.
    730         sendImsFeatureRemovedCallback(featurePair.slotId, featurePair.featureType);
    731     }
    732 
    733     // This method should only be called when already synchronized on mLock.
    734     // overridden by compat layer to create features
    735     protected IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
    736             throws RemoteException {
    737         switch (featureType) {
    738             case ImsFeature.FEATURE_MMTEL: {
    739                 return mIImsServiceController.createMmTelFeature(slotId, c);
    740             }
    741             case ImsFeature.FEATURE_RCS: {
    742                 return mIImsServiceController.createRcsFeature(slotId, c);
    743             }
    744             default:
    745                 return null;
    746         }
    747     }
    748 
    749     // overridden by compat layer to remove features
    750     protected void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
    751             throws RemoteException {
    752         mIImsServiceController.removeImsFeature(slotId, featureType, c);
    753     }
    754 
    755     // This method should only be called when synchronized on mLock
    756     private void addImsFeatureBinder(int slotId, int featureType, IInterface b) {
    757         mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b));
    758     }
    759 
    760     // This method should only be called when synchronized on mLock
    761     private void removeImsFeatureBinder(int slotId, int featureType) {
    762         ImsFeatureContainer container = mImsFeatureBinders.stream()
    763                 .filter(f-> (f.slotId == slotId && f.featureType == featureType))
    764                 .findFirst().orElse(null);
    765         if (container != null) {
    766             mImsFeatureBinders.remove(container);
    767         }
    768     }
    769 
    770     private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) {
    771         return mImsFeatureBinders.stream()
    772                 .filter(f-> (f.slotId == slotId && f.featureType == featureType))
    773                 .findFirst().orElse(null);
    774     }
    775 
    776     private void notifyAllFeaturesRemoved() {
    777         if (mCallbacks == null) {
    778             Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks.");
    779             return;
    780         }
    781         synchronized (mLock) {
    782             for (ImsFeatureConfiguration.FeatureSlotPair feature : mImsFeatures) {
    783                 if (feature.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
    784                     // don't update ImsServiceController for emergency MMTEL.
    785                     mCallbacks.imsServiceFeatureRemoved(feature.slotId, feature.featureType, this);
    786                 }
    787                 sendImsFeatureRemovedCallback(feature.slotId, feature.featureType);
    788             }
    789         }
    790     }
    791 
    792     private void cleanUpService() {
    793         synchronized (mLock) {
    794             mImsDeathRecipient = null;
    795             mImsServiceConnection = null;
    796             mImsServiceControllerBinder = null;
    797             setServiceController(null);
    798         }
    799     }
    800 }
    801