cold sim clean up

- add new api to control provisioning notification visibility through
      network agent
- rework on the interaction between carrier app and framework
- code cleanup
- unit test support
- hook pco value into datacallcomplete, enabling test by set sysprop for
  pco values

Bug: 28567303
Change-Id: Id6b9b2aff4c4f128103593aab0bcef1c3a365141
diff --git a/src/java/com/android/internal/telephony/CarrierSignalAgent.java b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
new file mode 100644
index 0000000..3d0bc6e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.telephony;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class act as an CarrierSignalling Agent.
+ * it load registered carrier signalling receivers from Carrier Config and cache the result to avoid
+ * repeated polling and send the intent to the interested receivers.
+ * each CarrierSignalAgent is associated with a phone object.
+ */
+public class CarrierSignalAgent {
+
+    private static final String LOG_TAG = "CarrierSignalAgent";
+    private static final boolean DBG = true;
+
+    /** Member variables */
+    private final Phone mPhone;
+    /**
+     * This is a map of intent action -> string array of carrier signal receiver names which are
+     * interested in this intent action
+     */
+    private static final HashMap<String, String[]> mCachedCarrierSignalReceiverNames =
+            new HashMap<>();
+    /**
+     * This is a map of intent action -> carrier config key of signal receiver names which are
+     * interested in this intent action
+     */
+    private static final Map<String, String> mIntentToCarrierConfigKeyMap =
+            new HashMap<String, String>() {{
+                put(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED,
+                        CarrierConfigManager.KEY_SIGNAL_REDIRECTION_RECEIVER_STRING_ARRAY);
+                put(TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE,
+                        CarrierConfigManager.KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY);
+                put(TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
+                        CarrierConfigManager.KEY_SIGNAL_DCFAILURE_RECEIVER_STRING_ARRAY);
+            }};
+
+    /** Constructor */
+    public CarrierSignalAgent(Phone phone) {
+        mPhone = phone;
+    }
+
+    /**
+     * Read carrier signalling receiver name from CarrierConfig based on the intent type
+     * @return array of receiver Name: the package (a String) name / the class (a String) name
+     */
+    private String[] getCarrierSignalReceiverName(String intentAction) {
+        String receiverType = mIntentToCarrierConfigKeyMap.get(intentAction);
+        if(receiverType == null) {
+            return null;
+        }
+        String[] receiverNames = mCachedCarrierSignalReceiverNames.get(intentAction);
+        // In case of cache miss, we need to look up/load from carrier config.
+        if (!mCachedCarrierSignalReceiverNames.containsKey(intentAction)) {
+            CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                    .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            PersistableBundle b = null;
+            if (configManager != null) {
+                b = configManager.getConfig();
+            }
+            if (b != null) {
+                receiverNames = b.getStringArray(receiverType);
+                if(receiverNames!=null) {
+                    for(String name: receiverNames) {
+                        Rlog.d("loadCarrierSignalReceiverNames: ", name);
+                    }
+                }
+            } else {
+                // Return static default defined in CarrierConfigManager.
+                receiverNames = CarrierConfigManager.getDefaultConfig().getStringArray(receiverType);
+            }
+            mCachedCarrierSignalReceiverNames.put(intentAction, receiverNames);
+        }
+        return receiverNames;
+    }
+
+    /**
+     * Check if there are registered carrier broadcast receivers to handle any registered intents.
+     */
+    public boolean hasRegisteredCarrierSignalReceivers() {
+        for(String intent : mIntentToCarrierConfigKeyMap.keySet()) {
+            if(!ArrayUtils.isEmpty(getCarrierSignalReceiverName(intent))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean notifyCarrierSignalReceivers(Intent intent) {
+        // Read a list of broadcast receivers from carrier config manager
+        // which are interested on certain intent type
+        String[] receiverName = getCarrierSignalReceiverName(intent.getAction());
+        if (receiverName == null) {
+            loge("Carrier receiver name is null");
+            return false;
+        }
+        final PackageManager packageManager = mPhone.getContext().getPackageManager();
+        boolean ret = false;
+
+        for(String name : receiverName) {
+            ComponentName componentName = ComponentName.unflattenFromString(name);
+            if (componentName == null) {
+                loge("Carrier receiver name could not be parsed");
+                return false;
+            }
+            intent.setComponent(componentName);
+            // Check if broadcast receiver is available
+            if (packageManager.queryBroadcastReceivers(intent,
+                    PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
+                loge("Carrier signal receiver is configured, but not available: " + name);
+                break;
+            }
+
+            intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mPhone.getSubId());
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+            try {
+                mPhone.getContext().sendBroadcast(intent);
+                if (DBG) log("send Intent to carrier signal receiver with action: " +
+                        intent.getAction());
+                ret = true;
+            } catch (ActivityNotFoundException e) {
+                loge("sendBroadcast failed: " + e);
+            }
+        }
+
+        return ret;
+    }
+
+    /* Clear cached receiver names */
+    public void reset() {
+        mCachedCarrierSignalReceiverNames.clear();
+    }
+
+    private void log(String s) {
+        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 79a054b..e4b62c9 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -225,6 +225,8 @@
     private int mCallRingContinueToken;
     private int mCallRingDelay;
     private boolean mIsVoiceCapable = true;
+    /* Used for communicate between configured CarrierSignalling receivers */
+    private CarrierSignalAgent mCarrierSignalAgent;
 
     // Variable to cache the video capability. When RAT changes, we lose this info and are unable
     // to recover from the state. We cache it and notify listeners when they register.
@@ -424,6 +426,7 @@
         mContext = context;
         mLooper = Looper.myLooper();
         mCi = ci;
+        mCarrierSignalAgent = new CarrierSignalAgent(this);
         mActionDetached = this.getClass().getPackage().getName() + ".action_detached";
         mActionAttached = this.getClass().getPackage().getName() + ".action_attached";
 
@@ -1718,6 +1721,10 @@
         return (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_ENABLED);
     }
 
+    public CarrierSignalAgent getCarrierSignalAgent() {
+        return mCarrierSignalAgent;
+    }
+
     /**
      *  Query the CDMA roaming preference setting
      *
@@ -2616,6 +2623,25 @@
                 (mDcTracker.isDataPossible(apnType)));
     }
 
+
+    /**
+     * Action set from carrier signalling broadcast receivers to enable/disable metered apns.
+     */
+    public void carrierActionSetMeteredApnsEnabled(boolean enabled) {
+        if(mDcTracker != null) {
+            mDcTracker.carrierActionSetMeteredApnsEnabled(enabled);
+        }
+    }
+
+    /**
+     * Action set from carrier signalling broadcast receivers to enable/disable radio
+     */
+    public void carrierActionSetRadioEnabled(boolean enabled) {
+        if(mDcTracker != null) {
+            mDcTracker.carrierActionSetRadioEnabled(enabled);
+        }
+    }
+
     /**
      * Notify registrants of a new ringing Connection.
      * Subclasses of Phone probably want to replace this with a
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index cff562a..ebe8827 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -114,6 +114,8 @@
     static final String REASON_SIM_NOT_READY = "simNotReady";
     static final String REASON_IWLAN_AVAILABLE = "iwlanAvailable";
     static final String REASON_CARRIER_CHANGE = "carrierChange";
+    static final String REASON_CARRIER_ACTION_DISABLE_METERED_APN =
+            "carrierActionDisableMeteredApn";
 
     // Used for band mode selection methods
     static final int BM_UNSPECIFIED = RILConstants.BAND_MODE_UNSPECIFIED; // automatic
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 8af290e..1122b36 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -252,6 +252,8 @@
 
     private boolean mImsRegistrationOnOff = false;
     private boolean mAlarmSwitch = false;
+    /** Radio is disabled by carrier. Radio power will not be override if this field is set */
+    private boolean mRadioDisabledByCarrier = false;
     private PendingIntent mRadioOffIntent = null;
     private static final String ACTION_RADIO_OFF = "android.intent.action.ACTION_RADIO_OFF";
     private boolean mPowerOffDelayNeed = true;
@@ -796,6 +798,16 @@
     }
 
     /**
+     * Radio power set from carrier action. if set to false means carrier desire to turn radio off
+     * and radio wont be re-enabled unless carrier explicitly turn it back on.
+     * @param enable indicate if radio power is enabled or disabled from carrier action.
+     */
+    public void setRadioPowerFromCarrier(boolean enable) {
+        mRadioDisabledByCarrier = !enable;
+        setRadioPower(enable);
+    }
+
+    /**
      * These two flags manage the behavior of the cell lock -- the
      * lock should be held if either flag is true.  The intention is
      * to allow temporary acquisition of the lock to get a single
@@ -2266,7 +2278,8 @@
                     ", mDesiredPowerState=" + mDesiredPowerState +
                     ", getRadioState=" + mCi.getRadioState() +
                     ", mPowerOffDelayNeed=" + mPowerOffDelayNeed +
-                    ", mAlarmSwitch=" + mAlarmSwitch);
+                    ", mAlarmSwitch=" + mAlarmSwitch +
+                    ", mRadioDisabledByCarrier=" + mRadioDisabledByCarrier);
         }
 
         if (mPhone.isPhoneTypeGsm() && mAlarmSwitch) {
@@ -2279,7 +2292,8 @@
 
         // If we want it on and it's off, turn it on
         if (mDesiredPowerState
-                && mCi.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) {
+                && mCi.getRadioState() == CommandsInterface.RadioState.RADIO_OFF &&
+                !mRadioDisabledByCarrier) {
             mCi.setRadioPower(true, null);
         } else if (!mDesiredPowerState && mCi.getRadioState().isOn()) {
             // If it's on and available and we want it off gracefully
@@ -4566,6 +4580,7 @@
         pw.println(" mImsRegistered=" + mImsRegistered);
         pw.println(" mImsRegistrationOnOff=" + mImsRegistrationOnOff);
         pw.println(" mAlarmSwitch=" + mAlarmSwitch);
+        pw.println(" mRadioDisabledByCarrier" + mRadioDisabledByCarrier);
         pw.println(" mPowerOffDelayNeed=" + mPowerOffDelayNeed);
         pw.println(" mDeviceShuttingDown=" + mDeviceShuttingDown);
         pw.println(" mSpnUpdatePending=" + mSpnUpdatePending);
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 568c348..38b054b 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -290,16 +290,12 @@
         // FIXME: consider stick this into database too
         String countryIso = getSubscriptionCountryIso(id);
 
-        int simProvisioningStatus = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.SIM_PROVISIONING_STATUS));
-
         if (VDBG) {
             String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
             logd("[getSubInfoRecord] id:" + id + " iccid:" + iccIdToPrint + " simSlotIndex:"
                     + simSlotIndex + " displayName:" + displayName + " nameSource:" + nameSource
                     + " iconTint:" + iconTint + " dataRoaming:" + dataRoaming
-                    + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso
-                    + " simProvisioningStatus:" + simProvisioningStatus);
+                    + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso);
         }
 
         // If line1number has been set to a different number, use it instead.
@@ -308,8 +304,7 @@
             number = line1Number;
         }
         return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
-                nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
-                simProvisioningStatus);
+                nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso);
     }
 
     /**
@@ -1099,47 +1094,6 @@
         return result;
     }
 
-    /**
-     * Set SimProvisioning Status by subscription ID
-     * @param provisioningStatus with the subscription:
-     * {@See SubscriptionManager#SIM_PROVISIONED}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_COLD}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_OUT_OF_CREDIT}
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     */
-    @Override
-    public int setSimProvisioningStatus(int provisioningStatus, int subId) {
-
-        if (DBG) {
-            logd("[setSimProvisioningStatus]+ provisioningStatus:" + provisioningStatus + " subId:"
-                    + subId);
-        }
-
-        enforceModifyPhoneState("setSimProvisioningStatus");
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subId);
-            if (provisioningStatus < 0 || provisioningStatus >
-                    SubscriptionManager.MAX_SIM_PROVISIONING_STATUS) {
-                logd("[setSimProvisioningStatus]- fail with wrong provisioningStatus");
-                return -1;
-            }
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.SIM_PROVISIONING_STATUS, provisioningStatus);
-
-            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
-                            Long.toString(subId), null);
-            notifySubscriptionInfoChanged();
-
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     @Override
     public int getSlotId(int subId) {
         if (VDBG) printStackTrace("[getSlotId] subId=" + subId);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 22a5c7c..c5064e1 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.dataconnection;
 
 import com.android.internal.telephony.CallTracker;
+import com.android.internal.telephony.CarrierSignalAgent;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.Phone;
@@ -1513,6 +1514,11 @@
             updateTcpBufferSizes(mRilRat);
 
             final NetworkMisc misc = new NetworkMisc();
+            final CarrierSignalAgent carrierSignalAgent = mPhone.getCarrierSignalAgent();
+            if(carrierSignalAgent.hasRegisteredCarrierSignalReceivers()) {
+                // carrierSignal Receivers will place the carrier-specific provisioning notification
+                misc.provisioningNotificationDisabled = true;
+            }
             misc.subscriberId = mPhone.getSubscriberId();
 
             if (createNetworkAgent) {
@@ -1811,7 +1817,6 @@
                    and let DcTracker to make the decision */
                 Message msg = mDct.obtainMessage(DctConstants.EVENT_REDIRECTION_DETECTED,
                         redirectUrl);
-                AsyncResult.forMessage(msg, mApnContexts, null);
                 msg.sendToTarget();
             }
         }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 9862e9c..7280f3a 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
@@ -48,7 +47,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
-import android.os.PersistableBundle;
 import android.os.RegistrantList;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -57,13 +55,12 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.provider.Telephony;
-import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
+import android.telephony.Rlog;
 import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.TelephonyManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyManager;
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
@@ -72,24 +69,22 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.view.WindowManager;
-import android.telephony.Rlog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.GsmCdmaPhone;
-import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.GsmCdmaPhone;
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.UiccController;
-import com.android.internal.telephony.dataconnection.DataConnection.ConnectionParams;
-import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.AsyncChannel;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -108,9 +103,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
-import java.lang.StringBuilder;
-
-import com.android.internal.telephony.ServiceStateTracker;
 /**
  * {@hide}
  */
@@ -174,10 +166,6 @@
     private static final String INTENT_DATA_STALL_ALARM =
             "com.android.internal.telephony.data-stall";
 
-    private static final String REDIRECTION_URL_KEY = "redirectionUrl";
-    private static final String ERROR_CODE_KEY = "errorCode";
-    private static final String APN_TYPE_KEY = "apnType";
-
     @VisibleForTesting
     public static class DataAllowFailReason {
         private HashSet<DataAllowFailReasonType> mDataAllowFailReasonSet = new HashSet<>();
@@ -372,8 +360,6 @@
                     int subId = mPhone.getSubId();
                     if (SubscriptionManager.isValidSubscriptionId(subId)) {
                         registerSettingsObserver();
-                        /* check if sim is un-provisioned */
-                        applyUnProvisionedSimDetected();
                     }
                     if (mPreviousSubId.getAndSet(subId) != subId &&
                             SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -627,21 +613,10 @@
 
     private int mDisconnectPendingCount = 0;
 
-    /** mRedirectUrl is set when we got the validation failure with the redirection URL
-     * based on which we start the Carrier App to check the sim state */
-    private String mRedirectUrl = null;
-
-    /** mColdSimDetected is set to true when we received SubInfoChanged &&
-     * SubscriptionInfo.simProvisioningStatus equals to SIM_UNPROVISIONED_COLD */
-    private boolean mColdSimDetected = false;
-
-    /** mmOutOfCreditSimDetected is set to true when we received SubInfoChanged &&
-     * SubscriptionInfo.simProvisioningStatus equals to SIM_UNPROVISIONED_OUT_OF_CREDIT */
-    private boolean mOutOfCreditSimDetected = false;
-
-    /** HashSet of ApnContext associated with redirected data-connection.
-     * those apn contexts tear down upon redirection and re-establish upon non-cold sim detection */
-    private HashSet<ApnContext> redirectApnContextSet = new HashSet<>();
+    /** Indicate if metered APNs are disabled.
+     *  set to block all the metered APNs from continuously sending requests, which causes
+     *  undesired network load */
+    private boolean mMeteredApnDisabled = false;
 
     /**
      * Handles changes to the APN db.
@@ -975,44 +950,6 @@
         return true;
     }
 
-    /**
-     * Called when there is any change to any SubscriptionInfo Typically
-     * this method invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
-     */
-    private boolean isColdSimDetected() {
-        int subId = mPhone.getSubId();
-        if(SubscriptionManager.isValidSubscriptionId(subId)) {
-            final SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
-            if (subInfo != null) {
-                final int simProvisioningStatus = subInfo.getSimProvisioningStatus();
-                if (simProvisioningStatus == SubscriptionManager.SIM_UNPROVISIONED_COLD) {
-                    log("Cold Sim Detected on SubId: " + subId);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Called when there is any change to any SubscriptionInfo Typically
-     * this method invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
-     */
-    private boolean isOutOfCreditSimDetected() {
-        int subId = mPhone.getSubId();
-        if(SubscriptionManager.isValidSubscriptionId(subId)) {
-            final SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
-            if (subInfo != null) {
-                final int simProvisioningStatus = subInfo.getSimProvisioningStatus();
-                if (simProvisioningStatus == SubscriptionManager.SIM_UNPROVISIONED_OUT_OF_CREDIT) {
-                    log("Out Of Credit Sim Detected on SubId: " + subId);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     public int getApnPriority(String name) {
         ApnContext apnContext = mApnContexts.get(name);
         if (apnContext == null) {
@@ -1340,7 +1277,8 @@
     private boolean isDataEnabled(boolean checkUserDataEnabled) {
         synchronized (mDataEnabledLock) {
             if (!(mInternalDataEnabled && (!checkUserDataEnabled || mUserDataEnabled)
-                    && (!checkUserDataEnabled || sPolicyDataEnabled)))
+                    && (!checkUserDataEnabled || sPolicyDataEnabled)
+                    && (!checkUserDataEnabled || !mMeteredApnDisabled)))
                 return false;
         }
         return true;
@@ -1590,7 +1528,7 @@
 
         if (apnContext.isConnectable() && (isEmergencyApn ||
                 (isDataAllowed && isDataAllowedForApn(apnContext) &&
-                isDataEnabled(checkUserDataEnabled) && !isEmergency())) && !mColdSimDetected ) {
+                isDataEnabled(checkUserDataEnabled) && !isEmergency()))) {
             if (apnContext.getState() == DctConstants.State.FAILED) {
                 String str = "trySetupData: make a FAILED ApnContext IDLE so its reusable";
                 if (DBG) log(str);
@@ -1651,14 +1589,12 @@
             if (!isDataEnabled(checkUserDataEnabled)) {
                 str.append("isDataEnabled(" + checkUserDataEnabled + ") = false. " +
                         "mInternalDataEnabled = " + mInternalDataEnabled + " , mUserDataEnabled = "
-                        + mUserDataEnabled + ", sPolicyDataEnabled = " + sPolicyDataEnabled + " ");
+                        + mUserDataEnabled + ", sPolicyDataEnabled = " + sPolicyDataEnabled +
+                        ", mMeteredApnDisabled = " + mMeteredApnDisabled);
             }
             if (isEmergency()) {
                 str.append("emergency = true");
             }
-            if(mColdSimDetected) {
-                str.append("coldSimDetected = true");
-            }
 
             if (DBG) log(str.toString());
             apnContext.requestLog(str.toString());
@@ -1704,17 +1640,18 @@
     private boolean cleanUpAllConnections(boolean tearDown, String reason) {
         if (DBG) log("cleanUpAllConnections: tearDown=" + tearDown + " reason=" + reason);
         boolean didDisconnect = false;
-        boolean specificDisable = false;
+        boolean disableMeteredOnly = false;
 
-        // Either user disable mobile data or under roaming service and user disabled roaming
+        // reasons that only metered apn will be torn down
         if (!TextUtils.isEmpty(reason)) {
-            specificDisable = reason.equals(Phone.REASON_DATA_SPECIFIC_DISABLED) ||
-                    reason.equals(Phone.REASON_ROAMING_ON);
+            disableMeteredOnly = reason.equals(Phone.REASON_DATA_SPECIFIC_DISABLED) ||
+                    reason.equals(Phone.REASON_ROAMING_ON) ||
+                    reason.equals(Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN);
         }
 
         for (ApnContext apnContext : mApnContexts.values()) {
             if (apnContext.isDisconnected() == false) didDisconnect = true;
-            if (specificDisable) {
+            if (disableMeteredOnly) {
                 // Use ApnSetting to decide metered or non-metered.
                 // Tear down all metered data connections.
                 ApnSetting apnSetting = apnContext.getApnSetting();
@@ -2403,35 +2340,38 @@
         setupDataOnConnectableApns(Phone.REASON_SIM_LOADED);
     }
 
-    private void applyUnProvisionedSimDetected() {
-        if(isColdSimDetected()) {
-            if(!mColdSimDetected) {
-                if(DBG) {
-                    log("onColdSimDetected: cleanUpAllDataConnections");
-                }
-                cleanUpAllConnections(null);
-                //send otasp_sim_unprovisioned so that SuW is able to proceed and notify users
+    /**
+     * Action set from carrier signalling broadcast receivers to enable/disable metered apns.
+     */
+    public void carrierActionSetMeteredApnsEnabled(boolean enabled) {
+        if (enabled == mMeteredApnDisabled) {
+            if (DBG) {
+                log("carrier Action: set metered apns enabled: " + enabled);
+            }
+            // Disable/enable all metered apns
+            mMeteredApnDisabled = !enabled;
+            if (!enabled) {
+                // Send otasp_sim_unprovisioned so that SuW is able to proceed and notify users
                 mPhone.notifyOtaspChanged(ServiceStateTracker.OTASP_SIM_UNPROVISIONED);
-                mColdSimDetected = true;
+                // Tear down all metered apns
+                cleanUpAllConnections(true, Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN);
+            } else {
+                setupDataOnConnectableApns(Phone.REASON_DATA_ENABLED);
             }
-        } else if (isOutOfCreditSimDetected()) {
-            if(!mOutOfCreditSimDetected) {
-                if(DBG) {
-                    log("onOutOfCreditSimDetected on subId: re-establish data connection");
-                }
-                for (ApnContext context : redirectApnContextSet) {
-                    onTrySetupData(context);
-                    redirectApnContextSet.remove(context);
-                }
-                mOutOfCreditSimDetected = true;
-            }
-        } else {
-            if (DBG) log("Provisioned Sim Detected on subId: " + mPhone.getSubId() );
-            mColdSimDetected = false;
-            mOutOfCreditSimDetected = false;
         }
     }
 
+    /**
+     * Action set from carrier signalling broadcast receivers to enable/disable radio
+     */
+    public void carrierActionSetRadioEnabled(boolean enabled) {
+        if (DBG) {
+            log("carrier Action: set radio enabled: " + enabled);
+        }
+        final ServiceStateTracker sst = mPhone.getServiceStateTracker();
+        sst.setRadioPowerFromCarrier(enabled);
+    }
+
     private void onSimNotReady() {
         if (DBG) log("onSimNotReady");
 
@@ -2984,6 +2924,16 @@
                     log("onDataSetupComplete: SETUP complete type=" + apnContext.getApnType()
                         + ", reason:" + apnContext.getReason());
                 }
+                if (Build.IS_DEBUGGABLE) {
+                    // adb shell setprop persist.radio.test.pco [pco_val]
+                    String radioTestProperty = "persist.radio.test.pco";
+                    int pcoVal = SystemProperties.getInt(radioTestProperty, 0);
+                    log("PCO testing: read pco value from persist.radio.test.pco " + pcoVal);
+                    Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE);
+                    intent.putExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY, apnContext.getApnType());
+                    intent.putExtra(TelephonyIntents.EXTRA_PCO_KEY, pcoVal);
+                    mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
+                }
             }
         } else {
             cause = (DcFailCause) (ar.result);
@@ -3002,11 +2952,12 @@
             mPhone.notifyPreciseDataConnectionFailed(apnContext.getReason(),
                     apnContext.getApnType(), apn != null ? apn.apn : "unknown", cause.toString());
 
-            //compose broadcast intent send to the specific carrier apps
-            Intent intent = new Intent(TelephonyIntents.ACTION_REQUEST_NETWORK_FAILED);
-            intent.putExtra(ERROR_CODE_KEY, cause.getErrorCode());
-            intent.putExtra(APN_TYPE_KEY, apnContext.getApnType());
-            notifyCarrierAppWithIntent(intent);
+            // Compose broadcast intent send to the specific carrier signaling receivers
+            Intent intent = new Intent(TelephonyIntents
+                    .ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED);
+            intent.putExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY, cause.getErrorCode());
+            intent.putExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY, apnContext.getApnType());
+            mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
 
             if (cause.isRestartRadioFail() || apnContext.restartOnError(cause.getErrorCode())) {
                 if (DBG) log("Modem restarted.");
@@ -3092,48 +3043,14 @@
     }
 
     /**
-     * Read Carrier App name from CarrierConfig
-     * @return String[0] Package name, String[1] Activity name
-     */
-    private String[] getActivationAppName() {
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle b = null;
-        String[] activationApp;
-
-       if (configManager != null) {
-            b = configManager.getConfig();
-        }
-        if (b != null) {
-            activationApp = b.getStringArray(CarrierConfigManager
-                    .KEY_SIM_PROVISIONING_STATUS_DETECTION_CARRIER_APP_STRING_ARRAY);
-        } else {
-            // Return static default defined in CarrierConfigManager.
-            activationApp = CarrierConfigManager.getDefaultConfig().getStringArray
-                    (CarrierConfigManager
-                            .KEY_SIM_PROVISIONING_STATUS_DETECTION_CARRIER_APP_STRING_ARRAY);
-        }
-        return activationApp;
-    }
-
-    /**
      * Called when EVENT_REDIRECTION_DETECTED is received.
      */
-    private void onDataConnectionRedirected(String redirectUrl,
-                                            HashMap<ApnContext, ConnectionParams> apnContextMap) {
+    private void onDataConnectionRedirected(String redirectUrl) {
         if (!TextUtils.isEmpty(redirectUrl)) {
-            mRedirectUrl = redirectUrl;
-            Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_REDIRECTED);
-            intent.putExtra(REDIRECTION_URL_KEY, redirectUrl);
-            if(!isColdSimDetected() && !isOutOfCreditSimDetected()
-                    && checkCarrierAppAvailable(intent)) {
-                log("Starting Activation Carrier app with redirectUrl : " + redirectUrl);
-
-                // Tear down data connections for all apn types
-                for(ApnContext context : apnContextMap.keySet()) {
-                    cleanUpConnection(true, context);
-                    redirectApnContextSet.add(context);
-                }
+            Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
+            intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, redirectUrl);
+            if(mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent)) {
+                log("Notify carrier signal receivers with redirectUrl: " + redirectUrl);
             }
         }
     }
@@ -3168,8 +3085,6 @@
                 if (mDisconnectPendingCount == 0) {
                     notifyDataDisconnectComplete();
                     notifyAllDataDisconnected();
-                    // Notify carrier app with redirection when there is no pending disconnect req
-                    notifyCarrierAppForRedirection();
                 }
                 return;
             }
@@ -3217,8 +3132,6 @@
                     mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed());
             notifyDataDisconnectComplete();
             notifyAllDataDisconnected();
-            // Notify carrier app with redirection when there is no pending disconnect req
-            notifyCarrierAppForRedirection();
         }
 
     }
@@ -3835,10 +3748,9 @@
                 break;
 
             case DctConstants.EVENT_REDIRECTION_DETECTED:
-                AsyncResult ar = (AsyncResult) msg.obj;
-                String url = (String) ar.userObj;
+                String url = (String) msg.obj;
                 log("dataConnectionTracker.handleMessage: EVENT_REDIRECTION_DETECTED=" + url);
-                onDataConnectionRedirected(url, (HashMap<ApnContext, ConnectionParams>) ar.result);
+                onDataConnectionRedirected(url);
 
             case DctConstants.EVENT_RADIO_AVAILABLE:
                 onRadioAvailable();
@@ -4090,8 +4002,11 @@
                     mIccRecords.set(newIccRecords);
                     newIccRecords.registerForRecordsLoaded(
                             this, DctConstants.EVENT_RECORDS_LOADED, null);
-                    SubscriptionController.getInstance().setSimProvisioningStatus(
-                            SubscriptionManager.SIM_PROVISIONED, mPhone.getSubId());
+                    // reset carrier actions on sim loaded
+                    final ServiceStateTracker sst = mPhone.getServiceStateTracker();
+                    sst.setRadioPowerFromCarrier(true);
+                    mMeteredApnDisabled = false;
+                    mPhone.getCarrierSignalAgent().reset();
                 }
             } else {
                 onSimNotReady();
@@ -4129,60 +4044,6 @@
         sendMessage(msg);
     }
 
-    private boolean checkCarrierAppAvailable(Intent intent) {
-        // Read from carrier config manager
-        String[] activationApp = getActivationAppName();
-        if(activationApp == null || activationApp.length != 2) {
-            return false;
-        }
-
-        intent.setClassName(activationApp[0], activationApp[1]);
-        // Check if activation app is available
-        final PackageManager packageManager = mPhone.getContext().getPackageManager();
-        if (packageManager.queryBroadcastReceivers(intent,
-                PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
-            loge("Activation Carrier app is configured, but not available: "
-                    + activationApp[0] + "." + activationApp[1]);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean notifyCarrierAppWithIntent(Intent intent) {
-        // RIL has limitation to process new request while there is pending deactivation requests
-        // Make sure there is no pending disconnect before launching carrier app
-        if (mDisconnectPendingCount != 0) {
-            loge("Wait for pending disconnect requests done");
-            return false;
-        }
-        if (!checkCarrierAppAvailable(intent)) {
-            loge("Carrier app is unavailable");
-            return false;
-        }
-
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mPhone.getSubId());
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-
-        try {
-            mPhone.getContext().sendBroadcast(intent);
-        } catch (ActivityNotFoundException e) {
-            loge("sendBroadcast failed: " + e);
-            return false;
-        }
-
-        if (DBG) log("send Intent to Carrier app with action: " + intent.getAction());
-        return true;
-    }
-
-    private void notifyCarrierAppForRedirection() {
-        // Notify carrier app with redirectionUrl
-        if (!isColdSimDetected() && !isOutOfCreditSimDetected() && mRedirectUrl != null) {
-            Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_REDIRECTED);
-            intent.putExtra(REDIRECTION_URL_KEY, mRedirectUrl);
-            if (notifyCarrierAppWithIntent(intent)) mRedirectUrl = null;
-        }
-    }
-
     private void notifyDataDisconnectComplete() {
         log("notifyDataDisconnectComplete");
         for (Message m: mDisconnectAllCompleteMsgList) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index f8f9ac9..3993e7b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -488,6 +488,8 @@
         }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt());
 
         doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+        //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt());
+        doReturn(mBundle).when(mCarrierConfigManager).getConfig();
 
         mConfiguration.locale = Locale.US;
         doReturn(mConfiguration).when(mResources).getConfiguration();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
index 50e557f..ac97937 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
@@ -35,8 +35,7 @@
     @Before
     public void setUp() throws Exception {
         mSubscriptionInfoUT = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0, "T-mobile",
-                "T-mobile", 0, 255, "12345", 0, null, 310, 260, "156",
-                SubscriptionManager.SIM_PROVISIONED);
+                "T-mobile", 0, 255, "12345", 0, null, 310, 260, "156");
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 2452069..976f418 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -44,6 +44,7 @@
 import android.provider.BlockedNumberContract;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.util.Log;
@@ -169,8 +170,12 @@
     protected EriManager mEriManager;
     @Mock
     protected IBinder mConnMetLoggerBinder;
+    @Mock
+    protected CarrierSignalAgent mCarrierSignalAgent;
 
     protected TelephonyManager mTelephonyManager;
+    protected SubscriptionManager mSubscriptionManager;
+    protected PackageManager mPackageManager;
     protected SimulatedCommands mSimulatedCommands;
     protected ContextFixture mContextFixture;
     protected Context mContext;
@@ -299,6 +304,9 @@
         mPhone.mCi = mSimulatedCommands;
         mCT.mCi = mSimulatedCommands;
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mSubscriptionManager = (SubscriptionManager) mContext.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        mPackageManager = mContext.getPackageManager();
 
         replaceInstance(TelephonyManager.class, "sInstance", null,
                 mContext.getSystemService(Context.TELEPHONY_SERVICE));
@@ -344,6 +352,7 @@
         doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mPhone).getPhoneType();
         doReturn(mCT).when(mPhone).getCallTracker();
         doReturn(mSST).when(mPhone).getServiceStateTracker();
+        doReturn(mCarrierSignalAgent).when(mPhone).getCarrierSignalAgent();
         mPhone.mEriManager = mEriManager;
 
         //mUiccController
@@ -443,8 +452,7 @@
     }
 
     protected void setupMockPackagePermissionChecks() throws Exception {
-        PackageManager mockPackageManager = mContext.getPackageManager();
-        doReturn(new String[]{TAG}).when(mockPackageManager).getPackagesForUid(anyInt());
-        doReturn(mPackageInfo).when(mockPackageManager).getPackageInfo(eq(TAG), anyInt());
+        doReturn(new String[]{TAG}).when(mPackageManager).getPackagesForUid(anyInt());
+        doReturn(mPackageInfo).when(mPackageManager).getPackageInfo(eq(TAG), anyInt());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
index a014980..772905b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -35,6 +35,8 @@
 import android.provider.Telephony;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -53,6 +55,8 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.lang.reflect.Method;
 import java.util.Arrays;
@@ -72,6 +76,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -103,6 +108,8 @@
     IBinder mBinder;
     @Mock
     NetworkRequest mNetworkRequest;
+    @Mock
+    SubscriptionInfo mSubscriptionInfo;
 
     private DcTracker mDct;
 
@@ -110,6 +117,8 @@
 
     private PersistableBundle mBundle;
 
+    private SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
+
     private final ApnSettingContentProvider mApnSettingContentProvider =
             new ApnSettingContentProvider();
 
@@ -299,6 +308,18 @@
         doReturn(true).when(mSimRecords).getRecordsLoaded();
         doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
         doReturn(true).when(mSST).getDesiredPowerState();
+        doAnswer(
+                new Answer<Void>() {
+                    @Override
+                    public Void answer(InvocationOnMock invocation) throws Throwable {
+                        mOnSubscriptionsChangedListener =
+                                (SubscriptionManager.OnSubscriptionsChangedListener)
+                                        invocation.getArguments()[0];
+                        return null;
+                    }
+                }
+        ).when(mSubscriptionManager).addOnSubscriptionsChangedListener(any());
+        doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
 
         doReturn(1).when(mIsub).getDefaultDataSubId();
         doReturn(mIsub).when(mBinder).queryLocalInterface(anyString());
@@ -739,4 +760,51 @@
         allowed = isDataAllowed(failureReason);
         assertFalse(failureReason.getDataAllowFailReason(), allowed);
     }
+
+    // Test for API carrierActionSetMeteredApnsEnabled.
+    @Test
+    @MediumTest
+    public void testCarrierActionSetMeteredApnsEnabled() throws Exception {
+        //step 1: setup two DataCalls one for Internet and IMS
+        //step 2: set data is enabled
+        //step 3: cold sim is detected
+        //step 4: all data connection is torn down
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
+
+        boolean dataEnabled = mDct.getDataEnabled();
+        mDct.setDataEnabled(true);
+
+        mDct.setEnabled(5, true);
+        mDct.setEnabled(0, true);
+
+        logd("Sending EVENT_RECORDS_LOADED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        waitForMs(200);
+
+        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
+                eq(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS), eq(2), eq(FAKE_APN3),
+                eq(""), eq(""), eq(3), eq("IP"), any(Message.class));
+        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
+                eq(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS), eq(0), eq(FAKE_APN1),
+                eq(""), eq(""), eq(0), eq("IP"), any(Message.class));
+        assertEquals(DctConstants.State.CONNECTED, mDct.getOverallState());
+
+        mDct.carrierActionSetMeteredApnsEnabled(false);
+        waitForMs(100);
+
+        // Validate all metered data connections have been torn down
+        verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(anyInt(), anyInt(),
+                any(Message.class));
+        assertEquals(DctConstants.State.CONNECTED, mDct.getOverallState());
+        assertEquals(DctConstants.State.IDLE, mDct.getState(PhoneConstants.APN_TYPE_DEFAULT));
+
+        // Reset settings at the end of test
+        mDct.setDataEnabled(dataEnabled);
+        waitForMs(200);
+    }
 }