Merge "Modify tests in accordance with API changes"
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 6f3121b..8683406 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -280,7 +280,6 @@
 
                 sNotificationChannelController = new NotificationChannelController(context);
 
-                sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];
                 for (int i = 0; i < numPhones; i++) {
                     sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
                             sSubscriptionMonitor, Looper.myLooper(), sPhones[i]);
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 7e8bd80..839210f 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -2385,21 +2385,16 @@
              * is set to roaming when either is true.
              *
              * There are exceptions for the above rule.
-             * The new SS is not set as roaming while gsm service reports
-             * roaming but indeed it is same operator.
-             * And the operator is considered non roaming.
+             * The new SS is not set as roaming while gsm service or
+             * data service reports roaming but indeed it is same
+             * operator. And the operator is considered non roaming.
              *
              * The test for the operators is to handle special roaming
              * agreements and MVNO's.
              */
             boolean roaming = (mGsmRoaming || mDataRoaming);
 
-            // for IWLAN case, data is home. Only check voice roaming.
-            if (mNewSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
-                roaming = mGsmRoaming;
-            }
-
-            if (mGsmRoaming && !isOperatorConsideredRoaming(mNewSS)
+            if (roaming && !isOperatorConsideredRoaming(mNewSS)
                     && (isSameNamedOperators(mNewSS) || isOperatorConsideredNonRoaming(mNewSS))) {
                 log("updateRoamingState: resource override set non roaming.isSameNamedOperators="
                         + isSameNamedOperators(mNewSS) + ",isOperatorConsideredNonRoaming="
@@ -3106,6 +3101,11 @@
             }
         }
 
+        // Filter out per transport data RAT changes, only want to track changes based on
+        // transport preference changes (WWAN to WLAN, for example).
+        boolean hasDataTransportPreferenceChanged = !anyDataRatChanged
+                && (mSS.getRilDataRadioTechnology() != mNewSS.getRilDataRadioTechnology());
+
         boolean hasVoiceRegStateChanged =
                 mSS.getVoiceRegState() != mNewSS.getVoiceRegState();
 
@@ -3185,6 +3185,7 @@
                     + " hasDataRegStateChanged = " + hasDataRegStateChanged
                     + " hasRilVoiceRadioTechnologyChanged = " + hasRilVoiceRadioTechnologyChanged
                     + " hasRilDataRadioTechnologyChanged = " + hasRilDataRadioTechnologyChanged
+                    + " hasDataTransportPreferenceChanged = " + hasDataTransportPreferenceChanged
                     + " hasChanged = " + hasChanged
                     + " hasVoiceRoamingOn = " + hasVoiceRoamingOn
                     + " hasVoiceRoamingOff = " + hasVoiceRoamingOff
@@ -3360,7 +3361,10 @@
             }
 
             if (hasDataRegStateChanged.get(transport)
-                    || hasRilDataRadioTechnologyChanged.get(transport)) {
+                    || hasRilDataRadioTechnologyChanged.get(transport)
+                    // Update all transports if preference changed so that consumers can be notified
+                    // that ServiceState#getRilDataRadioTechnology has changed.
+                    || hasDataTransportPreferenceChanged) {
                 notifyDataRegStateRilRadioTechnologyChanged(transport);
                 mPhone.notifyDataConnection();
             }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 7606073..78f7779 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -22,6 +22,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
@@ -38,11 +39,13 @@
 import android.net.StringNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Telephony;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DataFailCause;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.Rlog;
@@ -50,11 +53,13 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.ApnSetting.ApnType;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
 import android.telephony.data.DataServiceCallback;
 import android.text.TextUtils;
+import android.util.LocalLog;
 import android.util.Pair;
 import android.util.StatsLog;
 import android.util.TimeUtils;
@@ -174,6 +179,8 @@
 
     private final String mTagSuffix;
 
+    private final LocalLog mHandoverLocalLog = new LocalLog(100);
+
     /**
      * Used internally for saving connecting parameters.
      */
@@ -680,6 +687,7 @@
             linkProperties = dc.getLinkProperties();
             // Preserve the potential network agent from the source data connection. The ownership
             // is not transferred at this moment.
+            mHandoverLocalLog.log("Handover started. Preserved the agent.");
             mHandoverSourceNetworkAgent = dc.getNetworkAgent();
             log("Get the handover source network agent: " + mHandoverSourceNetworkAgent);
             dc.setHandoverState(HANDOVER_STATE_BEING_TRANSFERRED);
@@ -1131,14 +1139,28 @@
      * @return True if this data connection should only be used for unmetered purposes.
      */
     private boolean isUnmeteredUseOnly() {
-        // The data connection can only be unmetered used only if all requests' reasons are
-        // unmetered.
+        // If this data connection is on IWLAN, then it's unmetered and can be used by everyone.
+        // Should not be for unmetered used only.
+        if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+            return false;
+        }
+
+        // If data is enabled, this data connection can't be for unmetered used only because
+        // everyone should be able to use it.
+        if (mPhone.getDataEnabledSettings().isDataEnabled()) {
+            return false;
+        }
+
+        // If the device is roaming and data roaming it turned on, then this data connection can't
+        // be for unmetered use only.
+        if (mDct.getDataRoamingEnabled() && mPhone.getServiceState().getDataRoaming()) {
+            return false;
+        }
+
+        // The data connection can only be unmetered used only if all attached APN contexts
+        // attached to this data connection are unmetered.
         for (ApnContext apnContext : mApnContexts.keySet()) {
-            DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
-            boolean isDataAllowed = mDct.isDataAllowed(apnContext, DcTracker.REQUEST_TYPE_NORMAL,
-                    dataConnectionReasons);
-            if (!isDataAllowed || !dataConnectionReasons.contains(
-                    DataConnectionReasons.DataAllowedReasonType.UNMETERED_APN)) {
+            if (ApnSettingUtils.isMeteredApnType(apnContext.getApnTypeBitmask(), mPhone)) {
                 return false;
             }
         }
@@ -1667,6 +1689,31 @@
                 mHandoverState = HANDOVER_STATE_COMPLETED;
             }
 
