Merge "callforwarding: Add checking response NPE"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 39daea8..d75e484 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1447,6 +1447,17 @@
     <string name="alert_dialog_no">No</string>
     <!-- ECM: ECM exit dialog choice -->
     <string name="alert_dialog_dismiss">Dismiss</string>
+    <!-- ECM: Notification body wihout data restriction hint -->
+    <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint">The phone is in emergency callback mode</string>
+    <!-- ECM: Displays the time when ECM will end without data restriction hint, Example: "Until 10:45 AM" -->
+    <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint">Until <xliff:g id="completeTime">%s</xliff:g></string>
+    <!-- ECM: Dialog box message without data restriction hint for exiting from the notifications screen -->
+    <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint">
+        <!-- number of minutes is one -->
+        <item quantity="one">The phone will be in emergency callback mode for <xliff:g id="count">%s</xliff:g> minute.\nDo you want to exit now?</item>
+        <!-- number of minutes is not equal to one -->
+        <item quantity="other">The phone will be in emergency callback mode for <xliff:g id="count">%s</xliff:g> minutes.\nDo you want to exit now?</item>
+    </plurals>
 
     <!-- For incoming calls, this is a string we can get from a CDMA network instead of
          the actual phone number, to indicate there's no number present.  DO NOT TRANSLATE. -->
diff --git a/src/com/android/phone/CallBarringEditPreference.java b/src/com/android/phone/CallBarringEditPreference.java
index 4541926..5d83de1 100644
--- a/src/com/android/phone/CallBarringEditPreference.java
+++ b/src/com/android/phone/CallBarringEditPreference.java
@@ -35,6 +35,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
@@ -108,7 +109,7 @@
         if (!skipReading) {
             // Query call barring status
             mPhone.getCallBarring(mFacility, "", mHandler.obtainMessage(
-                    MyHandler.MESSAGE_GET_CALL_BARRING), 0);
+                    MyHandler.MESSAGE_GET_CALL_BARRING), CommandsInterface.SERVICE_CLASS_VOICE);
             if (mTcpListener != null) {
                 mTcpListener.onStarted(this, true);
             }
@@ -202,7 +203,8 @@
             }
             // Send set call barring message to RIL layer.
             mPhone.setCallBarring(mFacility, !mIsActivated, password,
-                    mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_BARRING), 0);
+                    mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_BARRING),
+                    CommandsInterface.SERVICE_CLASS_VOICE);
             if (mTcpListener != null) {
                 mTcpListener.onStarted(this, false);
             }
@@ -326,7 +328,7 @@
                     "",
                     obtainMessage(MESSAGE_GET_CALL_BARRING, 0, MESSAGE_SET_CALL_BARRING,
                             ar.exception),
-                    0);
+                    CommandsInterface.SERVICE_CLASS_VOICE);
         }
     }
 }
diff --git a/src/com/android/phone/EmergencyCallbackModeExitDialog.java b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
index dcfa024..6edc155 100644
--- a/src/com/android/phone/EmergencyCallbackModeExitDialog.java
+++ b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
@@ -300,8 +300,14 @@
             return String.format(getResources().getQuantityText(
                     R.plurals.alert_dialog_not_avaialble_in_ecm, minutes).toString(), time);
         case EXIT_ECM_DIALOG:
-            return String.format(getResources().getQuantityText(R.plurals.alert_dialog_exit_ecm,
-                    minutes).toString(), time);
+                boolean shouldRestrictData = mPhone.getImsPhone() != null
+                        && mPhone.getImsPhone().isInImsEcm();
+                return String.format(getResources().getQuantityText(
+                        // During IMS ECM, data restriction hint should be removed.
+                        shouldRestrictData
+                        ? R.plurals.alert_dialog_exit_ecm_without_data_restriction_hint
+                        : R.plurals.alert_dialog_exit_ecm,
+                        minutes).toString(), time);
         }
         return null;
     }
