Merge "MMI code: Handling MMI code on CDMA VoLTE"
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index b969ce4..997f06c 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -42,6 +42,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.service.carrier.CarrierIdentifier;
 import android.service.carrier.CarrierService;
@@ -102,8 +103,10 @@
     private CarrierServiceConnection[] mServiceConnection;
     // Whether we are bound to a service for each phone
     private boolean[] mServiceBound;
-    // Whether we have sent config change bcast for each phone id.
+    // Whether we have sent config change broadcast for each phone id.
     private boolean[] mHasSentConfigChange;
+    // Whether the broadcast was sent from EVENT_SYSTEM_UNLOCKED, to track rebroadcasts
+    private boolean[] mFromSystemUnlocked;
     // SubscriptionInfoUpdater
     private final SubscriptionInfoUpdater mSubscriptionInfoUpdater;
 
@@ -199,11 +202,13 @@
                 case EVENT_SYSTEM_UNLOCKED: {
                     for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount();
                             ++i) {
-                        // When user unlock device, we should only try to send broadcast again if we
-                        // have sent it before unlock. This will avoid we try to load carrier config
-                        // when SIM is still loading when unlock happens.
+                        // When the user unlocks the device, send the broadcast again (with a
+                        // rebroadcast extra) if we have sent it before unlock. This will avoid
+                        // trying to load the carrier config when the SIM is still loading when the
+                        // unlock happens.
                         if (mHasSentConfigChange[i]) {
                             logdWithLocalLog("System unlocked");
+                            mFromSystemUnlocked[i] = true;
                             updateConfigForPhoneId(i);
                         }
                     }
@@ -537,6 +542,7 @@
         mServiceConnection = new CarrierServiceConnection[numPhones];
         mServiceBound = new boolean[numPhones];
         mHasSentConfigChange = new boolean[numPhones];
+        mFromSystemUnlocked = new boolean[numPhones];
         // Make this service available through ServiceManager.
         ServiceManager.addService(Context.CARRIER_CONFIG_SERVICE, this);
         logd("CarrierConfigLoader has started");
@@ -634,9 +640,17 @@
             }
         }
         intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, phoneId);
