[RCS] Initialize RcsFeatureManager and RcsFeatureConnection instance

Create RcsFeatureManager and RcsFeatureConnection to handle the RCS UCE operation.

Bug: 139260798
Test: Manual
Change-Id: I28b304080a6ef216763512fc24e6f9944e207dde
Merged-In: Ibb1ad42154c76bc3716047e6959ee9e1fd3ec86d
diff --git a/src/java/com/android/ims/FeatureConnection.java b/src/java/com/android/ims/FeatureConnection.java
new file mode 100644
index 0000000..5bec9df
--- /dev/null
+++ b/src/java/com/android/ims/FeatureConnection.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 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.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.aidl.IImsMmTelFeature;
+import android.telephony.ims.aidl.IImsRcsFeature;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.ims.MmTelFeatureConnection.IFeatureUpdate;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Base class of MmTelFeatureConnection and RcsFeatureConnection.
+ */
+public abstract class FeatureConnection {
+    protected static final String TAG = "FeatureConnection";
+
+    protected static boolean sImsSupportedOnDevice = true;
+
+    protected final int mSlotId;
+    protected final int mFeatureType;
+    protected Context mContext;
+    protected IBinder mBinder;
+    @VisibleForTesting
+    public Executor mExecutor;
+
+    // We are assuming the feature is available when started.
+    protected volatile boolean mIsAvailable = true;
+    // ImsFeature Status from the ImsService. Cached.
+    protected Integer mFeatureStateCached = null;
+    protected IFeatureUpdate mStatusCallback;
+    protected final Object mLock = new Object();
+
+    public FeatureConnection(Context context, int slotId, int featureType) {
+        mSlotId = slotId;
+        mContext = context;
+        mFeatureType = featureType;
+
+        // 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()));
+        }
+    }
+
+    protected TelephonyManager getTelephonyManager() {
+        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    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.
+            }
+        }
+    }
+
+    protected 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();
+    };
+
+    /**
+     * Called when the MmTelFeature/RcsFeature has either been removed by Telephony or crashed.
+     */
+    protected void onRemovedOrDied() {
+        synchronized (mLock) {
+            if (mIsAvailable) {
+                mIsAvailable = false;
+                if (mBinder != null) {
+                    mBinder.unlinkToDeath(mDeathRecipient, 0);
+                }
+                if (mStatusCallback != null) {
+                    mStatusCallback.notifyUnavailable();
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public IImsServiceFeatureCallback getListener() {
+        return mListenerBinder;
+    }
+
+    private final IImsServiceFeatureCallback mListenerBinder =
+        new IImsServiceFeatureCallback.Stub() {
+            @Override
+            public void imsFeatureCreated(int slotId, int feature) {
+                mExecutor.execute(() -> {
+                    handleImsFeatureCreatedCallback(slotId, feature);
+                });
+            }
+            @Override
+            public void imsFeatureRemoved(int slotId, int feature) {
+                mExecutor.execute(() -> {
+                    handleImsFeatureRemovedCallback(slotId, feature);
+                });
+            }
+            @Override
+            public void imsStatusChanged(int slotId, int feature, int status) {
+                mExecutor.execute(() -> {
+                    handleImsStatusChangedCallback(slotId, feature, status);
+                });
+            }
+        };
+
+    protected abstract void handleImsFeatureCreatedCallback(int slotId, int feature);
+    protected abstract void handleImsFeatureRemovedCallback(int slotId, int feature);
+    protected abstract void handleImsStatusChangedCallback(int slotId, int feature, int status);
+
+    protected 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.");
+        }
+    }
+
+    /**
+     * @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();
+    }
+
+    /**
+     * @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 state = retrieveFeatureState();
+        synchronized (mLock) {
+            if (state == null) {
+                return ImsFeature.STATE_UNAVAILABLE;
+            }
+            // Cache only non-null value for feature status.
+            mFeatureStateCached = state;
+        }
+        Log.i(TAG, "getFeatureState - returning " + ImsFeature.STATE_LOG_MAP.get(state));
+        return state;
+    }
+
+    /**
+     * Internal method used to retrieve the feature status from the corresponding ImsService.
+     */
+    protected abstract Integer retrieveFeatureState();
+}
diff --git a/src/java/com/android/ims/MmTelFeatureConnection.java b/src/java/com/android/ims/MmTelFeatureConnection.java
index ea7d1e1..76d12c0 100644
--- a/src/java/com/android/ims/MmTelFeatureConnection.java
+++ b/src/java/com/android/ims/MmTelFeatureConnection.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Looper;
@@ -58,16 +57,14 @@
 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
+ * the platform currently supports: MMTel
  */
 
-public class MmTelFeatureConnection {
+public class MmTelFeatureConnection extends FeatureConnection {
     protected static final String TAG = "MmTelFeatureConnection";
 
     // Manages callbacks to the associated MmTelFeature in mMmTelFeatureConnection.
@@ -417,38 +414,14 @@
         }
     }
 
-    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;
@@ -461,7 +434,7 @@
             return serviceProxy;
         }
 