+            // Check for dangling agent. Ideally the handover source agent should be null if
+            // handover process is smooth. When it's not null, that means handover failed. The
+            // agent was not successfully transferred to the new data connection. We should
+            // gracefully notify connectivity service the network was disconnected.
+            if (mHandoverSourceNetworkAgent != null) {
+                DataConnection sourceDc = mHandoverSourceNetworkAgent.getDataConnection();
+                if (sourceDc != null) {
+                    // If the source data connection still owns this agent, then just reset the
+                    // handover state back to idle because handover is already failed.
+                    mHandoverLocalLog.log(
+                            "Handover failed. Reset the source dc state to idle");
+                    sourceDc.setHandoverState(HANDOVER_STATE_IDLE);
+                } else {
+                    // The agent is now a dangling agent. No data connection owns this agent.
+                    // Gracefully notify connectivity service disconnected.
+                    mHandoverLocalLog.log(
+                            "Handover failed and dangling agent found.");
+                    mHandoverSourceNetworkAgent.acquireOwnership(
+                            DataConnection.this, mTransportType);
+                    mHandoverSourceNetworkAgent.sendNetworkInfo(mNetworkInfo, DataConnection.this);
+                    mHandoverSourceNetworkAgent.releaseOwnership(DataConnection.this);
+                }
+                mHandoverSourceNetworkAgent = null;
+            }
+
             if (mConnectionParams != null) {
                 if (DBG) {
                     log("DcInactiveState: enter notifyConnectCompleted +ALL failCause="
@@ -1941,15 +1988,25 @@
                 }
 
                 if (mHandoverSourceNetworkAgent != null) {
-                    log("Transfer network agent successfully.");
+                    String logStr = "Transfer network agent successfully.";
+                    log(logStr);
+                    mHandoverLocalLog.log(logStr);
                     mNetworkAgent = mHandoverSourceNetworkAgent;
                     mNetworkAgent.acquireOwnership(DataConnection.this, mTransportType);
+
+                    // TODO: Should evaluate mDisabledApnTypeBitMask again after handover. We don't
+                    // do it now because connectivity service does not support dynamically removing
+                    // immutable capabilities.
+
+                    // Update the capability after handover
                     mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
                             DataConnection.this);
                     mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
                     mHandoverSourceNetworkAgent = null;
                 } else {
-                    loge("Failed to get network agent from original data connection");
+                    String logStr = "Failed to get network agent from original data connection";
+                    loge(logStr);
+                    mHandoverLocalLog.log(logStr);
                     return;
                 }
             } else {
@@ -1958,6 +2015,9 @@
                         mPhone.getPhoneId());
                 final int factorySerialNumber = (null == factory)
                         ? NetworkFactory.SerialNumber.NONE : factory.getSerialNumber();
+
+                mDisabledApnTypeBitMask |= getDisallowedApnTypes();
+
                 mNetworkAgent = DcNetworkAgent.createDcNetworkAgent(DataConnection.this,
                         mPhone, mNetworkInfo, mScore, misc, factorySerialNumber, mTransportType);
             }
@@ -2562,6 +2622,8 @@
     }
 
     void setHandoverState(@HandoverState int state) {
+        mHandoverLocalLog.log("State changed from " + handoverStateToString(mHandoverState)
+                + " to " + handoverStateToString(state));
         mHandoverState = state;
     }
 
@@ -2713,6 +2775,33 @@
                 == NetworkRegistrationInfo.NR_STATE_CONNECTED;
     }
 