-        logd("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId);
-        mContext.sendBroadcast(intent);
+        intent.putExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
+                mFromSystemUnlocked[phoneId]);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        int[] subIds = SubscriptionManager.getSubId(phoneId);
+        if (subIds != null && subIds.length > 0) {
+            logd("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId + ", subId=" + subIds[0]);
+        } else {
+            logd("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId);
+        }
         mHasSentConfigChange[phoneId] = true;
+        mFromSystemUnlocked[phoneId] = false;
     }
 
     /** Binds to the default or carrier config app. */
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 20a818e..c567806 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -87,7 +87,6 @@
     private static final int THEME = com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert;
 
     /** USSD information used to aggregate all USSD messages */
-    private static AlertDialog sUssdDialog = null;
     private static StringBuilder sUssdMsg = new StringBuilder();
 
     private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT =
@@ -524,39 +523,36 @@
         // displaying system alert dialog on the screen instead of
         // using another activity to display the message.  This
         // places the message at the forefront of the UI.
+        AlertDialog ussdDialog = new AlertDialog.Builder(context, THEME)
+                .setPositiveButton(R.string.ok, null)
+                .setCancelable(true)
+                .setOnDismissListener(new DialogInterface.OnDismissListener() {
+                    @Override
+                    public void onDismiss(DialogInterface dialog) {
+                        sUssdMsg.setLength(0);
+                    }
+                })
+                .create();
 
-        if (sUssdDialog == null) {
-            sUssdDialog = new AlertDialog.Builder(context, THEME)
-                    .setPositiveButton(R.string.ok, null)
-                    .setCancelable(true)
-                    .setOnDismissListener(new DialogInterface.OnDismissListener() {
-                        @Override
-                        public void onDismiss(DialogInterface dialog) {
-                            sUssdMsg.setLength(0);
-                        }
-                    })
-                    .create();
+        ussdDialog.getWindow().setType(windowType);
+        ussdDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
 
-            sUssdDialog.getWindow().setType(windowType);
-            sUssdDialog.getWindow().addFlags(
-                    WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-        }
         if (sUssdMsg.length() != 0) {
-            sUssdMsg
-                    .insert(0, "\n")
+            sUssdMsg.insert(0, "\n")
                     .insert(0, app.getResources().getString(R.string.ussd_dialog_sep))
                     .insert(0, "\n");
         }
         if (phone != null && phone.getCarrierName() != null) {
-            sUssdDialog.setTitle(app.getResources().getString(R.string.carrier_mmi_msg_title,
+            ussdDialog.setTitle(app.getResources().getString(R.string.carrier_mmi_msg_title,
                     phone.getCarrierName()));
         } else {
-            sUssdDialog
+            ussdDialog
                     .setTitle(app.getResources().getString(R.string.default_carrier_mmi_msg_title));
         }
         sUssdMsg.insert(0, text);
-        sUssdDialog.setMessage(sUssdMsg.toString());
-        sUssdDialog.show();
+        ussdDialog.setMessage(sUssdMsg.toString());
+        ussdDialog.show();
     }
 
     /**
diff --git a/src/com/android/services/telephony/CdmaConferenceController.java b/src/com/android/services/telephony/CdmaConferenceController.java
index 9afcd0a..8523a5f 100644
--- a/src/com/android/services/telephony/CdmaConferenceController.java
+++ b/src/com/android/services/telephony/CdmaConferenceController.java
@@ -97,7 +97,7 @@
             return;
         }
 
-        if (!mCdmaConnections.isEmpty() && connection.isOutgoing()) {
+        if (!mCdmaConnections.isEmpty() && connection.isOutgoingCall()) {
             // There already exists a connection, so this will probably result in a conference once
             // it is added. For outgoing connections which are added while another connection
             // exists, we mark them as "dialing" for a set amount of time to give the user time to
@@ -183,7 +183,7 @@
                 isNewlyCreated = true;
             }
 
-            if (newConnection.isOutgoing()) {
+            if (newConnection.isOutgoingCall()) {
                 // Only an outgoing call can be merged with an ongoing call.
                 mConference.updateCapabilities(Connection.CAPABILITY_MERGE_CONFERENCE);
             } else {
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index 36618c5..90e7663 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -81,9 +81,9 @@
             Connection connection,
             EmergencyTonePlayer emergencyTonePlayer,
             boolean allowMute,
-            boolean isOutgoing,
+            int callDirection,
             String telecomCallId) {
-        super(connection, telecomCallId, isOutgoing);
+        super(connection, telecomCallId, callDirection);
         mEmergencyTonePlayer = emergencyTonePlayer;
         mAllowMute = allowMute;
         mIsCallWaiting = connection != null && connection.getState() == Call.State.WAITING;
@@ -151,7 +151,7 @@
     @Override
     public TelephonyConnection cloneConnection() {
         CdmaConnection cdmaConnection = new CdmaConnection(getOriginalConnection(),
-                mEmergencyTonePlayer, mAllowMute, mIsOutgoing, getTelecomCallId());
+                mEmergencyTonePlayer, mAllowMute, getCallDirection(), getTelecomCallId());
         return cdmaConnection;
     }
 
@@ -202,10 +202,6 @@
         }
     }
 
-    boolean isOutgoing() {
-        return mIsOutgoing;
-    }
-
     boolean isCallWaiting() {
         return mIsCallWaiting;
     }
@@ -305,7 +301,7 @@
 
     private void handleCdmaConnectionTimeReset() {
         boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
-        if (!isImsCall && !mIsConnectionTimeReset && mIsOutgoing
+        if (!isImsCall && !mIsConnectionTimeReset && isOutgoingCall()
                 && getOriginalConnection() != null
                 && getOriginalConnection().getState() == Call.State.ACTIVE
                 && getOriginalConnection().getDurationMillis() > 0) {
diff --git a/src/com/android/services/telephony/GsmConnection.java b/src/com/android/services/telephony/GsmConnection.java
index 47434c0..769ec22 100644
--- a/src/com/android/services/telephony/GsmConnection.java
+++ b/src/com/android/services/telephony/GsmConnection.java
@@ -22,8 +22,8 @@
  * Manages a single phone call handled by GSM.
  */
 final class GsmConnection extends TelephonyConnection {
-    GsmConnection(Connection connection, String telecomCallId, boolean isOutgoing) {
-        super(connection, telecomCallId, isOutgoing);
+    GsmConnection(Connection connection, String telecomCallId, int callDirection) {
+        super(connection, telecomCallId, callDirection);
     }
 
     /**
@@ -35,9 +35,7 @@
      */
     @Override
     public TelephonyConnection cloneConnection() {
-        GsmConnection gsmConnection = new GsmConnection(getOriginalConnection(),
-                getTelecomCallId(), mIsOutgoing);
-        return gsmConnection;
+        return new GsmConnection(getOriginalConnection(), getTelecomCallId(), getCallDirection());
     }
 
     /** {@inheritDoc} */
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index c11a1ca..f7fcf64 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -20,7 +20,6 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.PersistableBundle;
 import android.telecom.Connection;
 import android.telecom.Connection.VideoProvider;
 import android.telecom.DisconnectCause;
@@ -28,7 +27,6 @@
 import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
-import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.util.Pair;
 
@@ -38,7 +36,6 @@
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 import com.android.telephony.Rlog;
@@ -79,6 +76,126 @@
     }
 
     /**
+     * Abstracts out carrier configuration items specific to the conference.
+     */
+    public static class CarrierConfiguration {
+        /**
+         * Builds and instance of {@link CarrierConfiguration}.
+         */
+        public static class Builder {
+            private boolean mIsMaximumConferenceSizeEnforced = false;
+            private int mMaximumConferenceSize = 5;
+            private boolean mShouldLocalDisconnectEmptyConference = false;
+            private boolean mIsHoldAllowed = false;
+
+            /**
+             * Sets whether the maximum size of the conference is enforced.
+             * @param isMaximumConferenceSizeEnforced {@code true} if conference size enforced.
+             * @return builder instance.
+             */
+            public Builder setIsMaximumConferenceSizeEnforced(
+                    boolean isMaximumConferenceSizeEnforced) {
+                mIsMaximumConferenceSizeEnforced = isMaximumConferenceSizeEnforced;
+                return this;
+            }
+
+            /**
+             * Sets the maximum size of an IMS conference.
+             * @param maximumConferenceSize Max conference size.
+             * @return builder instance.
+             */
+            public Builder setMaximumConferenceSize(int maximumConferenceSize) {
+                mMaximumConferenceSize = maximumConferenceSize;
+                return this;
+            }
+
+            /**
+             * Sets whether an empty conference should be locally disconnected.
+             * @param shouldLocalDisconnectEmptyConference {@code true} if conference should be
+             * locally disconnected if empty.
+             * @return builder instance.
+             */
+            public Builder setShouldLocalDisconnectEmptyConference(
+                    boolean shouldLocalDisconnectEmptyConference) {
+                mShouldLocalDisconnectEmptyConference = shouldLocalDisconnectEmptyConference;
+                return this;
+            }
+
+            /**
+             * Sets whether holding the conference is allowed.
+             * @param isHoldAllowed {@code true} if holding is allowed.
+             * @return builder instance.
+             */
+            public Builder setIsHoldAllowed(boolean isHoldAllowed) {
+                mIsHoldAllowed = isHoldAllowed;
+                return this;
+            }
+
+            /**
+             * Build instance of {@link CarrierConfiguration}.
+             * @return carrier config instance.
+             */
+            public ImsConference.CarrierConfiguration build() {
+                return new ImsConference.CarrierConfiguration(mIsMaximumConferenceSizeEnforced,
+                        mMaximumConferenceSize, mShouldLocalDisconnectEmptyConference,
+                        mIsHoldAllowed);
+            }
+        }
+
+        private CarrierConfiguration(boolean isMaximumConferenceSizeEnforced,
+                int maximumConferenceSize, boolean shouldLocalDisconnectEmptyConference,
+                boolean isHoldAllowed) {
+            mIsMaximumConferenceSizeEnforced = isMaximumConferenceSizeEnforced;
+            mMaximumConferenceSize = maximumConferenceSize;
+            mShouldLocalDisconnectEmptyConference = shouldLocalDisconnectEmptyConference;
+            mIsHoldAllowed = isHoldAllowed;
+        }
+
+        private boolean mIsMaximumConferenceSizeEnforced;
+
+        private int mMaximumConferenceSize;
+
+        private boolean mShouldLocalDisconnectEmptyConference;
+
+        private boolean mIsHoldAllowed;
+
+        /**
+         * Determines whether the {@link ImsConference} should enforce a size limit based on
+         * {@link #getMaximumConferenceSize()}.
+         * {@code true} if maximum size limit should be enforced, {@code false} otherwise.
+         */
+        public boolean isMaximumConferenceSizeEnforced() {
+            return mIsMaximumConferenceSizeEnforced;
+        }
+
+        /**
+         * Determines the maximum number of participants (not including the host) in a conference
+         * which is enforced when {@link #isMaximumConferenceSizeEnforced()} is {@code true}.
+         */
+        public int getMaximumConferenceSize() {
+            return mMaximumConferenceSize;
+        }
+
+        /**
+         * Determines whether this {@link ImsConference} should locally disconnect itself when the
+         * number of participants in the conference drops to zero.
+         * {@code true} if empty conference should be locally disconnected, {@code false}
+         * otherwise.
+         */
+        public boolean shouldLocalDisconnectEmptyConference() {
+            return mShouldLocalDisconnectEmptyConference;
+        }
+
+        /**
+         * Determines whether holding the conference is permitted or not.
+         * {@code true} if hold is permitted, {@code false} otherwise.
+         */
+        public boolean isHoldAllowed() {
+            return mIsHoldAllowed;
+        }
+    }
+
+    /**
      * Listener used to respond to changes to the underlying radio connection for the conference
      * host connection.  Used to respond to SRVCC changes.
      */
@@ -260,6 +377,7 @@
     private boolean mIsHoldable;
     private boolean mCouldManageConference;
     private FeatureFlagProxy mFeatureFlagProxy;
+    private final CarrierConfiguration mCarrierConfig;
     private boolean mIsEmulatingSinglePartyCall = false;
     private boolean mIsUsingSimCallManager = false;
 
@@ -301,12 +419,13 @@
     public ImsConference(TelecomAccountRegistry telecomAccountRegistry,
             TelephonyConnectionServiceProxy telephonyConnectionService,
             TelephonyConnection conferenceHost, PhoneAccountHandle phoneAccountHandle,
-            FeatureFlagProxy featureFlagProxy) {
+            FeatureFlagProxy featureFlagProxy, CarrierConfiguration carrierConfig) {
 
         super(phoneAccountHandle);
 
         mTelecomAccountRegistry = telecomAccountRegistry;
         mFeatureFlagProxy = featureFlagProxy;
+        mCarrierConfig = carrierConfig;
 
         // Specify the connection time of the conference to be the connection time of the original
         // connection.
@@ -323,7 +442,7 @@
 
         int capabilities = Connection.CAPABILITY_MUTE |
                 Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
-        if (canHoldImsCalls()) {
+        if (mCarrierConfig.isHoldAllowed()) {
             capabilities |= Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD;
             mIsHoldable = true;
         }
@@ -491,6 +610,8 @@
             } catch (CallStateException e) {
                 Log.e(this, e, "Exception thrown trying to hangup conference");
             }
+        } else {
+            Log.w(this, "onDisconnect - null call");
         }
     }
 
@@ -935,6 +1056,14 @@
             if (newParticipantsAdded || oldParticipantsRemoved) {
                 updateManageConference();
             }
+
+            // If the conference is empty and we're supposed to do a local disconnect, do so now.
+            if (mCarrierConfig.shouldLocalDisconnectEmptyConference()
+                    && oldParticipantCount > 0 && newParticipantCount == 0) {
+                Log.i(this, "handleConferenceParticipantsUpdate: empty conference; "
+                        + "local disconnect.");
+                onDisconnect();
+            }
         }
     }
 
@@ -1021,6 +1150,7 @@
             setConnectionStartElapsedRealtimeMillis(
                     entry.getConnectionStartElapsedRealtimeMillis());
             setConnectionTime(entry.getConnectTimeMillis());
+            setCallDirection(entry.getCallDirection());
             mLoneParticipantIdentity = new Pair<>(entry.getUserEntity(), entry.getEndpoint());
 
             // Remove the participant from Telecom.  It'll get picked up in a future CEP update
@@ -1225,7 +1355,7 @@
             if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
                 Log.i(this,"handleOriginalConnectionChange : SRVCC to GSM");
                 GsmConnection c = new GsmConnection(originalConnection, getTelecomCallId(),
-                        mConferenceHost.isOutgoingCall());
+                        mConferenceHost.getCallDirection());
                 // This is a newly created conference connection as a result of SRVCC
                 c.setConferenceSupported(true);
                 c.setTelephonyConnectionProperties(
@@ -1346,51 +1476,6 @@
         return sb.toString();
     }
 