diff --git a/src/com/android/phone/EmergencyCallbackModeService.java b/src/com/android/phone/EmergencyCallbackModeService.java
index 41d83c4..012a670 100644
--- a/src/com/android/phone/EmergencyCallbackModeService.java
+++ b/src/com/android/phone/EmergencyCallbackModeService.java
@@ -194,7 +194,11 @@
         // Format notification string
         String text = null;
         if(mInEmergencyCall) {
-            text = getText(R.string.phone_in_ecm_call_notification_text).toString();
+            text = getText(
+                    // During IMS ECM, data restriction hint should be removed.
+                    (imsPhone != null && imsPhone.isInImsEcm())
+                    ? R.string.phone_in_ecm_call_notification_text_without_data_restriction_hint
+                    : R.string.phone_in_ecm_call_notification_text).toString();
         } else {
             // Calculate the time in ms when the notification will be finished.
             long finishedCountMs = millisUntilFinished + System.currentTimeMillis();
@@ -205,7 +209,11 @@
 
             String completeTime = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(
                     finishedCountMs);
-            text = getResources().getString(R.string.phone_in_ecm_notification_complete_time,
+            text = getResources().getString(
+                    // During IMS ECM, data restriction hint should be removed.
+                    (imsPhone != null && imsPhone.isInImsEcm())
+                    ? R.string.phone_in_ecm_notification_complete_time_without_data_restriction_hint
+                    : R.string.phone_in_ecm_notification_complete_time,
                     completeTime);
         }
         builder.setContentText(text);
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/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index fe55335..fccceec 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -470,7 +470,7 @@
 
     private Intent getShowVoicemailIntentForDefaultDialer(UserHandle userHandle) {
         String dialerPackage = mContext.getSystemService(TelecomManager.class)
-                .getDefaultDialerPackage(userHandle.getIdentifier());
+                .getDefaultDialerPackage(userHandle);
         return new Intent(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION)
                 .setPackage(dialerPackage);
     }
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 4eb1788..8e6e7c8 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -35,7 +35,6 @@
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.net.NetworkStats;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Binder;
@@ -3210,8 +3209,8 @@
     @Override
     public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c)
             throws RemoteException {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("registerImsRegistrationCallback");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "registerImsRegistrationCallback");
 
         if (!ImsManager.isImsSupportedOnDevice(mApp)) {
             throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
@@ -3236,8 +3235,8 @@
      */
     @Override
     public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("unregisterImsRegistrationCallback");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "unregisterImsRegistrationCallback");
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
         }
@@ -3293,8 +3292,8 @@
      */
     @Override
     public void getImsMmTelRegistrationTransportType(int subId, IIntegerConsumer consumer) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("getImsMmTelRegistrationTransportType");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "getImsMmTelRegistrationTransportType");
         if (!ImsManager.isImsSupportedOnDevice(mApp)) {
             throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                     "IMS not available on device.");
@@ -3333,8 +3332,8 @@
     @Override
     public void registerMmTelCapabilityCallback(int subId, IImsCapabilityCallback c)
             throws RemoteException {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("registerMmTelCapabilityCallback");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "registerMmTelCapabilityCallback");
         if (!ImsManager.isImsSupportedOnDevice(mApp)) {
             throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                     "IMS not available on device.");
@@ -3358,8 +3357,8 @@
      */
     @Override
     public void unregisterMmTelCapabilityCallback(int subId, IImsCapabilityCallback c) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("unregisterMmTelCapabilityCallback");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "unregisterMmTelCapabilityCallback");
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
         }
@@ -3455,8 +3454,8 @@
      */
     @Override
     public boolean isAdvancedCallingSettingEnabled(int subId) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("isAdvancedCallingSettingEnabled");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "isAdvancedCallingSettingEnabled");
 
         // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
         final long token = Binder.clearCallingIdentity();
@@ -3492,8 +3491,8 @@
      */
     @Override
     public boolean isVtSettingEnabled(int subId) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("isVtSettingEnabled");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "isVtSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -3526,8 +3525,8 @@
      */
     @Override
     public boolean isVoWiFiSettingEnabled(int subId) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("isVoWiFiSettingEnabled");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "isVoWiFiSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -3561,8 +3560,8 @@
      */
     @Override
     public boolean isVoWiFiRoamingSettingEnabled(int subId) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("isVoWiFiRoamingSettingEnabled");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "isVoWiFiRoamingSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -3613,8 +3612,8 @@
      */
     @Override
     public int getVoWiFiModeSetting(int subId) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("getVoWiFiModeSetting");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "getVoWiFiModeSetting");
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -3695,8 +3694,8 @@
      */
     @Override
     public boolean isTtyOverVolteEnabled(int subId) {
-        //TODO: b/147498511 will add TelephonyPermissions#checkCallingOrSelfReadPrecisePhoneState
-        enforceReadPrivilegedPermission("isTtyOverVolteEnabled");
+        TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "isTtyOverVolteEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -7158,33 +7157,6 @@
     }
 
     /**
-     * Get aggregated video call data usage since boot.
-     *
-     * @param perUidStats True if requesting data usage per uid, otherwise overall usage.
-     * @return Snapshot of video call data usage
-     * {@hide}
-     */
-    @Override
-    public NetworkStats getVtDataUsage(int subId, boolean perUidStats) {
-        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_NETWORK_USAGE_HISTORY,
-                null);
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            // NetworkStatsService keeps tracking the active network interface and identity. It
-            // records the delta with the corresponding network identity.
-            // We just return the total video call data usage snapshot since boot.
-            Phone phone = getPhone(subId);
-            if (phone != null) {
-                return phone.getVtDataUsage(perUidStats);
-            }
-            return null;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
      * Policy control of data connection. Usually used when data limit is passed.
      * @param enabled True if enabling the data, otherwise disabling.
      * @param subId Subscription index