+    /**
+     * @return The disallowed APN types bitmask
+     */
+    private @ApnType int getDisallowedApnTypes() {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        int apnTypesBitmask = 0;
+        if (configManager != null) {
+            PersistableBundle bundle = configManager.getConfigForSubId(mSubId);
+            if (bundle != null) {
+                String key = (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        ? CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY
+                        : CarrierConfigManager.KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY;
+                if (bundle.getStringArray(key) != null) {
+                    String disallowedApnTypesString =
+                            TextUtils.join(",", bundle.getStringArray(key));
+                    if (!TextUtils.isEmpty(disallowedApnTypesString)) {
+                        apnTypesBitmask = ApnSetting.getApnTypesBitmaskFromString(
+                                disallowedApnTypesString);
+                    }
+                }
+            }
+        }
+
+        return apnTypesBitmask;
+    }
+
     private void dumpToLog() {
         dump(null, new PrintWriter(new StringWriter(0)) {
             @Override
@@ -2758,6 +2847,15 @@
         return score;
     }
 
+    private String handoverStateToString(@HandoverState int state) {
+        switch (state) {
+            case HANDOVER_STATE_IDLE: return "IDLE";
+            case HANDOVER_STATE_BEING_TRANSFERRED: return "BEING_TRANSFERRED";
+            case HANDOVER_STATE_COMPLETED: return "COMPLETED";
+            default: return "UNKNOWN";
+        }
+    }
+
     /**
      * Dump the current state.
      *
@@ -2787,7 +2885,7 @@
         pw.println("mLinkProperties=" + mLinkProperties);
         pw.flush();
         pw.println("mDataRegState=" + mDataRegState);
-        pw.println("mHandoverState=" + mHandoverState);
+        pw.println("mHandoverState=" + handoverStateToString(mHandoverState));
         pw.println("mRilRat=" + mRilRat);
         pw.println("mNetworkCapabilities=" + getNetworkCapabilities());
         pw.println("mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime));
@@ -2797,12 +2895,18 @@
         pw.println("mSubscriptionOverride=" + Integer.toHexString(mSubscriptionOverride));
         pw.println("mRestrictedNetworkOverride=" + mRestrictedNetworkOverride);
         pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
+        pw.println("disallowedApnTypes="
+                + ApnSetting.getApnTypesStringFromBitmask(getDisallowedApnTypes()));
         pw.println("mInstanceNumber=" + mInstanceNumber);
         pw.println("mAc=" + mAc);
         pw.println("mScore=" + mScore);
         if (mNetworkAgent != null) {
             mNetworkAgent.dump(fd, pw, args);
         }
+        pw.println("handover local log:");
+        pw.increaseIndent();
+        mHandoverLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
         pw.decreaseIndent();
         pw.println();
         pw.flush();
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
index b36a490..d9edd28 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
@@ -132,6 +132,13 @@
         mDataConnection = null;
     }
 
+    /**
+     * @return The data connection that owns this agent
+     */
+    public synchronized DataConnection getDataConnection() {
+        return mDataConnection;
+    }
+
     @Override
     protected synchronized void unwanted() {
         if (mDataConnection == null) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
index ea11223..43275ec 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
@@ -60,7 +60,7 @@
  *
  * The device can operate in the following modes, which is stored in the system properties
  * ro.telephony.iwlan_operation_mode. If the system properties is missing, then it's tied to
- * IRadio version. For 1.4 or above, it's legacy mode. For 1.3 or below, it's
+ * IRadio version. For 1.4 or above, it's AP-assisted mdoe. For 1.3 or below, it's legacy mode.
  *
  * Legacy mode:
  *      Frameworks send all data requests to the default data service, which is the cellular data
@@ -75,13 +75,13 @@
  *      frameworks to bind.
  *
  *      Package name of data service:
- *          The resource overlay 'config_wwan_data_service_package' or,
+ *          The resource overlay 'config_wlan_data_service_package' or,
  *          the carrier config
  *          {@link CarrierConfigManager#KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
  *          The carrier config takes precedence over the resource overlay if both exist.
  *
  *      Package name of network service
- *          The resource overlay 'config_wwan_network_service_package' or
+ *          The resource overlay 'config_wlan_network_service_package' or
  *          the carrier config
  *          {@link CarrierConfigManager#KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
  *          The carrier config takes precedence over the resource overlay if both exist.
@@ -129,8 +129,8 @@
     public @interface IwlanOperationMode {}
 
     /**
-     * IWLAN default mode. On device that has IRadio 1.3 or above, it means
-     * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.2 or below, it means
+     * IWLAN default mode. On device that has IRadio 1.4 or above, it means
+     * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.3 or below, it means
      * {@link #IWLAN_OPERATION_MODE_LEGACY}.
      */
     public static final String IWLAN_OPERATION_MODE_DEFAULT = "default";
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index da005fb..3c0320c 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -34,6 +34,7 @@
 import android.os.UserHandle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsMmTelFeature;
@@ -217,7 +218,19 @@
                     SubscriptionManager.INVALID_SIM_SLOT_INDEX);
 
             if (slotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-                Log.i(TAG, "Received SIM change for invalid slot id.");
+                Log.i(TAG, "Received CCC for invalid slot id.");
+                return;
+            }
+
+            int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            int slotSimState = mTelephonyManagerProxy.getSimState(mContext, slotId);
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                    && slotSimState != TelephonyManager.SIM_STATE_ABSENT) {
+                // We only care about carrier config updates that happen when a slot is known to be
+                // absent or populated and the carrier config has been loaded.
+                Log.i(TAG, "Received CCC for slot " + slotId + " and sim state "
+                        + slotSimState + ", ignoring.");
                 return;
             }
 
@@ -254,6 +267,28 @@
         int getSlotIndex(int subId);
     }
 
+    /**
+     * Testing interface used to stub out TelephonyManager dependencies.
+     */
+    @VisibleForTesting
+    public interface TelephonyManagerProxy {
+        /**
+         * @return the SIM state for the slot ID specified.
+         */
+        int getSimState(Context context, int slotId);
+    }
+
+    private TelephonyManagerProxy mTelephonyManagerProxy = new TelephonyManagerProxy() {
+        @Override
+        public int getSimState(Context context, int slotId) {
+            TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+            if (tm == null) {
+                return TelephonyManager.SIM_STATE_UNKNOWN;
+            }
+            return tm.getSimState(slotId);
+        }
+    };
+
     private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
         @Override
         public int getSubId(int slotId) {
@@ -446,6 +481,13 @@
                     Log.w(TAG, "onError: " + name + "returned with an error result");
                     scheduleQueryForFeatures(name, DELAY_DYNAMIC_QUERY_MS);
                 }
+
+                @Override
+                public void onPermanentError(ComponentName name) {
+                    Log.w(TAG, "onPermanentError: component=" + name);
+                    mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE,
+                            name.getPackageName()).sendToTarget();
+                }
             };
 
     // Array index corresponds to slot Id associated with the service package name.
@@ -497,6 +539,11 @@
     }
 
     @VisibleForTesting