-    private boolean canHoldImsCalls() {
-        PersistableBundle b = getCarrierConfig();
-        // Return true if the CarrierConfig is unavailable
-        return b == null || b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL);
-    }
-
-    private PersistableBundle getCarrierConfig() {
-        if (mConferenceHost == null) {
-            return null;
-        }
-
-        Phone phone = mConferenceHost.getPhone();
-        if (phone == null) {
-            return null;
-        }
-        return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
-    }
-
-    /**
-     * @return {@code true} if the carrier associated with the conference requires that the maximum
-     *      size of the conference is enforced, {@code false} otherwise.
-     */
-    public boolean isMaximumConferenceSizeEnforced() {
-        PersistableBundle b = getCarrierConfig();
-        // Return false if the CarrierConfig is unavailable
-        return b != null && b.getBoolean(
-                CarrierConfigManager.KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL);
-    }
-
-    /**
-     * @return The maximum size of a conference call where
-     * {@link #isMaximumConferenceSizeEnforced()} is true.
-     */
-    public int getMaximumConferenceSize() {
-        PersistableBundle b = getCarrierConfig();
-
-        // If there is no carrier config its really a problem, but we'll still define a sane limit
-        // of 5 so that we can still make a conference.
-        if (b == null) {
-            Log.w(this, "getMaximumConferenceSize - failed to get conference size");
-            return 5;
-        }
-        return b.getInt(CarrierConfigManager.KEY_IMS_CONFERENCE_SIZE_LIMIT_INT);
-    }
-
     /**
      * @return The number of participants in the conference.
      */