@@ -7701,7 +7673,8 @@
 
     @Override
     public int getCdmaRoamingMode(int subId) {
-        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+        TelephonyPermissions
+                .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
                 mApp, subId, "getCdmaRoamingMode");
 
         final long identity = Binder.clearCallingIdentity();
diff --git a/src/com/android/services/telephony/CdmaConference.java b/src/com/android/services/telephony/CdmaConference.java
index 32badd0..7458195 100644
--- a/src/com/android/services/telephony/CdmaConference.java
+++ b/src/com/android/services/telephony/CdmaConference.java
@@ -17,6 +17,7 @@
 package com.android.services.telephony;
 
 import android.content.Context;
+import android.net.Uri;
 import android.os.PersistableBundle;
 import android.telecom.Connection;
 import android.telecom.PhoneAccountHandle;
@@ -73,6 +74,12 @@
     }
 
     @Override
+    public void onAddConferenceParticipants(List<Uri> participants) {
+        Log.e(this, new Exception(), "Adding Conference Participants not supported " +
+                " for CDMA conference call.");
+    }
+
+    @Override
     public void onAnswer(int videoState) {
         Log.e(this, new Exception(), "Answer not supported for CDMA conference call.");
     }
diff --git a/src/com/android/services/telephony/ConferenceParticipantConnection.java b/src/com/android/services/telephony/ConferenceParticipantConnection.java
index ac3928f..b7ecd48 100644
--- a/src/com/android/services/telephony/ConferenceParticipantConnection.java
+++ b/src/com/android/services/telephony/ConferenceParticipantConnection.java
@@ -19,13 +19,13 @@
 import android.net.Uri;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
-import com.android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
 import android.text.TextUtils;
 
 import com.android.ims.internal.ConferenceParticipant;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.telephony.Rlog;
 
 /**
  * Represents a participant in a conference call.
@@ -215,7 +215,7 @@
         sb.append(" connectTime:");
         sb.append(getConnectTimeMillis());
         sb.append(" connectElapsedTime:");
-        sb.append(getConnectElapsedTimeMillis());
+        sb.append(getConnectionStartElapsedRealtimeMillis());
         sb.append("]");
 
         return sb.toString();
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index f5f5c66..c11a1ca 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -30,7 +30,6 @@
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import com.android.telephony.Rlog;
 import android.util.Pair;
 
 import com.android.ims.internal.ConferenceParticipant;
@@ -42,6 +41,7 @@
 import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -313,10 +313,10 @@
         long connectTime = conferenceHost.getOriginalConnection().getConnectTime();
         long connectElapsedTime = conferenceHost.getOriginalConnection().getConnectTimeReal();
         setConnectionTime(connectTime);
-        setConnectionStartElapsedRealTime(connectElapsedTime);
+        setConnectionStartElapsedRealtimeMillis(connectElapsedTime);
         // Set the connectTime in the connection as well.
         conferenceHost.setConnectTimeMillis(connectTime);
-        conferenceHost.setConnectionStartElapsedRealTime(connectElapsedTime);
+        conferenceHost.setConnectionStartElapsedRealtimeMillis(connectElapsedTime);
 
         mTelephonyConnectionService = telephonyConnectionService;
         setConferenceHost(conferenceHost);
@@ -374,6 +374,10 @@
                 Connection.CAPABILITY_CAN_PAUSE_VIDEO,
                 mConferenceHost.getVideoPauseSupported() && isVideoCapable());
 
+        conferenceCapabilities = changeBitmask(conferenceCapabilities,
+                Connection.CAPABILITY_ADD_PARTICIPANT,
+                (capabilities & Connection.CAPABILITY_ADD_PARTICIPANT) != 0);
+
         return conferenceCapabilities;
     }
 
@@ -522,6 +526,19 @@
     }
 
     /**
+     * Supports adding participants to an existing conference call
+     *
+     * @param participants that are pulled to existing conference call
+     */
+    @Override
+    public void onAddConferenceParticipants(List<Uri> participants) {
+        if (mConferenceHost == null) {
+            return;
+        }
+        mConferenceHost.performAddConferenceParticipants(participants);
+    }
+
+    /**
      * Invoked when the conference is answered.
      */
     @Override
