Enhance TelephonyRcsService to only create needed features

1) When determining the features that should be added to an
RcsFeatureContainer, take into account that subscriptions
CarrierConfig. This will stop unnecessary polling for the
RcsFeature for subscriptions that are not configured to support
RCS in the first place.

2) Fix some of the logging

Bug: 149100088
Test: atest TeleServiceTests
Change-Id: Ieaa23ba002528c75b87af961f5af534b6589a130
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 6b1b5e3..3ea8df2 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -361,8 +361,8 @@
         int slotId = phone.getPhoneId();
         RcsFeatureController c = mRcsService.getFeatureController(slotId);
         if (c == null) {
-            throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
-                    "Cannot find RcsFeatureController instance for sub: " + subId);
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "The requested operation is not supported for subId " + subId);
         }
         return c;
     }
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index f451e9b..5094c57 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -125,7 +125,7 @@
                 public void connectionReady(RcsFeatureManager manager)
                         throws com.android.ims.ImsException {
                     if (manager == null) {
-                        Log.w(LOG_TAG, "connectionReady returned null RcsFeatureManager");
+                        logw("connectionReady returned null RcsFeatureManager");
                         return;
                     }
                     try {
@@ -192,6 +192,7 @@
      */
     public void connect() {
         synchronized (mLock) {
+            if (mFeatureConnector != null) return;
             mFeatureConnector = mFeatureFactory.create(mContext, mSlotId, mFeatureConnectorListener,
                     mContext.getMainExecutor(), LOG_TAG);
             mFeatureConnector.connect();
@@ -224,6 +225,25 @@
     }
 
     /**
+     * Removes the feature associated with this class.
+     */
+    public <T> void removeFeature(Class<T> clazz) {
+        synchronized (mLock) {
+            RcsFeatureController.Feature feature = mFeatures.remove(clazz);
+            feature.onDestroy();
+        }
+    }
+
+    /**
+     * @return true if this controller has features it is actively tracking.
+     */
+    public boolean hasActiveFeatures() {
+        synchronized (mLock) {
+            return mFeatures.size() > 0;
+        }
+    }
+
+    /**
      * Update the subscription associated with this controller.
      */
     public void updateAssociatedSubscription(int newSubId) {
@@ -247,7 +267,10 @@
      */
     public void destroy() {
         synchronized (mLock) {
-            mFeatureConnector.disconnect();
+            Log.i(LOG_TAG, "destroy: slotId=" + mSlotId);
+            if (mFeatureConnector != null) {
+                mFeatureConnector.disconnect();
+            }
             for (Feature c : mFeatures.values()) {
                 c.onRcsDisconnected();
                 c.onDestroy();
@@ -406,4 +429,15 @@
             pw.println(mFeatureManager != null);
         }
     }
+
+    private void logw(String log) {
+        Log.w(LOG_TAG, getLogPrefix().append(log).toString());
+    }
+
+    private StringBuilder getLogPrefix() {
+        StringBuilder sb = new StringBuilder("[");
+        sb.append(mSlotId);
+        sb.append("] ");
+        return sb;
+    }
 }
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index b4223d3..c85e9a9 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -28,6 +28,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.PhoneConfigurationManager;
@@ -35,8 +36,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Singleton service setup to manage RCS related services that the platform provides such as User
@@ -85,8 +84,8 @@
     private final Object mLock = new Object();
     private int mNumSlots;
 
-    // Index corresponds to the slot ID.
-    private List<RcsFeatureController> mFeatureControllers;
+    // Maps slot ID -> RcsFeatureController.
+    private SparseArray<RcsFeatureController> mFeatureControllers;
 
     private BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() {
         @Override
@@ -99,8 +98,10 @@
                 if (bundle == null) {
                     return;
                 }
-                int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX);
-                int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX);
+                int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX,
+                        SubscriptionManager.INVALID_PHONE_INDEX);
+                int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
                 updateFeatureControllerSubscription(slotId, subId);
             }
         }
@@ -125,10 +126,9 @@
     });
 
     public TelephonyRcsService(Context context, int numSlots) {
-        Log.i(LOG_TAG, "initialize");
         mContext = context;
         mNumSlots = numSlots;
-        mFeatureControllers = new ArrayList<>(numSlots);
+        mFeatureControllers = new SparseArray<>(numSlots);
     }
 
     /**
@@ -145,11 +145,7 @@
      * system callbacks.
      */
     public void initialize() {
-        synchronized (mLock) {
-            for (int i = 0; i < mNumSlots; i++) {
-                mFeatureControllers.add(constructFeatureController(i));
-            }
-        }
+        updateFeatureControllerSize(mNumSlots);
 
         PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
                 HANDLER_MSIM_CONFIGURATION_CHANGE, null);