+    public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) {
+        mTelephonyManagerProxy = proxy;
+    }
+
+    @VisibleForTesting
     public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
         mSubscriptionManagerProxy = proxy;
     }
@@ -520,13 +567,32 @@
      * Needs to be called after the constructor to kick off the process of binding to ImsServices.
      */
     public void initialize() {
-        Log.i(TAG, "Initializing cache and binding.");
+        Log.i(TAG, "Initializing cache.");
         mFeatureQueryManager = mDynamicQueryManagerFactory.create(mContext, mDynamicQueryListener);
-        // Populates the CarrierConfig override package names for each slot
-        mHandler.obtainMessage(HANDLER_CONFIG_CHANGED,
-                SubscriptionManager.INVALID_SIM_SLOT_INDEX).sendToTarget();
-        // Starts first bind to the system.
-        mHandler.obtainMessage(HANDLER_ADD_PACKAGE, null).sendToTarget();
+
+        // This will get all services with the correct intent filter from PackageManager
+        List<ImsServiceInfo> infos = getImsServiceInfo(null);
+        for (ImsServiceInfo info : infos) {
+            if (!mInstalledServicesCache.containsKey(info.name)) {
+                mInstalledServicesCache.put(info.name, info);
+            }
+        }
+        // Update the package names of the carrier ImsServices if they do not exist already and
+        // possibly bind if carrier configs exist. Otherwise wait for CarrierConfigChanged
+        // indication.
+        for (int i = 0; i < mNumSlots; i++) {
+            int subId = mSubscriptionManagerProxy.getSubId(i);
+            PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+            if (config != null && mCarrierServices[i] == null) {
+                String newPackageName = config.getString(
+                        CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+                if (!TextUtils.isEmpty(newPackageName)) {
+                    updateBoundCarrierServices(i, newPackageName);
+                    Log.i(TAG, "Initializing, found package " + newPackageName + " on slot "
+                            + i);
+                }
+            }
+        }
     }
 
     /**
@@ -781,8 +847,8 @@
         }
     }
 
-    // Remove the ImsService from the cache. At this point, the ImsService will have already been
-    // killed.
+    // Remove the ImsService from the cache. This may have been due to the ImsService being removed
+    // from the device or was returning permanent errors when bound.
     // Called from the handler ONLY
     private boolean maybeRemovedImsService(String packageName) {
         ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
@@ -860,7 +926,7 @@
                     Log.d(TAG, "Updating Features - New Features: " + features);
                     controller.changeImsServiceFeatures(features);
                 } else {
-                    Log.i(TAG, "updateImsServiceFeatures: unbound with active features, rebinding");
+                    Log.i(TAG, "updateImsServiceFeatures: unbound with active features, binding");
                     bindImsServiceWithFeatures(newInfo, features);
                 }
                 // If the carrier service features have changed, the device features will also
@@ -1007,6 +1073,15 @@
         handleFeaturesChanged(controller.getComponentName(), config.getServiceFeatures());
     }
 
+    @Override
+    public void imsServiceBindPermanentError(ComponentName name) {
+        if (name == null) {
+            return;
+        }
+        Log.w(TAG, "imsServiceBindPermanentError: component=" + name);
+        mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE, name.getPackageName()).sendToTarget();
+    }
+
     /**
      * Determines if the features specified should cause a bind or keep a binding active to an
      * ImsService.
@@ -1068,18 +1143,18 @@
                 // ImsService is retrieved from the cache. If the cache hasn't been populated yet,
                 // the calls to unbind/bind will fail (intended during initial start up).
                 unbindImsService(getImsServiceInfoFromCache(oldPackageName));
-                ImsServiceInfo newInfo = getImsServiceInfoFromCache(newPackageName);
-                // if there is no carrier ImsService, newInfo is null. This we still want to update
-                // bindings for device ImsService to pick up the missing features.
-                if (newInfo == null || newInfo.featureFromMetadata) {
-                    bindImsService(newInfo);
-                    // Recalculate the device ImsService features to reflect changes.
-                    updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
-                } else {
-                    // ImsServiceInfo that has not had features queried yet. Start async
-                    // bind and query features.
-                    scheduleQueryForFeatures(newInfo);
-                }
+            }
+            ImsServiceInfo newInfo = getImsServiceInfoFromCache(newPackageName);
+            // if there is no carrier ImsService, newInfo is null. This we still want to update
+            // bindings for device ImsService to pick up the missing features.
+            if (newInfo == null || newInfo.featureFromMetadata) {
+                bindImsService(newInfo);
+                // Recalculate the device ImsService features to reflect changes.
+                updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
+            } else {
+                // ImsServiceInfo that has not had features queried yet. Start async
+                // bind and query features.
+                scheduleQueryForFeatures(newInfo);
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index 4b1295c..997dac0 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -71,25 +71,23 @@
             synchronized (mLock) {
                 mIsBound = true;
                 mIsBinding = false;
-                Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
-                        + service);
-                if (service != null) {
-                    try {
-                        setServiceController(service);
-                        notifyImsServiceReady();
-                        // create all associated features in the ImsService
-                        for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
-                            addImsServiceFeature(i);
-                        }
-                    } catch (RemoteException e) {
-                        mIsBound = false;
-                        mIsBinding = false;
-                        // Remote exception means that the binder already died.
-                        cleanupConnection();
-                        startDelayedRebindToService();
-                        Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
-                                + e.getMessage());
+                try {
+                    Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
+                            + service);
+                    setServiceController(service);
+                    notifyImsServiceReady();
+                    // create all associated features in the ImsService
+                    for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
+                        addImsServiceFeature(i);
                     }
+                } catch (RemoteException e) {
+                    mIsBound = false;
+                    mIsBinding = false;
+                    // Remote exception means that the binder already died.
+                    cleanupConnection();
+                    startDelayedRebindToService();
+                    Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
+                            + e.getMessage());
                 }
             }
         }
@@ -111,10 +109,27 @@
                 mIsBound = false;
             }
             cleanupConnection();
+            // according to the docs, we should fully unbind before rebinding again.
+            mContext.unbindService(mImsServiceConnection);
             Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
             startDelayedRebindToService();
         }
 
+        @Override
+        public void onNullBinding(ComponentName name) {
+            Log.w(LOG_TAG, "ImsService(" + name + "): onNullBinding. Removing.");
+            synchronized (mLock) {
+                mIsBinding = false;
+                mIsBound = false;
+            }
+            cleanupConnection();
+            if (mCallbacks != null) {
+                // Will trigger an unbind.
+                mCallbacks.imsServiceBindPermanentError(getComponentName());
+            }
+        }
+
+        // Does not clear features, just removes all active features.
         private void cleanupConnection() {
             cleanupAllFeatures();
             cleanUpService();
@@ -151,6 +166,12 @@
          */
         void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
                 ImsServiceController controller);