@@ -741,7 +758,8 @@
             setAddress(mConferenceHost.getAddress(), mConferenceHost.getAddressPresentation());
             setCallerDisplayName(mConferenceHost.getCallerDisplayName(),
                     mConferenceHost.getCallerDisplayNamePresentation());
-            setConnectionStartElapsedRealTime(mConferenceHost.getConnectElapsedTimeMillis());
+            setConnectionStartElapsedRealtimeMillis(
+                    mConferenceHost.getConnectionStartElapsedRealtimeMillis());
             setConnectionTime(mConferenceHost.getConnectTimeMillis());
         }
 
@@ -959,7 +977,8 @@
             Log.d(this,
                     "stopEmulatingSinglePartyCall: restored lone participant connect time");
             loneParticipant.setConnectTimeMillis(getConnectionTime());
-            loneParticipant.setConnectionStartElapsedRealTime(getConnectionStartElapsedRealTime());
+            loneParticipant.setConnectionStartElapsedRealtimeMillis(
+                    getConnectionStartElapsedRealtimeMillis());
         }
 
         // Tell Telecom its a conference again.
@@ -999,7 +1018,8 @@
             setAddress(entry.getAddress(), entry.getAddressPresentation());
             setCallerDisplayName(entry.getCallerDisplayName(),
                     entry.getCallerDisplayNamePresentation());
-            setConnectionStartElapsedRealTime(entry.getConnectElapsedTimeMillis());
+            setConnectionStartElapsedRealtimeMillis(
+                    entry.getConnectionStartElapsedRealtimeMillis());
             setConnectionTime(entry.getConnectTimeMillis());
             mLoneParticipantIdentity = new Pair<>(entry.getUserEntity(), entry.getEndpoint());
 
@@ -1043,10 +1063,11 @@
                 !isConferenceHost() /* isRemotelyHosted */);
         if (participant.getConnectTime() == 0) {
             connection.setConnectTimeMillis(parent.getConnectTimeMillis());
-            connection.setConnectionStartElapsedRealTime(parent.getConnectElapsedTimeMillis());
+            connection.setConnectionStartElapsedRealtimeMillis(
+                    parent.getConnectionStartElapsedRealtimeMillis());
         } else {
             connection.setConnectTimeMillis(participant.getConnectTime());
-            connection.setConnectionStartElapsedRealTime(participant.getConnectElapsedTime());
+            connection.setConnectionStartElapsedRealtimeMillis(participant.getConnectElapsedTime());
         }
         // Indicate whether this is an MT or MO call to Telecom; the participant has the cached
         // data from the time of merge.
@@ -1212,7 +1233,8 @@
                 c.updateState();
                 // Copy the connect time from the conferenceHost
                 c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis());
-                c.setConnectionStartElapsedRealTime(mConferenceHost.getConnectElapsedTimeMillis());
+                c.setConnectionStartElapsedRealtimeMillis(
+                        mConferenceHost.getConnectionStartElapsedRealtimeMillis());
                 mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
                 mTelephonyConnectionService.addConnectionToConferenceController(c);
             } // CDMA case not applicable for SRVCC
diff --git a/src/com/android/services/telephony/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index d720639..7e4693f 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -16,6 +16,7 @@
 
 package com.android.services.telephony;
 
+import android.net.Uri;
 import android.telecom.Connection;
 import android.telecom.PhoneAccountHandle;
 
@@ -103,6 +104,12 @@
     }
 
     @Override
+    public void onAddConferenceParticipants(List<Uri> participants) {
+        Log.e(this, new Exception(), "Adding Conference Participants not supported " +
+                " for GSM conference call.");
+    }
+
+    @Override
     public void onMerge(Connection connection) {
         try {
             Phone phone = ((TelephonyConnection) connection).getPhone();
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
old mode 100644
new mode 100755
index 29b65d0..fd9de4f
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -40,7 +40,6 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
-import com.android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.SubscriptionManager;
@@ -51,6 +50,7 @@
 import android.util.Pair;
 
 import com.android.ims.ImsCall;
+import com.android.ims.ImsException;
 import com.android.ims.internal.ConferenceParticipant;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
@@ -63,12 +63,14 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.phone.ImsUtil;
 import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -870,6 +872,11 @@
     }
 
     @Override
+    public void onAddConferenceParticipants(List<Uri> participants) {
+        performAddConferenceParticipants(participants);
+    }
+
+    @Override
     public void onAbort() {
         Log.v(this, "onAbort");
         mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.LOCAL).sendToTarget();
@@ -949,6 +956,58 @@
     }
 
     @Override