@@ -173,15 +169,24 @@
             if (oldNumSlots == newNumSlots) {
                 return;
             }
+            Log.i(LOG_TAG, "updateFeatureControllers: oldSlots=" + oldNumSlots + ", newNumSlots="
+                    + newNumSlots);
             mNumSlots = newNumSlots;
             if (oldNumSlots < newNumSlots) {
                 for (int i = oldNumSlots; i < newNumSlots; i++) {
-                    mFeatureControllers.add(constructFeatureController(i));
+                    RcsFeatureController c = constructFeatureController(i);
+                    // Do not add feature controllers for inactive subscriptions
+                    if (c.hasActiveFeatures()) {
+                        mFeatureControllers.put(i, c);
+                    }
                 }
             } else {
                 for (int i = (oldNumSlots - 1); i > (newNumSlots - 1); i--) {
-                    RcsFeatureController controller = mFeatureControllers.remove(i);
-                    controller.destroy();
+                    RcsFeatureController c = mFeatureControllers.get(i);
+                    if (c != null) {
+                        mFeatureControllers.remove(i);
+                        c.destroy();
+                    }
                 }
             }
         }
@@ -190,24 +195,62 @@
     private void updateFeatureControllerSubscription(int slotId, int newSubId) {
         synchronized (mLock) {
             RcsFeatureController f = mFeatureControllers.get(slotId);
-            if (f == null) {
-                Log.w(LOG_TAG, "unexpected null FeatureContainer for slot " + slotId);
-                return;
+            Log.i(LOG_TAG, "updateFeatureControllerSubscription: slotId=" + slotId + " newSubId="
+                    + newSubId + ", existing feature=" + (f != null));
+            if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
+                if (f == null) {
+                    // A controller doesn't exist for this slot yet.
+                    f = mFeatureFactory.createController(mContext, slotId);
+                    updateSupportedFeatures(f, slotId, newSubId);
+                    if (f.hasActiveFeatures()) mFeatureControllers.put(slotId, f);
+                } else {
+                    updateSupportedFeatures(f, slotId, newSubId);
+                    // Do not keep an empty container around.
+                    if (!f.hasActiveFeatures()) {
+                        f.destroy();
+                        mFeatureControllers.remove(slotId);
+                    }
+                }
             }
-            f.updateAssociatedSubscription(newSubId);
+            if (f != null) f.updateAssociatedSubscription(newSubId);
         }
     }
 
     private RcsFeatureController constructFeatureController(int slotId) {
         RcsFeatureController c = mFeatureFactory.createController(mContext, slotId);
-        // TODO: integrate user setting into whether or not this feature is added as well as logic
-        // to listen for changes in user setting.
-        c.addFeature(mFeatureFactory.createUserCapabilityExchange(mContext, slotId,
-                getSubscriptionFromSlot(slotId)), UserCapabilityExchangeImpl.class);
-        c.connect();
+        int subId = getSubscriptionFromSlot(slotId);
+        updateSupportedFeatures(c, slotId, subId);
         return c;
     }
 