@@ -1403,8 +1488,8 @@
      *      participants in the conference has reached the limit, {@code false} otherwise.
      */
     public boolean isFullConference() {
-        return isMaximumConferenceSizeEnforced()
-                && getNumberOfParticipants() >= getMaximumConferenceSize();
+        return mCarrierConfig.isMaximumConferenceSizeEnforced()
+                && getNumberOfParticipants() >= mCarrierConfig.getMaximumConferenceSize();
     }
 
     /**
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 8789ba8..6d3d4c2 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -16,12 +16,16 @@
 
 package com.android.services.telephony;
 
+import android.content.Context;
+import android.os.PersistableBundle;
 import android.telecom.Conference;
 import android.telecom.Conferenceable;
 import android.telecom.Connection;
 import android.telecom.ConnectionService;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
+
 import com.android.telephony.Rlog;
 
 import com.android.internal.telephony.Phone;
@@ -403,6 +407,7 @@
         PhoneAccountHandle phoneAccountHandle = null;
 
         // Attempt to determine the phone account associated with the conference host connection.
+        ImsConference.CarrierConfiguration carrierConfig = null;
         if (connection.getPhone() != null &&
                 connection.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
             Phone imsPhone = connection.getPhone();
@@ -410,11 +415,13 @@
             // base GSM or CDMA phone, not on the ImsPhone itself).
             phoneAccountHandle =
                     PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
+            carrierConfig = getCarrierConfig(imsPhone);
         }
 
         ImsConference conference = new ImsConference(mTelecomAccountRegistry, mConnectionService,
-                conferenceHostConnection, phoneAccountHandle, mFeatureFlagProxy);
+                conferenceHostConnection, phoneAccountHandle, mFeatureFlagProxy, carrierConfig);
         conference.setState(conferenceHostConnection.getState());
+        conference.setCallDirection(conferenceHostConnection.getCallDirection());
         conference.addTelephonyConferenceListener(mConferenceListener);
         conference.updateConferenceParticipantsAfterCreation();
         mConnectionService.addConference(conference);
@@ -433,4 +440,33 @@
         // conferenceable connections for the conference to show merge calls option.
         recalculateConferenceable();
     }
+
+    public static ImsConference.CarrierConfiguration getCarrierConfig(Phone phone) {
+        ImsConference.CarrierConfiguration.Builder config =
+                new ImsConference.CarrierConfiguration.Builder();
+        if (phone == null) {
+            return config.build();
+        }
+
+        CarrierConfigManager cfgManager = (CarrierConfigManager)
+                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (cfgManager != null) {
+            PersistableBundle bundle = cfgManager.getConfigForSubId(phone.getSubId());
+            boolean isMaximumConferenceSizeEnforced = bundle.getBoolean(
+                    CarrierConfigManager.KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL);
+            int maximumConferenceSize = bundle.getInt(
+                    CarrierConfigManager.KEY_IMS_CONFERENCE_SIZE_LIMIT_INT);
+            boolean isHoldAllowed = bundle.getBoolean(
+                    CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL);
+            boolean shouldLocalDisconnectOnEmptyConference = bundle.getBoolean(
+                    CarrierConfigManager.KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL);
+
+            config.setIsMaximumConferenceSizeEnforced(isMaximumConferenceSizeEnforced)
+                    .setMaximumConferenceSize(maximumConferenceSize)
+                    .setIsHoldAllowed(isHoldAllowed)
+                    .setShouldLocalDisconnectEmptyConference(
+                            shouldLocalDisconnectOnEmptyConference);
+        }
+        return config.build();
+    }
 }
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 9e880a5..c38923f 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -32,6 +32,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.UserHandle;
@@ -57,6 +58,7 @@
 import android.text.TextUtils;
 
 import com.android.ims.ImsManager;
+import com.android.internal.telephony.ExponentialBackoff;
 import com.android.internal.telephony.LocaleTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
@@ -85,6 +87,26 @@
     private final static int DEFAULT_SIM_ICON =  R.drawable.ic_multi_sim;
     private final static String GROUP_PREFIX = "group_";
 
+    private static final int REGISTER_START_DELAY_MS = 1 * 1000; // 1 second
+    private static final int REGISTER_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute
+
+    /**
+     * Indicates the {@link SubscriptionManager.OnSubscriptionsChangedListener} has not yet been
+     * registered.
+     */
+    private static final int LISTENER_STATE_UNREGISTERED = 0;
+
+    /**
+     * Indicates the first {@link SubscriptionManager.OnSubscriptionsChangedListener} registration
+     * attempt failed and we are performing backoff registration.
+     */
+    private static final int LISTENER_STATE_PERFORMING_BACKOFF = 2;
+
+    /**
+     * Indicates the {@link SubscriptionManager.OnSubscriptionsChangedListener} has been registered.
+     */
+    private static final int LISTENER_STATE_REGISTERED = 3;
+
     final class AccountEntry implements PstnPhoneCapabilitiesNotifier.Listener {
         private final Phone mPhone;
         private PhoneAccount mAccount;
@@ -952,18 +974,46 @@
             new OnSubscriptionsChangedListener() {
         @Override
         public void onSubscriptionsChanged() {
-            // Any time the SubscriptionInfo changes...rerun the setup
-            Log.i(this, "onSubscriptionsChanged - update accounts");
+            if (mSubscriptionListenerState != LISTENER_STATE_REGISTERED) {
+                mRegisterSubscriptionListenerBackoff.stop();
+                mHandlerThread.quitSafely();
+            }
+            mSubscriptionListenerState = LISTENER_STATE_REGISTERED;
+
+            // Any time the SubscriptionInfo changes rerun the setup
+            Log.i(this, "TelecomAccountRegistry: onSubscriptionsChanged - update accounts");
             tearDownAccounts();
             setupAccounts();
         }
+
+        @Override
+        public void onAddListenerFailed() {
+            // Woe!  Failed to add the listener!
+            Log.w(this, "TelecomAccountRegistry: onAddListenerFailed - failed to register "
+                    + "OnSubscriptionsChangedListener");
+
+            // Even though registering the listener failed, we will still try to setup the phone
+            // accounts now; the phone instances should already be present and ready, so even if
+            // telephony registry is poking along we can still try to setup the phone account.
+            tearDownAccounts();
+            setupAccounts();
+
+            if (mSubscriptionListenerState == LISTENER_STATE_UNREGISTERED) {
+                // Initial registration attempt failed; start exponential backoff.
+                mSubscriptionListenerState = LISTENER_STATE_PERFORMING_BACKOFF;
+                mRegisterSubscriptionListenerBackoff.start();
+            } else {
+                // We're already doing exponential backoff and a registration failed.
+                mRegisterSubscriptionListenerBackoff.notifyFailed();
+            }
+        }
     };
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                Log.i(this, "User changed, re-registering phone accounts.");
+                Log.i(this, "TelecomAccountRegistry: User changed, re-registering phone accounts.");
 
                 int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                 UserHandle currentUserHandle = UserHandle.of(userHandleId);
@@ -1027,20 +1077,42 @@
     private final SubscriptionManager mSubscriptionManager;
     private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
     private final Object mAccountsLock = new Object();
+    private int mSubscriptionListenerState = LISTENER_STATE_UNREGISTERED;
     private int mServiceState = ServiceState.STATE_POWER_OFF;
     private int mActiveDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private boolean mIsPrimaryUser = true;
+    private ExponentialBackoff mRegisterSubscriptionListenerBackoff;
+    private final HandlerThread mHandlerThread = new HandlerThread("TelecomAccountRegistry");
 
     // TODO: Remove back-pointer from app singleton to Service, since this is not a preferred
     // pattern; redesign. This was added to fix a late release bug.
     private TelephonyConnectionService mTelephonyConnectionService;
 
+    // Used to register subscription changed listener when initial attempts fail.
+    private Runnable mRegisterOnSubscriptionsChangedListenerRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mSubscriptionListenerState != LISTENER_STATE_REGISTERED) {
+                Log.i(this, "TelecomAccountRegistry: performing delayed register.");
+                SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
+                        mOnSubscriptionsChangedListener);
+            }
+        }
+    };
+
     TelecomAccountRegistry(Context context) {
         mContext = context;
         mTelecomManager = context.getSystemService(TelecomManager.class);
         mImsManager = context.getSystemService(android.telephony.ims.ImsManager.class);
         mTelephonyManager = TelephonyManager.from(context);
         mSubscriptionManager = SubscriptionManager.from(context);
+        mHandlerThread.start();
+        mRegisterSubscriptionListenerBackoff = new ExponentialBackoff(
+                REGISTER_START_DELAY_MS,
+                REGISTER_MAXIMUM_DELAY_MS,
+                2, /* multiplier */
+                mHandlerThread.getLooper(),
+                mRegisterOnSubscriptionsChangedListenerRunnable);
     }
 
     /**
@@ -1256,6 +1328,7 @@
 
         // Register for SubscriptionInfo list changes which is guaranteed
         // to invoke onSubscriptionsChanged the first time.
+        Log.i(this, "TelecomAccountRegistry: setupOnBoot - register subscription listener");
         SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
                 mOnSubscriptionsChangedListener);
 
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index d5ed409..19afe24 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -772,11 +772,6 @@
     private boolean mIsCdmaVoicePrivacyEnabled;
 
     /**
-     * Indicates whether this call is an outgoing call.
-     */
-    protected final boolean mIsOutgoing;
-
-    /**
      * Indicates whether the connection can be held. This filed combined with the state of the
      * connection can determine whether {@link Connection#CAPABILITY_HOLD} should be added to the
      * connection.
@@ -810,8 +805,8 @@
             new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
 
     protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
-            String callId, boolean isOutgoingCall) {
-        mIsOutgoing = isOutgoingCall;
+            String callId, @android.telecom.Call.Details.CallDirection int callDirection) {
+        setCallDirection(callDirection);
         setTelecomCallId(callId);
         if (originalConnection != null) {
             setOriginalConnection(originalConnection);
@@ -2434,8 +2429,8 @@
     /**
      * @return {@code true} if this is an outgoing call, {@code false} otherwise.
      */