+    public void onTransfer(Uri number, boolean isConfirmationRequired) {
+        Log.v(this, "onTransfer");
+        if (mOriginalConnection != null) {
+            if (number == null) {
+                Log.w(this, "call transfer uri is null");
+                return;
+            }
+            String scheme = number.getScheme();
+            String transferNumber = "";
+            String uriString = number.getSchemeSpecificPart();
+            if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
+                if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
+                    Log.w(this, "onTransfer, number scheme is not of type tel instead: "
+                            + scheme);
+                    return;
+                }
+                if (PhoneNumberUtils.isUriNumber(uriString)) {
+                    Log.w(this, "Invalid transfer address. Not a legal PSTN number.");
+                    return;
+                }
+                transferNumber = PhoneNumberUtils.convertAndStrip(uriString);
+                if (TextUtils.isEmpty(transferNumber)) {
+                    Log.w(this, "Empty transfer number obtained from uri");
+                    return;
+                }
+            } else {
+                Log.w(this, "Cannot transfer to voicemail uri");
+                return;
+            }
+
+            try {
+                mOriginalConnection.transfer(transferNumber, isConfirmationRequired);
+            } catch (CallStateException e) {
+                Log.e(this, e, "Failed to transfer call.");
+            }
+        }
+    }
+
+    @Override
+    public void onTransfer(Connection otherConnection) {
+        Log.v(this, "onConsultativeTransfer");
+        if (mOriginalConnection != null && (otherConnection instanceof TelephonyConnection)) {
+            try {
+                mOriginalConnection.consultativeTransfer(
+                        ((TelephonyConnection) otherConnection).getOriginalConnection());
+            } catch (CallStateException e) {
+                Log.e(this, e, "Failed to transfer call.");
+            }
+        }
+    }
+
+    @Override
     public void onPostDialContinue(boolean proceed) {
         Log.v(this, "onPostDialContinue, proceed: " + proceed);
         if (mOriginalConnection != null) {
@@ -1120,6 +1179,29 @@
         }
     }
 