+
+        /**
+         * Called by the ImsServiceController when there has been an error binding that is
+         * not recoverable, such as the ImsService returning a null binder.
+         */
+        void imsServiceBindPermanentError(ComponentName name);
     }
 
     /**
@@ -385,6 +406,8 @@
             removeImsServiceFeatureCallbacks();
             Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
             mContext.unbindService(mImsServiceConnection);
+            mIsBound = false;
+            mIsBinding = false;
             cleanUpService();
         }
     }
@@ -397,6 +420,9 @@
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
             throws RemoteException {
         synchronized (mLock) {
+            if (mImsFeatures.equals(newImsFeatures)) {
+                return;
+            }
             Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
                     + "ImsService: " + mComponentName);
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
index cbeeead..5280ff3 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
@@ -25,6 +25,7 @@
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.util.Log;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -71,8 +72,9 @@
             if (service != null) {
                 queryImsFeatures(IImsServiceController.Stub.asInterface(service));
             } else {
-                Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null, cleaning up.");
+                Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null.");
                 cleanup();
+                mListener.onPermanentError(name);
             }
         }
 
@@ -91,7 +93,14 @@
                 mListener.onError(mName);
                 return;
             }
-            Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs = config.getServiceFeatures();
+            Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs;
+            if (config == null) {
+                // ensure that if the ImsService sent a null config, we return an empty feature
+                // set to the ImsResolver.
+                servicePairs = Collections.emptySet();
+            } else {
+                servicePairs = config.getServiceFeatures();
+            }
             // Complete, remove from active queries and notify.
             cleanup();
             mListener.onComplete(mName, servicePairs);
@@ -117,6 +126,11 @@
          * Called when a query has failed and should be retried.
          */
         void onError(ComponentName name);
+
+        /**
+         * Called when a query has failed due to a permanent error and should not be retried.
+         */
+        void onPermanentError(ComponentName name);
     }
 
     // Maps an active ImsService query (by Package Name String) its query.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index e4c7c11..cfd36c2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -1756,6 +1756,44 @@
         waitForMs(200);
     }
 
+    private void changeRegStateWithIwlan(int state, CellIdentity cid, int voiceRat, int dataRat,
+            int iwlanState, int iwlanDataRat) {
+        LteVopsSupportInfo lteVopsSupportInfo =
+                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
+        sst.mPollingContext[0] = 3;
+
+        // PS WWAN
+        NetworkRegistrationInfo dataResult = new NetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                state, dataRat, 0, false,
+                null, cid, 1, false, false, false, lteVopsSupportInfo, false);
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+
+        // CS WWAN
+        NetworkRegistrationInfo voiceResult = new NetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                state, voiceRat, 0, false,
+                null, cid, false, 0, 0, 0);
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+
+        // PS WLAN
+        NetworkRegistrationInfo dataIwlanResult = new NetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                iwlanState, iwlanDataRat, 0, false,
+                null, null, 1, false, false, false, lteVopsSupportInfo, false);
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_PS_IWLAN_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, dataIwlanResult, null)));
+        waitForMs(200);
+    }
+
     // Edge and GPRS are grouped under the same family and Edge has higher rate than GPRS.
     // Expect no rat update when move from E to G.
     @Test
@@ -1928,6 +1966,49 @@
         assertTrue(Arrays.equals(new int[0], sst.mSS.getCellBandwidths()));
     }
 