+    private void updateSupportedFeatures(RcsFeatureController c, int slotId, int subId) {
+        if (doesSubscriptionSupportPresence(subId)) {
+            if (c.getFeature(UserCapabilityExchangeImpl.class) == null) {
+                c.addFeature(mFeatureFactory.createUserCapabilityExchange(mContext, slotId, subId),
+                        UserCapabilityExchangeImpl.class);
+            }
+        } else {
+            if (c.getFeature(UserCapabilityExchangeImpl.class) != null) {
+                c.removeFeature(UserCapabilityExchangeImpl.class);
+            }
+        }
+        // Only start the connection procedure if we have active features.
+        if (c.hasActiveFeatures()) c.connect();
+    }
+
+    private boolean doesSubscriptionSupportPresence(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;
+        CarrierConfigManager carrierConfigManager =
+                mContext.getSystemService(CarrierConfigManager.class);
+        if (carrierConfigManager == null) return false;
+        boolean supportsUce = carrierConfigManager.getConfigForSubId(subId).getBoolean(
+                CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
+        supportsUce |= carrierConfigManager.getConfigForSubId(subId).getBoolean(
+                CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL);
+        return supportsUce;
+    }
+
+
     private int getSubscriptionFromSlot(int slotId) {
         SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
         if (manager == null) {
@@ -229,7 +272,8 @@
         pw.println("RcsFeatureControllers:");
         pw.increaseIndent();
         synchronized (mLock) {
-            for (RcsFeatureController f : mFeatureControllers) {
+            for (int i = 0; i < mNumSlots; i++) {
+                RcsFeatureController f = mFeatureControllers.get(i);
                 pw.increaseIndent();
                 f.dump(fd, printWriter, args);
                 pw.decreaseIndent();
diff --git a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
index 7521205..d488dff 100644
--- a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
+++ b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
@@ -44,7 +44,7 @@
 public class UserCapabilityExchangeImpl implements RcsFeatureController.Feature, SubscribePublisher,
         PresencePublisher {
 
-    private static final String LOG_TAG = "UserCapabilityExchangeImpl";
+    private static final String LOG_TAG = "RcsUceImpl";
 
     private int mSlotId;
     private int mSubId;
@@ -58,6 +58,7 @@
     UserCapabilityExchangeImpl(Context context, int slotId, int subId) {
         mSlotId = slotId;
         mSubId = subId;
+        logi("created");
 
         String[] volteError = context.getResources().getStringArray(
                 R.array.config_volte_provision_error_on_publish_response);
@@ -78,7 +79,7 @@
     // Runs on main thread.
     @Override
     public void onRcsConnected(RcsFeatureManager rcsFeatureManager) {
-        Log.i(LOG_TAG, "onRcsConnected: slotId=" + mSlotId + ", subId=" + mSubId);
+        logi("onRcsConnected");
         mPresencePublication.updatePresencePublisher(this);
         mPresenceSubscriber.updatePresenceSubscriber(this);
     }
@@ -86,7 +87,7 @@
     // Runs on main thread.
     @Override
     public void onRcsDisconnected() {
-        Log.i(LOG_TAG, "onRcsDisconnected: phoneId=" + mSlotId + ", subId=" + mSubId);
+        logi("onRcsDisconnected");
         mPresencePublication.removePresencePublisher();
         mPresenceSubscriber.removePresenceSubscriber();
     }
@@ -127,7 +128,7 @@
                 new ContactCapabilityResponse() {
                     @Override
                     public void onSuccess(int reqId) {
-                        Log.i(LOG_TAG, "onSuccess called for reqId:" + reqId);
+                        logi("onSuccess called for reqId:" + reqId);
                     }
 
                     @Override
@@ -137,16 +138,16 @@
                             if (c != null) {
                                 c.onError(toUceError(resultCode));
                             } else {
-                                Log.w(LOG_TAG, "onError called for unknown reqId:" + reqId);
+                                logw("onError called for unknown reqId:" + reqId);
                             }
                         } catch (RemoteException e) {
-                            Log.i(LOG_TAG, "Calling back to dead service");
+                            logi("Calling back to dead service");
                         }
                     }
 
                     @Override
                     public void onFinish(int reqId) {
-                        Log.i(LOG_TAG, "onFinish called for reqId:" + reqId);
+                        logi("onFinish called for reqId:" + reqId);
                     }
 
                     @Override
@@ -156,10 +157,10 @@
                             if (c != null) {
                                 c.onError(RcsUceAdapter.ERROR_REQUEST_TIMEOUT);
                             } else {
-                                Log.w(LOG_TAG, "onTimeout called for unknown reqId:" + reqId);
+                                logw("onTimeout called for unknown reqId:" + reqId);
                             }
                         } catch (RemoteException e) {
-                            Log.i(LOG_TAG, "Calling back to dead service");
+                            logi("Calling back to dead service");
                         }
                     }
 
@@ -172,10 +173,10 @@
                             if (c != null) {
                                 c.onCapabilitiesReceived(contactCapabilities);
                             } else {
-                                Log.w(LOG_TAG, "onCapabilitiesUpdated, unknown reqId:" + reqId);
+                                logw("onCapabilitiesUpdated, unknown reqId:" + reqId);
                             }
                         } catch (RemoteException e) {
-                            Log.w(LOG_TAG, "onCapabilitiesUpdated on dead service");
+                            logw("onCapabilitiesUpdated on dead service");
                         }
                     }
                 });
@@ -184,7 +185,7 @@
                 c.onError(toUceError(taskId));
                 return;
             } catch (RemoteException e) {
-                Log.i(LOG_TAG, "Calling back to dead service");
+                logi("Calling back to dead service");
             }
         }
         mPendingCapabilityRequests.put(taskId, c);
@@ -272,4 +273,21 @@
                 return RcsUceAdapter.ERROR_GENERIC_FAILURE;
         }
     }
+
+    private void logi(String log) {
+        Log.i(LOG_TAG, getLogPrefix().append(log).toString());
+    }
+
+    private void logw(String log) {
+        Log.w(LOG_TAG, getLogPrefix().append(log).toString());
+    }
+
+    private StringBuilder getLogPrefix() {
+        StringBuilder builder = new StringBuilder("[");
+        builder.append(mSlotId);
+        builder.append("->");
+        builder.append(mSubId);
+        builder.append("] ");
+        return builder;
+    }
 }