+    private String[] getAddConferenceParticipants(List<Uri> participants) {
+        String[] addConfParticipants = new String[participants.size()];
+        int i = 0;
+        for (Uri participant : participants) {
+           addConfParticipants[i] = participant.getSchemeSpecificPart();
+           i++;
+        }
+        return addConfParticipants;
+    }
+
+    public void performAddConferenceParticipants(List<Uri> participants) {
+        Log.v(this, "performAddConferenceParticipants");
+        if (mOriginalConnection.getCall() instanceof ImsPhoneCall) {
+            ImsPhoneCall imsPhoneCall = (ImsPhoneCall)mOriginalConnection.getCall();
+            try {
+                imsPhoneCall.getImsCall().inviteParticipants(
+                        getAddConferenceParticipants(participants));
+            } catch(ImsException e) {
+                Log.e(this, e, "failed to add conference participants");
+            }
+        }
+    }
+
     /**
      * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based
      * capabilities.
@@ -1154,6 +1236,12 @@
         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_SUPPORT_DEFLECT,
                 isImsConnection() && canDeflectImsCalls());
 
+        newCapabilities = applyAddParticipantCapabilities(newCapabilities);
+        newCapabilities = changeBitmask(newCapabilities, CAPABILITY_TRANSFER_CONSULTATIVE,
+                isImsConnection() && canConsultativeTransfer());
+        newCapabilities = changeBitmask(newCapabilities, CAPABILITY_TRANSFER,
+                isImsConnection() && canTransferToNumber());
+
         if (getConnectionCapabilities() != newCapabilities) {
             setConnectionCapabilities(newCapabilities);
             notifyConnectionCapabilitiesChanged(newCapabilities);
@@ -1186,7 +1274,7 @@
                 isExternalConnection());
         newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY,
                 mIsCdmaVoicePrivacyEnabled);
-        newProperties = changeBitmask(newProperties, PROPERTY_ASSISTED_DIALING_USED,
+        newProperties = changeBitmask(newProperties, PROPERTY_ASSISTED_DIALING,
                 mIsUsingAssistedDialing);
         newProperties = changeBitmask(newProperties, PROPERTY_IS_RTT, isRtt());
         newProperties = changeBitmask(newProperties, PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
@@ -1580,6 +1668,75 @@
                 || !VideoProfile.isVideo(getVideoState()));
     }
 
+    private boolean isConferenceHosted() {
+        boolean isHosted = false;
+        if (getTelephonyConnectionService() != null) {
+            for (Conference current : getTelephonyConnectionService().getAllConferences()) {
+                if (current instanceof ImsConference) {
+                    ImsConference other = (ImsConference) current;
+                    if (getState() == current.getState()) {
+                        continue;
+                    }
+                    if (other.isConferenceHost()) {
+                        isHosted = true;
+                        break;
+                    }
+                }
+            }
+        }
+        return isHosted;
+    }
+
+    private boolean isAddParticipantCapable() {
+        // not add participant capable for non ims phones
+        if (getPhone() == null || getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
+            return false;
+        }
+
+        if (!getCarrierConfig()
+                .getBoolean(CarrierConfigManager.KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL)) {
+            return false;
+        }
+
+        boolean isCapable = !mTreatAsEmergencyCall && (mConnectionState == Call.State.ACTIVE ||
+                mConnectionState == Call.State.HOLDING);
+
+        // add participant capable if current connection is a host connection or
+        // if conference is not hosted on the device
+        isCapable = isCapable && ((mOriginalConnection != null &&
+                mOriginalConnection.isConferenceHost()) ||
+                !isConferenceHosted());
+
+        /**
+          * For individual IMS calls, if the extra for remote conference support is
+          *     - indicated, then consider the same for add participant capability
+          *     - not indicated, then the add participant capability is same as before.
+          */
+        if (isCapable && (mOriginalConnection != null) && !mIsMultiParty) {
+            isCapable = mOriginalConnectionExtras.getBoolean(
+                    ImsCallProfile.EXTRA_CONFERENCE_AVAIL, isCapable);
+        }
+        return isCapable;
+    }
+
+    /**
+     * Applies the add participant capabilities to the {@code CallCapabilities} bit-mask.
+     *
+     * @param callCapabilities The {@code CallCapabilities} bit-mask.
+     * @return The capabilities with the add participant capabilities applied.
+     */
+    private int applyAddParticipantCapabilities(int callCapabilities) {
+        int currentCapabilities = callCapabilities;
+        if (isAddParticipantCapable()) {
+            currentCapabilities = changeBitmask(currentCapabilities,
+                    Connection.CAPABILITY_ADD_PARTICIPANT, true);
+        } else {
+            currentCapabilities = changeBitmask(currentCapabilities,
+                    Connection.CAPABILITY_ADD_PARTICIPANT, false);
+        }
+        return currentCapabilities;
+    }
+
     @VisibleForTesting
     public PersistableBundle getCarrierConfig() {
         Phone phone = getPhone();
@@ -1600,6 +1757,50 @@
         return false;
     }
 