-    boolean isOutgoingCall() {
-        return mIsOutgoing;
+    public boolean isOutgoingCall() {
+        return getCallDirection() == android.telecom.Call.Details.DIRECTION_OUTGOING;
     }
 
     /**
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index a703cb6..bd10aea 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -585,9 +585,11 @@
         }
 
         TelephonyConnection connection = (TelephonyConnection)conn;
+
         ImsConference conference = new ImsConference(TelecomAccountRegistry.getInstance(this),
                 mTelephonyConnectionServiceProxy, connection,
-                phoneAccountHandle, () -> true);
+                phoneAccountHandle, () -> true,
+                ImsConferenceController.getCarrierConfig(connection.getPhone()));
         mImsConferenceController.addConference(conference);
         conference.setVideoState(connection,
                 connection.getVideoState());
@@ -1821,12 +1823,14 @@
             boolean isAdhocConference) {
         TelephonyConnection returnConnection = null;
         int phoneType = phone.getPhoneType();
+        int callDirection = isOutgoing ? android.telecom.Call.Details.DIRECTION_OUTGOING
+                : android.telecom.Call.Details.DIRECTION_INCOMING;
         if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
-            returnConnection = new GsmConnection(originalConnection, telecomCallId, isOutgoing);
+            returnConnection = new GsmConnection(originalConnection, telecomCallId, callDirection);
         } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
             boolean allowsMute = allowsMute(phone);
             returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer,
-                    allowsMute, isOutgoing, telecomCallId);
+                    allowsMute, callDirection, telecomCallId);
         }
         if (returnConnection != null) {
             if (!isAdhocConference) {
diff --git a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
index 0606d32..9b0a303 100644
--- a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
+++ b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
@@ -643,13 +643,22 @@
         // Unregister IMS callback
         ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
         if (imsMmtelManager != null) {
-            imsMmtelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
-            imsMmtelManager.unregisterMmTelCapabilityCallback(mCapabilityCallback);
+            try {
+                imsMmtelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
+                imsMmtelManager.unregisterMmTelCapabilityCallback(mCapabilityCallback);
+            } catch (RuntimeException e) {
+                logw("unregister IMS callback error: " + e.getMessage());
+            }
         }
+
         // Unregister provision changed callback
         ProvisioningManager provisioningManager =
                 ProvisioningManager.createForSubscriptionId(subId);
-        provisioningManager.unregisterProvisioningChangedCallback(mProvisioningChangedCallback);
+        try {
+            provisioningManager.unregisterProvisioningChangedCallback(mProvisioningChangedCallback);
+        } catch (RuntimeException e) {
+            logw("unregister provisioning callback error: " + e.getMessage());
+        }
 
         // Remove all publish state callbacks
         clearPublishStateCallbacks();
@@ -718,10 +727,17 @@
             if (mSubId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                 return;
             }
+
             ImsMmTelManager ims = getImsMmTelManager(mSubId);
-            boolean isEnabled = ims.isVtSettingEnabled();
-            logi("SimInfo changed: VT setting=" + isEnabled);
-            mPresencePublication.onVtEnabled(isEnabled);
+            if (ims == null) return;
+
+            try {
+                boolean isEnabled = ims.isVtSettingEnabled();
+                logi("SimInfo changed: VT setting=" + isEnabled);
+                mPresencePublication.onVtEnabled(isEnabled);
+            } catch (RuntimeException e) {
+                logw("SimInfo changed error: " + e);
+            }
         }
     };
 
@@ -790,6 +806,7 @@
                     Context.TELEPHONY_IMS_SERVICE);
             return (imsManager == null) ? null : imsManager.getImsMmTelManager(subId);
         } catch (IllegalArgumentException e) {
+            logw("getImsMmTelManager error: " + e.getMessage());
             return null;
         }
     }
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 7251402..b84aca1 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -43,6 +43,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
+import java.util.Collections;
 
 public class ImsConferenceTest {
     @Mock
@@ -73,14 +74,15 @@
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
 
         ConferenceParticipant participant1 = new ConferenceParticipant(
                 Uri.parse("tel:6505551212"),
                 "A",
                 Uri.parse("sip:6505551212@testims.com"),
                 Connection.STATE_ACTIVE,
-                Call.Details.DIRECTION_INCOMING);
+                Call.Details.DIRECTION_OUTGOING);
         ConferenceParticipant participant2 = new ConferenceParticipant(
                 Uri.parse("tel:6505551213"),
                 "A",
@@ -98,6 +100,8 @@
         imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
                 Arrays.asList(participant1));
         assertEquals(0, imsConference.getNumberOfParticipants());
+        // Ensure the call direction is set correctly during emulation
+        assertEquals(Call.Details.DIRECTION_OUTGOING, imsConference.getCallDirection());
         reset(mMockTelephonyConnectionServiceProxy);
 
         // Back to 2!
@@ -120,7 +124,8 @@
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
 
         // Start off with 3 participants.
         ConferenceParticipant participant1 = new ConferenceParticipant(
@@ -183,7 +188,8 @@
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
 
         // Start off with 3 participants.
         ConferenceParticipant participant1 = new ConferenceParticipant(
@@ -240,7 +246,8 @@
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
 
         // Setup the initial conference state with 2 participants.
         ConferenceParticipant participant1 = new ConferenceParticipant(
@@ -298,7 +305,8 @@
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
 
         final boolean[] isConferenceState = new boolean[1];
         TelephonyConferenceBase.TelephonyConferenceListener conferenceListener =
@@ -354,7 +362,8 @@
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
 
         ConferenceParticipant participant1 = new ConferenceParticipant(
                 Uri.parse("tel:6505551212"),
@@ -379,7 +388,8 @@
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
 
         ConferenceParticipant participant1 = new ConferenceParticipant(
                 Uri.parse("tel:6505551212"),
@@ -410,13 +420,14 @@
 
     @Test
     @SmallTest
-    public void testNormalConference() {
+    public void testNormalConference() throws Exception {
         when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
                 .thenReturn(false);
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> false /* featureFlagProxy */);
+                null /* phoneAccountHandle */, () -> false /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
 
         ConferenceParticipant participant1 = new ConferenceParticipant(
                 Uri.parse("tel:6505551212"),
@@ -438,5 +449,45 @@
         imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
                 Arrays.asList(participant1));
         assertEquals(1, imsConference.getNumberOfParticipants());