+    /**
+     * Ensure that TransportManager changes due to transport preference changes are picked up in the
+     * new ServiceState when a poll event occurs. This causes ServiceState#getRilDataRadioTechnology
+     * to change even though the underlying transports have not changed state.
+     */
+    @SmallTest
+    @Test
+    public void testRilDataTechnologyChangeTransportPreference() {
+        when(mTransportManager.isAnyApnPreferredOnIwlan()).thenReturn(false);
+
+        // Start state: Cell data only LTE + IWLAN
+        CellIdentityLte cellIdentity =
+                new CellIdentityLte(1, 1, 5, 1, 5000, "001", "01", "test", "tst");
+        changeRegStateWithIwlan(
+                // WWAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyManager.NETWORK_TYPE_LTE,
+                // WLAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_LTE, sst.mSS.getRilDataRadioTechnology());
+
+        sst.registerForDataRegStateOrRatChanged(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                mTestHandler, EVENT_DATA_RAT_CHANGED, null);
+        // transport preference change for a PDN for IWLAN occurred, no registration change, but
+        // trigger unrelated poll to pick up transport preference.
+        when(mTransportManager.isAnyApnPreferredOnIwlan()).thenReturn(true);
+        changeRegStateWithIwlan(
+                // WWAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyManager.NETWORK_TYPE_LTE,
+                // WLAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+        // Now check to make sure a transport independent notification occurred for the registrants.
+        // There will be two, one when the registration happened and another when the transport
+        // preference changed.
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mTestHandler, times(2)).sendMessageAtTime(messageArgumentCaptor.capture(),
+                anyLong());
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, sst.mSS.getRilDataRadioTechnology());
+    }
+
     @Test
     public void testGetServiceProviderNameWithBrandOverride() {
         String brandOverride = "spn from brand override";
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
index b7fe4ca..018747e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -343,6 +343,12 @@
         return (long) method.invoke(mDc, response);
     }
 
+    private boolean isUnmeteredUseOnly() throws Exception {
+        Method method = DataConnection.class.getDeclaredMethod("isUnmeteredUseOnly");
+        method.setAccessible(true);
+        return (boolean) method.invoke(mDc);
+    }
+
     private SetupResult setLinkProperties(DataCallResponse response,
                                                          LinkProperties linkProperties)
             throws Exception {
@@ -500,6 +506,12 @@
         return (NetworkCapabilities) method.invoke(mDc);
     }
 
+    private int getDisallowedApnTypes() throws Exception {
+        Method method = DataConnection.class.getDeclaredMethod("getDisallowedApnTypes");
+        method.setAccessible(true);
+        return (int) method.invoke(mDc);
+    }
+
     @Test
     @SmallTest
     public void testNetworkCapability() throws Exception {
@@ -516,6 +528,10 @@
         assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS));
 
+        mContextFixture.getCarrierConfigBundle().putStringArray(
+                CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[] {"supl"});
+
         mDc.sendMessage(DataConnection.EVENT_DISCONNECT, mDcp);
         waitForMs(100);
         doReturn(mApn1).when(mApnContext).getApnSetting();
@@ -526,7 +542,7 @@
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN));
         assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
-        assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+        assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL));
     }
 
@@ -902,4 +918,40 @@
     public void testStartNattKeepaliveFailCondensed() throws Exception {
         checkStartNattKeepaliveFail(true);
     }