+    private boolean isCallTransferSupported() {
+        PersistableBundle b = getCarrierConfig();
+        // Return false if the CarrierConfig is unavailable
+        if (b != null) {
+            return b.getBoolean(CarrierConfigManager.KEY_CARRIER_ALLOW_TRANSFER_IMS_CALL_BOOL);
+        }
+        return false;
+    }
+
+    private boolean canTransfer(TelephonyConnection c) {
+        com.android.internal.telephony.Connection connection = c.getOriginalConnection();
+        return (connection != null && !connection.isMultiparty()
+                && (c.getState() == STATE_ACTIVE || c.getState() == STATE_HOLDING));
+    }
+
+    private boolean canTransferToNumber() {
+        if (!isCallTransferSupported()) {
+            return false;
+        }
+        return canTransfer(this);
+    }
+
+    private boolean canConsultativeTransfer() {
+        if (!isCallTransferSupported()) {
+            return false;
+        }
+        if (!canTransfer(this)) {
+            return false;
+        }
+        boolean canConsultativeTransfer = false;
+        if (getTelephonyConnectionService() != null) {
+            for (Connection current : getTelephonyConnectionService().getAllConnections()) {
+                if (current != this && current instanceof TelephonyConnection) {
+                    TelephonyConnection other = (TelephonyConnection) current;
+                    if (canTransfer(other)) {
+                        canConsultativeTransfer = true;
+                        break;
+                    }
+                }
+            }
+        }
+        return canConsultativeTransfer;
+    }
+
     /**
      * Determines if the device will respect the value of the
      * {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} configuration option.
@@ -1842,6 +2043,12 @@
 
                     // Ensure extras are propagated to Telecom.
                     putTelephonyExtras(mOriginalConnectionExtras);
+                    // If extras contain Conference support information,
+                    // then ensure capabilities are updated.
+                    if (mOriginalConnectionExtras.containsKey(
+                            ImsCallProfile.EXTRA_CONFERENCE_AVAIL)) {
+                        updateConnectionCapabilities();
+                    }
                 } else {
                     Log.d(this, "Extras update not required");
                 }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 73ea98d..c5eff69 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -1706,7 +1706,7 @@
             return true;
         }
         return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean(
-                CarrierConfigManager.KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL, true);
+                CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true);
     }
 
     private boolean shouldHoldForEmergencyCall(Phone phone) {
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;
+    }
 }
diff --git a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
index cfede94..fbb270d 100644
--- a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
@@ -107,7 +107,7 @@
     }
 
     @Test
-    public void testFeatureManagerConnectedAddFeature() throws Exception {
+    public void testFeatureManagerConnectedAddRemoveFeature() throws Exception {
         RcsFeatureController controller = createFeatureController();
         // Connect the RcsFeatureManager
         mConnectorListener.getValue().connectionReady(mFeatureManager);
@@ -115,6 +115,10 @@
 
         verify(mMockFeature).onRcsConnected(mFeatureManager);
         assertEquals(mMockFeature, controller.getFeature(RcsFeatureController.Feature.class));
+
+        controller.removeFeature(RcsFeatureController.Feature.class);
+        verify(mMockFeature).onDestroy();
+        assertNull(controller.getFeature(RcsFeatureController.Feature.class));
     }
 
     @Test
diff --git a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
index 68b08a7..cfb68b7 100644
--- a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
+++ b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
@@ -21,16 +21,21 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.BroadcastReceiver;
 import android.content.Intent;
+import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.TelephonyTestBase;
+import com.android.ims.FeatureConnector;
+import com.android.ims.RcsFeatureManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -45,20 +50,31 @@
 
     @Captor ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;
     @Mock TelephonyRcsService.FeatureFactory mFeatureFactory;
-    @Mock RcsFeatureController mFeatureControllerSlot0;
-    @Mock RcsFeatureController mFeatureControllerSlot1;
     @Mock UserCapabilityExchangeImpl mMockUceSlot0;
     @Mock UserCapabilityExchangeImpl mMockUceSlot1;
+    @Mock RcsFeatureController.RegistrationHelperFactory mRegistrationFactory;
+    @Mock RcsFeatureController.FeatureConnectorFactory<RcsFeatureManager> mFeatureConnectorFactory;
+    @Mock FeatureConnector<RcsFeatureManager> mFeatureConnector;
+
+    private RcsFeatureController mFeatureControllerSlot0;
+    private RcsFeatureController mFeatureControllerSlot1;
 
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        doReturn(mFeatureConnector).when(mFeatureConnectorFactory).create(any(), anyInt(),
+                any(), any(), any());
+        mFeatureControllerSlot0 = createFeatureController(0 /*slotId*/);
+        mFeatureControllerSlot1 = createFeatureController(1 /*slotId*/);
         doReturn(mFeatureControllerSlot0).when(mFeatureFactory).createController(any(), eq(0));
         doReturn(mFeatureControllerSlot1).when(mFeatureFactory).createController(any(), eq(1));
         doReturn(mMockUceSlot0).when(mFeatureFactory).createUserCapabilityExchange(any(), eq(0),
                 anyInt());
         doReturn(mMockUceSlot1).when(mFeatureFactory).createUserCapabilityExchange(any(), eq(1),
                 anyInt());
+        //set up default slot-> sub ID mappings.
+        setSlotToSubIdMapping(0 /*slotId*/, 1/*subId*/);
+        setSlotToSubIdMapping(1 /*slotId*/, 2/*subId*/);
     }
 
     @After
@@ -67,14 +83,46 @@
     }
 
     @Test