-        TelephonyManager tm  = getTelephonyManager(context);
+        TelephonyManager tm = serviceProxy.getTelephonyManager();
         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
@@ -481,10 +454,6 @@
         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
@@ -499,99 +468,16 @@
         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: "
-                            + ImsFeature.FEATURE_LOG_MAP.get(feature) +
-                            " status: " + ImsFeature.STATE_LOG_MAP.get(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()));
-        }
+        super(context, slotId, ImsFeature.FEATURE_MMTEL);
+
         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() {
+    @Override
+    protected void onRemovedOrDied() {
         synchronized (mLock) {
             mRegistrationCallbackManager.close();
             mCapabilityCallbackManager.close();
@@ -618,7 +504,7 @@
                 return mRegistrationBinder;
             }
         }
-        TelephonyManager tm = getTelephonyManager(mContext);
+        TelephonyManager tm = getTelephonyManager();
         // We don't want to synchronize on a binder call to another process.
         IImsRegistration regBinder = tm != null
                 ? tm.getImsRegistration(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
@@ -639,7 +525,7 @@
                 return mConfigBinder;
             }
         }
-        TelephonyManager tm = getTelephonyManager(mContext);
+        TelephonyManager tm = getTelephonyManager();
         IImsConfig configBinder = tm != null
                 ? tm.getImsConfig(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
         synchronized (mLock) {
@@ -651,27 +537,71 @@
         return mConfigBinder;
     }
 
-    public boolean isEmergencyMmTelAvailable() {
-        return mSupportsEmergencyCalling;
-    }
-
-    public IImsServiceFeatureCallback getListener() {
-        return mListenerBinder;
-    }
-
-    public void setBinder(IBinder binder) {
+    @Override
+    protected void handleImsFeatureCreatedCallback(int slotId, int feature) {
+        // The feature has been enabled. This happens when the feature is first created and
+        // may happen when the feature is re-enabled.
         synchronized (mLock) {
-            mBinder = binder;
-            try {
-                if (mBinder != null) {
-                    mBinder.linkToDeath(mDeathRecipient, 0);
+            if(mSlotId != slotId) {
+                return;
+            }
+            switch (feature) {
+                case ImsFeature.FEATURE_MMTEL: {
+                    if (!mIsAvailable) {
+                        Log.i(TAG, "MmTel enabled on slotId: " + slotId);
+                        mIsAvailable = true;
+                    }
+                    break;
                 }
-            } catch (RemoteException e) {
-                // No need to do anything if the binder is already dead.
+                case ImsFeature.FEATURE_EMERGENCY_MMTEL: {
+                    mSupportsEmergencyCalling = true;
+                    Log.i(TAG, "Emergency calling enabled on slotId: " + slotId);
+                    break;
+                }
             }
         }
     }
 
+    @Override
+    protected void handleImsFeatureRemovedCallback(int slotId, int feature) {
+        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
+    protected void handleImsStatusChangedCallback(int slotId, int feature, int status) {
+        synchronized (mLock) {
+            Log.i(TAG, "imsStatusChanged: slot: " + slotId + " feature: "
+                + ImsFeature.FEATURE_LOG_MAP.get(feature) +
+                " status: " + ImsFeature.STATE_LOG_MAP.get(status));
+            if (mSlotId == slotId && feature == ImsFeature.FEATURE_MMTEL) {
+                mFeatureStateCached = status;
+                if (mStatusCallback != null) {
+                    mStatusCallback.notifyStateChanged();
+                }
+            }
+        }
+    }
+
+    public boolean isEmergencyMmTelAvailable() {
+        return mSupportsEmergencyCalling;
+    }
+
     /**
      * 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
@@ -891,32 +821,14 @@
     }
 
     /**
-     * @return an integer describing the current Feature Status, defined in
-     * {@link ImsFeature.ImsState}.
+     * @param c Callback that will fire when the feature status has changed.
      */
-    public int getFeatureState() {
-        synchronized (mLock) {
-            if (isBinderAlive() && mFeatureStateCached != null) {
-                return mFeatureStateCached;
-            }
-        }
-        // Don't synchronize on Binder call.
-        Integer state = retrieveFeatureState();
-        synchronized (mLock) {
-            if (state == null) {
-                return ImsFeature.STATE_UNAVAILABLE;
-            }
-            // Cache only non-null value for feature status.
-            mFeatureStateCached = state;
-        }
-        Log.i(TAG, "getFeatureState - returning " + ImsFeature.STATE_LOG_MAP.get(state));
-        return state;
+    public void setStatusCallback(IFeatureUpdate c) {
+        mStatusCallback = c;
     }
 
-    /**
-     * Internal method used to retrieve the feature status from the corresponding ImsService.
-     */
-    private Integer retrieveFeatureState() {
+    @Override
+    protected Integer retrieveFeatureState() {
         if (mBinder != null) {
             try {
                 return getServiceInterface(mBinder).getFeatureState();
@@ -927,42 +839,6 @@
         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);
     }
diff --git a/src/java/com/android/ims/RcsFeatureConnection.java b/src/java/com/android/ims/RcsFeatureConnection.java
new file mode 100644
index 0000000..7f65355
--- /dev/null
+++ b/src/java/com/android/ims/RcsFeatureConnection.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 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.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.ims.aidl.IRcsFeatureListener;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.aidl.IImsRcsFeature;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A container of the IImsServiceController binder, which implements all of the RcsFeatures that
+ * the platform currently supports: RCS
+ */
+public class RcsFeatureConnection extends FeatureConnection {
+    private static final String TAG = "RcsFeatureConnection";
+
+    public static @NonNull RcsFeatureConnection create(Context context , int slotId) {
+        RcsFeatureConnection serviceProxy = new RcsFeatureConnection(context, slotId);
+        if (!ImsManager.isImsSupportedOnDevice(context)) {
+            // Return empty service proxy in the case that IMS is not supported.
+            sImsSupportedOnDevice = false;
+            return serviceProxy;
+        }
+
+        if (!sRcsFeatureManagerProxy.isRcsUceSupportedByCarrier(context, slotId)) {
+            // Return empty service proxy in the case that RCS feature is not supported.
+            Rlog.w(TAG, "create: RCS UCE feature is not supported");
+            return serviceProxy;
+        }
+
+        TelephonyManager tm =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm == null) {
+            Rlog.w(TAG, "create: TelephonyManager is null");
+            return serviceProxy;
+        }
+
+        IImsRcsFeature binder = tm.getImsRcsFeatureAndListen(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);
+        }
+
+        Rlog.d(TAG, "create: RcsFeatureConnection");
+        return serviceProxy;
+    }
+
+    private RcsFeatureConnection(Context context, int slotId) {
+        super(context, slotId, ImsFeature.FEATURE_RCS);
+    }
+
+    /**
+     * Testing interface used to mock RcsFeatureManager in testing
+     * @hide
+     */
+    @VisibleForTesting
+    public interface RcsFeatureManagerProxy {
+        /**
+         * Mock-able interface for
+         * {@link RcsFeatureManager#isRcsUceSupportedByCarrier(Context, int)} used for testing.
+         */
+        boolean isRcsUceSupportedByCarrier(Context context, int slotId);
+    }
+
+    private static RcsFeatureManagerProxy sRcsFeatureManagerProxy = new RcsFeatureManagerProxy() {
+        @Override
+        public boolean isRcsUceSupportedByCarrier(Context context, int slotId) {
+            return RcsFeatureManager.isRcsUceSupportedByCarrier(context, slotId);
+        }
+    };
+
+    /**
+     * Testing function used to mock RcsFeatureManager in testing
+     * @hide
+     */
+    @VisibleForTesting
+    public static void setRcsFeatureManagerProxy(RcsFeatureManagerProxy proxy) {
+        sRcsFeatureManagerProxy = proxy;
+    }
+
+    @Override
+    protected void handleImsFeatureCreatedCallback(int slotId, int feature) {
+        Log.i(TAG, "IMS feature created: slotId= " + slotId + ", feature=" + feature);
+        if (!isUpdateForThisFeatureAndSlot(slotId, feature)) {
+            return;
+        }
+
+        synchronized(mLock) {
+            if (!mIsAvailable) {
+                Log.i(TAG, "RCS enabled on slotId: " + slotId);
+                mIsAvailable = true;
+            }
+        }
+    }
+
+    @Override
+    protected void handleImsFeatureRemovedCallback(int slotId, int feature) {
+        Log.i(TAG, "IMS feature removed: slotId= " + slotId + ", feature=" + feature);
+        if (!isUpdateForThisFeatureAndSlot(slotId, feature)) {
+            return;
+        }
+
+        synchronized(mLock) {
+            mIsAvailable = false;
+        }
+    }
+
+    @Override
+    protected void handleImsStatusChangedCallback(int slotId, int feature, int status) {
+        Log.i(TAG, "IMS status changed: slotId=" + slotId
+                + ", feature=" + feature + ", status=" + status);
+        if (!isUpdateForThisFeatureAndSlot(slotId, feature)) {
+            return;
+        }
+
+        synchronized(mLock) {
+            mFeatureStateCached = status;
+        }
+    }
+
+    private boolean isUpdateForThisFeatureAndSlot(int slotId, int feature) {
+        if (mSlotId == slotId && feature == ImsFeature.FEATURE_RCS) {
+            return true;
+        }
+        return false;
+    }
+
+    public void setRcsFeatureListener(IRcsFeatureListener listener) throws RemoteException {
+        checkServiceIsReady();
+        getServiceInterface(mBinder).setListener(listener);
+    }
+
+    @Override
+    @VisibleForTesting
+    public void checkServiceIsReady() throws RemoteException {
+        super.checkServiceIsReady();
+        if (!sRcsFeatureManagerProxy.isRcsUceSupportedByCarrier(mContext, mSlotId)) {
+            throw new RemoteException("RCS UCE feature is not supported");
+        }
+    }
+
+    @Override
+    @VisibleForTesting
+    public Integer retrieveFeatureState() {
+        if (mBinder != null) {
+            try {
+                return getServiceInterface(mBinder).getFeatureState();
+            } catch (RemoteException e) {
+                // Status check failed, don't update cache
+            }
+        }
+        return null;
+    }
+
+    private IImsRcsFeature getServiceInterface(IBinder b) {
+        return IImsRcsFeature.Stub.asInterface(b);
+    }
+}
diff --git a/src/java/com/android/ims/RcsFeatureManager.java b/src/java/com/android/ims/RcsFeatureManager.java
new file mode 100644
index 0000000..10cd1c7
--- /dev/null
+++ b/src/java/com/android/ims/RcsFeatureManager.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2019 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.content.Context;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+public class RcsFeatureManager {
+    private static final String TAG = "RcsFeatureManager";
+
+    private final int mSlotId;
+    private final Context mContext;
+    private RcsFeatureConnection mRcsFeatureConnection;
+
+    public RcsFeatureManager(Context context, int slotId) {
+        Log.d(TAG, "RcsFeatureManager slotId: " + slotId);
+        mContext = context;
+        mSlotId = slotId;
+
+        mRcsFeatureConnection = RcsFeatureConnection.create(mContext, mSlotId);
+    }
+
+    /**
+     * Testing interface used to mock SubscriptionManager in testing
+     * @hide
+     */
+    @VisibleForTesting
+    public interface SubscriptionManagerProxy {
+        /**
+         * Mock-able interface for {@link SubscriptionManager#getSubId(int)} used for testing.
+         */
+        int getSubId(int slotId);
+    }
+
+    private static SubscriptionManagerProxy sSubscriptionManagerProxy
+            = new SubscriptionManagerProxy() {
+                @Override
+                public int getSubId(int slotId) {
+                    int[] subIds = SubscriptionManager.getSubId(slotId);
+                    if (subIds != null) {
+                        Log.i(TAG, "getSubId : " + subIds[0]);
+                        return subIds[0];
+                    }
+                    return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+                }
+            };
+
+    /**
+     * Testing function used to mock SubscriptionManager in testing
+     * @hide
+     */
+    @VisibleForTesting
+    public static void setSubscriptionManager(SubscriptionManagerProxy proxy) {
+        sSubscriptionManagerProxy = proxy;
+    }
+
+    /**
+     * Check if RCS UCE feature is supported by carrier.
+     */
+    public static boolean isRcsUceSupportedByCarrier(Context context, int slotId) {
+        int subId = sSubscriptionManagerProxy.getSubId(slotId);
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            Log.e(TAG, "Getting subIds is failure! slotId: " + slotId);
+            return false;
+        }
+
+        boolean isPresenceSupported = false;
+        boolean isSipOptionsSupported = false;
+        CarrierConfigManager configManager =
+            (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(subId);
+            if (b != null) {
+                isPresenceSupported =
+                    b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, false);
+                isSipOptionsSupported =
+                    b.getBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, false);
+            }
+        }
+        Log.d(TAG, "isRcsUceSupportedByCarrier subId: " + subId
+                + ", presence= " + isPresenceSupported
+                + ", sip options=" + isSipOptionsSupported);
+        return isPresenceSupported|isSipOptionsSupported;
+    }
+}