+
+        // Drop to 0 participants; should not hangup the conf now
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost, Collections.emptyList());
+        assertEquals(0, imsConference.getNumberOfParticipants());
+        verify(mConferenceHost.mMockCall, never()).hangup();
+    }
+
+    @Test
+    @SmallTest
+    public void testLocalDisconnectOnEmptyConference() throws Exception {
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+                .thenReturn(false);
+
+        ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+                mMockTelephonyConnectionServiceProxy, mConferenceHost,
+                null /* phoneAccountHandle */, () -> false /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder()
+                        .setShouldLocalDisconnectEmptyConference(true)
+                        .build());
+
+        ConferenceParticipant participant1 = new ConferenceParticipant(
+                Uri.parse("tel:6505551212"),
+                "A",
+                Uri.parse("sip:6505551212@testims.com"),
+                Connection.STATE_ACTIVE,
+                Call.Details.DIRECTION_INCOMING);
+        ConferenceParticipant participant2 = new ConferenceParticipant(
+                Uri.parse("tel:6505551213"),
+                "A",
+                Uri.parse("sip:6505551213@testims.com"),
+                Connection.STATE_ACTIVE,
+                Call.Details.DIRECTION_INCOMING);
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1, participant2));
+        assertEquals(2, imsConference.getNumberOfParticipants());
+
+        // Drop to 0 participants; should have a hangup request.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost, Collections.emptyList());
+        assertEquals(0, imsConference.getNumberOfParticipants());
+        verify(mConferenceHost.mMockCall).hangup();
     }
 }
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index 5b31c0f..c36c405 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.when;
 
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
@@ -72,8 +73,13 @@
         return mMockRadioConnection;
     }
 
+    @Override
+    protected Call getCall() {
+        return mMockCall;
+    }
+
     public TestTelephonyConnection() {
-        super(null, null, false);
+        super(null, null, android.telecom.Call.Details.DIRECTION_INCOMING);
         MockitoAnnotations.initMocks(this);
 
         mMockPhone = mock(Phone.class);
@@ -100,6 +106,11 @@
         when(mMockPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
         when(mMockCall.getState()).thenReturn(Call.State.ACTIVE);
         when(mMockCall.getPhone()).thenReturn(mMockPhone);
+        try {
+            doNothing().when(mMockCall).hangup();
+        } catch (CallStateException e) {
+            e.printStackTrace();
+        }
     }
 
     @Override