-    public void testUserCapabilityExchangeConnected() {
+    public void testUserCapabilityExchangePresenceConnected() {
+        setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
         createRcsService(1 /*numSlots*/);
         verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
         verify(mFeatureControllerSlot0).connect();
     }
 
     @Test
+    public void testUserCapabilityExchangeOptionsConnected() {
+        setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, true /*isEnabled*/);
+        createRcsService(1 /*numSlots*/);
+        verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+        verify(mFeatureControllerSlot0).connect();
+    }
+
+    @Test
+    public void testNoFeaturesEnabled() {
+        createRcsService(1 /*numSlots*/);
+        // No carrier config set for UCE.
+        verify(mFeatureControllerSlot0, never()).addFeature(mMockUceSlot0,
+                UserCapabilityExchangeImpl.class);
+        verify(mFeatureControllerSlot0, never()).connect();
+    }
+
+    @Test
+    public void testNoFeaturesEnabledCarrierConfigChanged() {
+        createRcsService(1 /*numSlots*/);
+        // No carrier config set for UCE.
+
+        sendCarrierConfigChanged(0, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        verify(mFeatureControllerSlot0, never()).addFeature(mMockUceSlot0,
+                UserCapabilityExchangeImpl.class);
+        verify(mFeatureControllerSlot0, never()).connect();
+        verify(mFeatureControllerSlot0, never()).updateAssociatedSubscription(anyInt());
+    }
+
+
+    @Test
     public void testSlotUpdates() {
+        setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
         TelephonyRcsService service = createRcsService(1 /*numSlots*/);
         verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
         verify(mFeatureControllerSlot0).connect();
@@ -94,8 +142,7 @@
         verify(mFeatureControllerSlot0, times(1)).addFeature(mMockUceSlot0,
                 UserCapabilityExchangeImpl.class);
         verify(mFeatureControllerSlot0, times(1)).connect();
-        verify(mFeatureControllerSlot1, times(1)).addFeature(mMockUceSlot1,
-                UserCapabilityExchangeImpl.class);
+        verify(mFeatureControllerSlot1).addFeature(mMockUceSlot1, UserCapabilityExchangeImpl.class);
         verify(mFeatureControllerSlot1, times(1)).connect();
 
         // Remove a slot.
@@ -116,6 +163,7 @@
 
     @Test
     public void testCarrierConfigUpdate() {
+        setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
         createRcsService(2 /*numSlots*/);
         verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
         verify(mFeatureControllerSlot1).addFeature(mMockUceSlot1, UserCapabilityExchangeImpl.class);
@@ -132,6 +180,37 @@
         verify(mFeatureControllerSlot1, times(1)).updateAssociatedSubscription(2);
     }
 
+    @Test
+    public void testCarrierConfigUpdateUceToNoUce() {
+        setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
+        createRcsService(1 /*numSlots*/);
+        verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+        verify(mFeatureControllerSlot0).connect();
+
+
+        // Send carrier config update for each slot.
+        setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, false /*isEnabled*/);
+        sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+        verify(mFeatureControllerSlot0).removeFeature(UserCapabilityExchangeImpl.class);
+        verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
+    }
+
+    @Test
+    public void testCarrierConfigUpdateNoUceToUce() {
+        createRcsService(1 /*numSlots*/);
+        verify(mFeatureControllerSlot0, never()).addFeature(mMockUceSlot0,
+                UserCapabilityExchangeImpl.class);
+        verify(mFeatureControllerSlot0, never()).connect();
+
+
+        // Send carrier config update for each slot.
+        setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
+        sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+        verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+        verify(mFeatureControllerSlot0).connect();
+        verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
+    }
+
     private void sendCarrierConfigChanged(int slotId, int subId) {
         Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, slotId);
@@ -139,6 +218,18 @@
         mReceiverCaptor.getValue().onReceive(mContext, intent);
     }
 
+    private void setCarrierConfig(String key, boolean value) {
+        PersistableBundle bundle = mContext.getCarrierConfig();
+        bundle.putBoolean(key, value);
+    }
+
+    private void setSlotToSubIdMapping(int slotId, int loadedSubId) {
+        SubscriptionManager m = mContext.getSystemService(SubscriptionManager.class);
+        int [] subIds = new int[1];
+        subIds[0] = loadedSubId;
+        doReturn(subIds).when(m).getSubscriptionIds(eq(slotId));
+    }
+
     private TelephonyRcsService createRcsService(int numSlots) {
         TelephonyRcsService service = new TelephonyRcsService(mContext, numSlots);
         service.setFeatureFactory(mFeatureFactory);
@@ -146,4 +237,13 @@
         verify(mContext).registerReceiver(mReceiverCaptor.capture(), any());
         return service;
     }
+
+    private RcsFeatureController createFeatureController(int slotId) {
+        // Create a spy instead of a mock because TelephonyRcsService relies on state provided by
+        // RcsFeatureController.
+        RcsFeatureController controller = spy(new RcsFeatureController(mContext, slotId,
+                mRegistrationFactory));
+        controller.setFeatureConnectorFactory(mFeatureConnectorFactory);
+        return controller;
+    }
 }