+
+    @Test
+    @SmallTest
+    public void testIsUnmeteredUseOnly() throws Exception {
+        Field field = DataConnection.class.getDeclaredField("mTransportType");
+        field.setAccessible(true);
+        field.setInt(mDc, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        assertFalse(isUnmeteredUseOnly());
+
+        field = DataConnection.class.getDeclaredField("mTransportType");
+        field.setAccessible(true);
+        field.setInt(mDc, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        doReturn(false).when(mDataEnabledSettings).isDataEnabled();
+        doReturn(false).when(mServiceState).getDataRoaming();
+        doReturn(ApnSetting.TYPE_MMS).when(mApnContext).getApnTypeBitmask();
+
+        mContextFixture.getCarrierConfigBundle().putStringArray(
+                CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[] { "default" });
+
+        assertTrue(isUnmeteredUseOnly());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetDisallowedApnTypes() throws Exception {
+        mContextFixture.getCarrierConfigBundle().putStringArray(
+                CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[] { "mms", "supl", "fota" });
+        testConnectEvent();
+
+        assertEquals(ApnSetting.TYPE_MMS | ApnSetting.TYPE_SUPL | ApnSetting.TYPE_FOTA,
+                getDisallowedApnTypes());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
index 4062b31..db7cf8e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -46,6 +46,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
@@ -88,6 +89,8 @@
     @Mock
     ImsResolver.SubscriptionManagerProxy mTestSubscriptionManagerProxy;
     @Mock
+    ImsResolver.TelephonyManagerProxy mTestTelephonyManagerProxy;
+    @Mock
     CarrierConfigManager mMockCarrierConfigManager;
     @Mock
     ImsResolver.ImsDynamicQueryManagerFactory mMockQueryManagerFactory;
@@ -129,7 +132,7 @@
         setupController();
 
         // Complete package manager lookup and cache.
-        startBind();
+        startBindCarrierConfigAlreadySet();
 
         ImsResolver.ImsServiceInfo testCachedService =
                 mTestImsResolver.getImsServiceInfoFromCache(
@@ -155,7 +158,7 @@
         setupController();
 
         // Complete package manager lookup and cache.
-        startBind();
+        startBindCarrierConfigAlreadySet();
 
         ImsResolver.ImsServiceInfo testCachedService =
                 mTestImsResolver.getImsServiceInfoFromCache(
@@ -185,7 +188,7 @@
         ImsServiceController controller = setupController();
 
         // Start bind to carrier service
-        startBind();
+        startBindCarrierConfigAlreadySet();
         // setup features response
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
 
@@ -210,7 +213,7 @@
         setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
         ImsServiceController controller = setupController();
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
 
         verify(controller).bind(features);
@@ -234,7 +237,7 @@
         ImsServiceController controller = setupController();
 
         // Bind without emergency calling
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
         verify(controller).bind(features);
         verify(controller, never()).unbind();
@@ -266,7 +269,7 @@
         // Set the CarrierConfig string to null so that ImsResolver will not bind to the available
         // Services
         setConfigCarrierString(0, null);
-        startBind();
+        startBindCarrierConfigAlreadySet();
 
         waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
         verify(mMockQueryManager, never()).startQuery(any(), any());
@@ -293,7 +296,7 @@
         ImsServiceController controller = setupController();
 
 
-        startBind();
+        startBindNoCarrierConfig(1);
         // Wait to make sure that there are no dynamic queries that are being completed.
         waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
 
@@ -333,7 +336,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
@@ -344,6 +347,7 @@
         // device controller (including emergency voice for slot 0)
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
                 convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.removeAll(carrierFeatures);
         verify(deviceController).bind(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
@@ -394,7 +398,7 @@
         setupPackageQuery(info);
         ImsServiceController controller = setupController();
         // Bind using default features
-        startBind();
+        startBindNoCarrierConfig(2);
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet =
                 convertToHashSet(features, 0);
         featureSet.addAll(convertToHashSet(features, 1));
@@ -442,7 +446,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
@@ -455,9 +459,7 @@
                 convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         deviceFeatureSet.removeAll(carrierFeatures);
-        // we will first have bound to device and then the features will change once the dynamic
-        // returns. So, instead of checking the bind parameters, we will check the change parameters
-        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+        verify(deviceController).bind(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -508,7 +510,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
         // Verify that all features that have been defined for the carrier override are bound
         verify(carrierController).bind(carrierFeatures);
@@ -520,9 +522,7 @@
                 convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         deviceFeatureSet.removeAll(carrierFeatures);
-        // device ImsService will bind with all of its defined features first and then when the
-        // carrier query comes back, it will change. So, checking change instead of bind here.
-        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+        verify(deviceController).bind(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -566,7 +566,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
         // Verify that all features that have been defined for the carrier override are bound
         verify(carrierController).bind(carrierFeatures);
@@ -578,9 +578,7 @@
                 convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         deviceFeatureSet.removeAll(carrierFeatures);
-        // we will first have bound to device and then the features will change once the dynamic
-        // returns. So, instead of checking the bind parameters, we will check the change parameters
-        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+        verify(deviceController).bind(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -624,7 +622,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
 
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
@@ -670,7 +668,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Tell the package manager that carrier app is uninstalled
@@ -714,14 +712,11 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         setConfigCarrierString(0, null);
-        Intent carrierConfigIntent = new Intent();
-        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
-        mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        sendCarrierConfigChanged(0, 0);
 
         // Verify that the carrier controller is unbound
         verify(carrierController).unbind();
@@ -768,14 +763,11 @@
         ImsServiceController carrierController2 = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController1, carrierController2);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures1, 1);
 
         setConfigCarrierString(0, TEST_CARRIER_2_DEFAULT_NAME.getPackageName());
-        Intent carrierConfigIntent = new Intent();
-        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
-        mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        sendCarrierConfigChanged(0, 0);
         setupDynamicQueryFeatures(TEST_CARRIER_2_DEFAULT_NAME, carrierFeatures2, 1);
 
         // Verify that carrier 1 is unbound
@@ -816,7 +808,7 @@
         setImsServiceControllerFactory(deviceController, carrierController);
 
         // Bind with device ImsService
-        startBind();
+        startBindCarrierConfigAlreadySet();
 
         // Boot complete happens and the Carrier ImsService is now available.
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
@@ -838,6 +830,97 @@
         verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
     }
 
+    /**
+     * If a misbehaving ImsService returns null for the Binder connection when we perform a dynamic
+     * feature query, verify we never perform a full bind for any features.
+     */
+    @Test
+    @SmallTest
+    public void testPermanentBindFailureDuringFeatureQuery() throws RemoteException {
+        setupResolver(1/*numSlots*/);
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0
+        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        // Carrier service doesn't support the voice feature.
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        startBindCarrierConfigAlreadySet();
+        // dynamic query results in a failure.
+        setupDynamicQueryFeaturesFailure(TEST_CARRIER_DEFAULT_NAME, 1);
+
+        // Verify that a bind never occurs for the carrier controller.
+        verify(carrierController, never()).bind(any());
+        verify(carrierController, never()).unbind();
+        // Verify that all features are used to bind to the device ImsService since the carrier
+        // ImsService failed to bind properly.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        verify(deviceController).bind(deviceFeatureSet);
+        verify(deviceController, never()).unbind();
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+    }
+
+    /**
+     * If a misbehaving ImsService returns null for the Binder connection when we perform bind,
+     * verify the service is disconnected.
+     */
+    @Test
+    @SmallTest
+    public void testPermanentBindFailureDuringBind() throws RemoteException {
+        setupResolver(1/*numSlots*/);
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0
+        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        // Carrier service doesn't support the voice feature.
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        startBindCarrierConfigAlreadySet();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+        // Verify that a bind never occurs for the carrier controller.
+        verify(carrierController).bind(carrierFeatures);
+        verify(carrierController, never()).unbind();
+        // Verify that all features that are not defined in the carrier override are bound in the
+        // device controller (including emergency voice for slot 0)
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).bind(deviceFeatureSet);
+        verify(deviceController, never()).unbind();
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+
+        mTestImsResolver.imsServiceBindPermanentError(TEST_CARRIER_DEFAULT_NAME);
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        verify(carrierController).unbind();
+        // Verify that the device ImsService features are changed to include the ones previously
+        // taken by the carrier app.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> originalDeviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        verify(deviceController).changeImsServiceFeatures(originalDeviceFeatureSet);
+    }
+
     private void setupResolver(int numSlots) {
         when(mMockContext.getSystemService(eq(Context.CARRIER_CONFIG_SERVICE))).thenReturn(
                 mMockCarrierConfigManager);
@@ -849,6 +932,8 @@
                     mCarrierConfigs[i]);
             when(mTestSubscriptionManagerProxy.getSlotIndex(eq(i))).thenReturn(i);
             when(mTestSubscriptionManagerProxy.getSubId(eq(i))).thenReturn(i);
+            when(mTestTelephonyManagerProxy.getSimState(any(Context.class), eq(i))).thenReturn(
+                    TelephonyManager.SIM_STATE_READY);
         }
 
         mTestImsResolver = new ImsResolver(mMockContext, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
@@ -865,6 +950,7 @@
         mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(0);
         mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(1);
         mTestImsResolver.setSubscriptionManagerProxy(mTestSubscriptionManagerProxy);
+        mTestImsResolver.setTelephonyManagerProxy(mTestTelephonyManagerProxy);
         when(mMockQueryManagerFactory.create(any(Context.class),
                 any(ImsServiceFeatureQueryManager.Listener.class))).thenReturn(mMockQueryManager);
         mTestImsResolver.setImsDynamicQueryManagerFactory(mMockQueryManagerFactory);
@@ -906,7 +992,11 @@
         return controller;
     }
 
-    private void startBind() {
+    /**
+     * In this case, there is a CarrierConfig already set for the sub/slot combo when initializing.
+     * This automatically kicks off the binding internally.
+     */
+    private void startBindCarrierConfigAlreadySet() {
         mTestImsResolver.initialize();
         ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
                 ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
@@ -915,6 +1005,23 @@
         waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
     }
 
+    /**
+     * In this case, there is no carrier config override, send CarrierConfig loaded intent to all
+     * slots, indicating that the SIMs are loaded and to bind the device default.
+     */
+    private void startBindNoCarrierConfig(int numSlots) {
+        mTestImsResolver.initialize();
+        ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
+                ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
+        verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
+        mDynamicQueryListener = queryManagerCaptor.getValue();
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        // For ease of testing, slotId = subId
+        for (int i = 0; i < numSlots; i++) {
+            sendCarrierConfigChanged(i, i);
+        }
+    }
+
     private void setupDynamicQueryFeatures(ComponentName name,
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features, int times) {
         // wait for schedule to happen
@@ -928,6 +1035,18 @@
         waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
     }
 
+    private void setupDynamicQueryFeaturesFailure(ComponentName name, int times) {
+        // wait for schedule to happen
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        // ensure that startQuery was called
+        when(mMockQueryManager.startQuery(any(ComponentName.class), any(String.class)))
+                .thenReturn(true);
+        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
+        mDynamicQueryListener.onPermanentError(name);
+        // wait for handling of onPermanentError
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+    }
+
     public void packageChanged(String packageName) {
         // Tell the package manager that a new device feature is installed
         Intent addPackageIntent = new Intent();
@@ -1004,6 +1123,14 @@
     }
 
 
+    private void sendCarrierConfigChanged(int subId, int slotId) {
+        Intent carrierConfigIntent = new Intent();
+        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, slotId);
+        mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+    }
+
     private void setConfigCarrierString(int subId, String packageName) {
         mCarrierConfigs[subId].putString(
                 CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, packageName);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
index 7e1d71c..85a83ce 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -295,6 +295,26 @@
     }
 
     /**
+     * Ensures that imsServiceBindPermanentError is called when the binder returns null.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceAndReturnedNull() throws RemoteException {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+        // Slot 1, MMTel
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+        // Slot 1, RCS
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+
+        bindAndNullServiceError(testFeatures);
+
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(anyInt(), anyInt(),
+                eq(mTestImsServiceController));
+        verify(mMockProxyCallbacks, never()).imsFeatureCreated(anyInt(), anyInt());
+        verify(mMockCallbacks).imsServiceBindPermanentError(eq(mTestComponentName));
+    }
+
+    /**
      * Ensures ImsService and ImsResolver are notified when a feature is added.
      */
     @SmallTest
@@ -499,16 +519,27 @@
         verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
     }
 
+    private void bindAndNullServiceError(
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
+        ServiceConnection connection = bindService(testFeatures);
+        connection.onNullBinding(mTestComponentName);
+    }
+
     private ServiceConnection bindAndConnectService(
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
+        ServiceConnection connection = bindService(testFeatures);
+        IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class);
+        when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
+        connection.onServiceConnected(mTestComponentName, controllerStub);
+        return connection;
+    }
+
+    private ServiceConnection bindService(
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
         ArgumentCaptor<ServiceConnection> serviceCaptor =
                 ArgumentCaptor.forClass(ServiceConnection.class);
         assertTrue(mTestImsServiceController.bind(testFeatures));
         verify(mMockContext).bindService(any(), serviceCaptor.capture(), anyInt());
-        IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class);
-        when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
-        serviceCaptor.getValue().onServiceConnected(mTestComponentName,
-                controllerStub);
         return serviceCaptor.getValue();
     }
 }