Merge "Migrate GSM SignalStrength to WCDMA on HAL 1.0"
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index 4239dd4..44ada6d 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -642,6 +642,38 @@
   }
 }
 
+message EmergencyNumberInfo {
+  // Dialing address
+  optional string address = 1 /* [
+    (datapol.semantic_type) = ST_PHONE_NUMBER,
+    (datapol.qualifier) = {is_public: true}
+  ] */;
+
+  // Country code string (lowercase character) in ISO 3166 format
+  optional string country_iso = 2 /* [(datapol.semantic_type) = ST_LOCATION] */;
+
+  // Mobile Network Code
+  optional string mnc = 3 /* [(datapol.semantic_type) = ST_LOCATION] */;
+
+  // Bitmask of emergency service categories
+  optional int32 service_categories_bitmask = 4;
+
+  // Emergency Uniform Resources Names (URN)
+  // Reference: https://tools.ietf.org/html/rfc5031
+  repeated string urns = 5;
+
+  // Bitmask of the sources
+  optional int32 number_sources_bitmask = 6;
+
+  // Emergency call routing information.
+  // Emergency call routing is a flag to tell how modem handles the calling with
+  // emergency numbers. For example, 110 in India, modem needs to handle/route
+  // it like a normal call. There are only two possible options for emergency
+  // call routing: emergency call routing vs normal call routing. It is usually
+  // a country or carrier requirement.
+  optional int32 routing = 7;
+}
+
 message TelephonyEvent {
 
   enum Type {
@@ -710,6 +742,9 @@
 
     // Enabled modem change event.
     ENABLED_MODEM_CHANGED = 20;
+
+    // Emergency Number update event (Device HAL >= 1.4).
+    EMERGENCY_NUMBER_REPORT = 21;
   }
 
   enum ApnType {
@@ -1649,6 +1684,9 @@
   // The modem state represent by a bitmap, the i-th bit(LSB) indicates the i-th modem
   // state (0 - disabled, 1 - enabled).
   optional int32 enabled_modem_bitmap = 24;
+
+  // Updated Emergency Call info.
+  optional EmergencyNumberInfo updated_emergency_number = 25;
 }
 
 message ActiveSubscriptionInfo {
@@ -1952,6 +1990,12 @@
       // Detailed cause code for CS Call failures
       // frameworks/base/telephony/java/android/telephony/PreciseDisconnectCause.java
       optional int32 precise_disconnect_cause = 6;
+
+      // Indicate if the call is an emergency call
+      optional bool is_emergency_call = 7;
+
+      // Indicate the emergency call information dialed from the CS call
+      optional EmergencyNumberInfo emergency_number_info = 8;
     }
 
     // Single Radio Voice Call Continuity(SRVCC) progress state
@@ -2173,6 +2217,11 @@
     // Uplink call quality summary at the end of a call
     optional CallQualitySummary call_quality_summary_ul = 25;
 
+    // Indicate if it is IMS emergency call
+    optional bool is_ims_emergency_call = 26;
+
+    // Emergency call info
+    optional EmergencyNumberInfo ims_emergency_number_info = 27;
   }
 
   // Time when call has started, in minutes since epoch,
diff --git a/src/java/com/android/internal/telephony/CallTracker.java b/src/java/com/android/internal/telephony/CallTracker.java
index b48f21c..002e082 100644
--- a/src/java/com/android/internal/telephony/CallTracker.java
+++ b/src/java/com/android/internal/telephony/CallTracker.java
@@ -101,6 +101,8 @@
 
     protected abstract void handlePollCalls(AsyncResult ar);
 
+    protected abstract Phone getPhone();
+
     protected Connection getHoConnection(DriverCall dc) {
         for (Connection hoConn : mHandoverConnections) {
             log("getHoConnection - compare number: hoConn= " + hoConn.toString());
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index b5b3796..d4d606d 100755
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -506,10 +506,9 @@
      *
      * @hide
      */
-    public void setEmergencyCallInfo() {
-        Call call = getCall();
-        if (call != null) {
-            Phone phone = call.getPhone();
+    public void setEmergencyCallInfo(CallTracker ct) {
+        if (ct != null) {
+            Phone phone = ct.getPhone();
             if (phone != null) {
                 EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker();
                 if (tracker != null) {
@@ -517,9 +516,17 @@
                     if (num != null) {
                         mIsEmergencyCall = true;
                         mEmergencyNumberInfo = num;
+                    } else {
+                        Rlog.e(TAG, "setEmergencyCallInfo: emergency number is null");
                     }
+                } else {
+                    Rlog.e(TAG, "setEmergencyCallInfo: emergency number tracker is null");
                 }
+            } else {
+                Rlog.e(TAG, "setEmergencyCallInfo: phone is null");
             }
+        } else {
+            Rlog.e(TAG, "setEmergencyCallInfo: call tracker is null");
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index 2b003a9..0a7acee 100755
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -1091,8 +1091,10 @@
                 newUnknownConnectionCdma = null;
             }
         }
+
         if (locallyDisconnectedConnections.size() > 0) {
-            mMetrics.writeRilCallList(mPhone.getPhoneId(), locallyDisconnectedConnections);
+            mMetrics.writeRilCallList(mPhone.getPhoneId(), locallyDisconnectedConnections,
+                    getNetworkCountryIso());
         }
 
         /* Disconnect any pending Handover connections */
@@ -1163,7 +1165,7 @@
         for (GsmCdmaConnection conn : connections) {
             if (conn != null) activeConnections.add(conn);
         }
-        mMetrics.writeRilCallList(mPhone.getPhoneId(), activeConnections);
+        mMetrics.writeRilCallList(mPhone.getPhoneId(), activeConnections, getNetworkCountryIso());
     }
 
     private void handleRadioNotAvailable() {
@@ -1236,7 +1238,8 @@
             return;
         } else {
             try {
-                mMetrics.writeRilHangup(mPhone.getPhoneId(), conn, conn.getGsmCdmaIndex());
+                mMetrics.writeRilHangup(mPhone.getPhoneId(), conn, conn.getGsmCdmaIndex(),
+                        getNetworkCountryIso());
                 mCi.hangupConnection (conn.getGsmCdmaIndex(), obtainCompleteMessage());
             } catch (CallStateException ex) {
                 // Ignore "connection not found"
@@ -1331,7 +1334,7 @@
             } catch (CallStateException ex) {
                 call_index = -1;
             }
-            mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, call_index);
+            mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, call_index, getNetworkCountryIso());
         }
         if (VDBG) Rlog.v(LOG_TAG, "logHangupEvent logged " + count + " Connections ");
     }
@@ -1353,7 +1356,8 @@
         for (int i = 0; i < count; i++) {
             GsmCdmaConnection cn = (GsmCdmaConnection)call.mConnections.get(i);
             if (!cn.mDisconnected && cn.getGsmCdmaIndex() == index) {
-                mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, cn.getGsmCdmaIndex());
+                mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, cn.getGsmCdmaIndex(),
+                        getNetworkCountryIso());
                 mCi.hangupConnection(index, obtainCompleteMessage());
                 return;
             }
@@ -1368,7 +1372,8 @@
             for (int i = 0; i < count; i++) {
                 GsmCdmaConnection cn = (GsmCdmaConnection)call.mConnections.get(i);
                 if (!cn.mDisconnected) {
-                    mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, cn.getGsmCdmaIndex());
+                    mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, cn.getGsmCdmaIndex(),
+                            getNetworkCountryIso());
                     mCi.hangupConnection(cn.getGsmCdmaIndex(), obtainCompleteMessage());
                 }
             }
@@ -1551,7 +1556,8 @@
                 updatePhoneState();
 
                 mPhone.notifyPreciseCallStateChanged();
-                mMetrics.writeRilCallList(mPhone.getPhoneId(), mDroppedDuringPoll);
+                mMetrics.writeRilCallList(mPhone.getPhoneId(), mDroppedDuringPoll,
+                        getNetworkCountryIso());
                 mDroppedDuringPoll.clear();
             break;
 
@@ -1742,6 +1748,7 @@
     }
 
     @UnsupportedAppUsage
+    @Override
     public GsmCdmaPhone getPhone() {
         return mPhone;
     }
@@ -1797,6 +1804,20 @@
                 MAX_CONNECTIONS_PER_CALL_CDMA;
     }
 
+    private String getNetworkCountryIso() {
+        String countryIso = "";
+        if (mPhone != null) {
+            ServiceStateTracker sst = mPhone.getServiceStateTracker();
+            if (sst != null) {
+                LocaleTracker lt = sst.getLocaleTracker();
+                if (lt != null) {
+                    countryIso = lt.getCurrentCountry();
+                }
+            }
+        }
+        return countryIso;
+    }
+
     /**
      * Called to force the call tracker to cleanup any stale calls.  Does this by triggering
      * {@code GET_CURRENT_CALLS} on the RIL.
diff --git a/src/java/com/android/internal/telephony/GsmCdmaConnection.java b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
index f5f2519..b3dea8e 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
@@ -136,7 +136,7 @@
         mHandler = new MyHandler(mOwner.getLooper());
 
         mAddress = dc.number;
-        setEmergencyCallInfo();
+        setEmergencyCallInfo(mOwner);
 
         mIsIncoming = dc.isMT;
         mCreateTime = System.currentTimeMillis();
@@ -179,7 +179,7 @@
 
         mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
         if (isEmergencyCall) {
-            setEmergencyCallInfo();
+            setEmergencyCallInfo(mOwner);
         }
 
         mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
diff --git a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
index 83d5432..00cea0d 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -236,12 +236,22 @@
     }
 
     private void bindService() {
+        Intent intent = null;
         String packageName = getPackageName();
+        String className = getClassName();
         if (TextUtils.isEmpty(packageName)) {
             loge("Can't find the binding package");
             return;
         }
 
+        if (TextUtils.isEmpty(className)) {
+            intent = new Intent(NetworkService.SERVICE_INTERFACE);
+            intent.setPackage(packageName);
+        } else {
+            ComponentName cm = new ComponentName(packageName, className);
+            intent = new Intent(NetworkService.SERVICE_INTERFACE).setComponent(cm);
+        }
+
         if (TextUtils.equals(packageName, mTargetBindingPackageName)) {
             logd("Service " + packageName + " already bound or being bound.");
             return;
@@ -258,9 +268,6 @@
             mPhone.getContext().unbindService(mServiceConnection);
         }
 
-        Intent intent = new Intent(NetworkService.SERVICE_INTERFACE);
-        intent.setPackage(getPackageName());
-
         try {
             // We bind this as a foreground service because it is operating directly on the SIM,
             // and we do not want it subjected to power-savings restrictions while doing so.
@@ -312,6 +319,39 @@
         return packageName;
     }
 
+    private String getClassName() {
+        String className;
+        int resourceId;
+        String carrierConfig;
+
+        switch (mTransportType) {
+            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
+                resourceId = com.android.internal.R.string.config_wwan_network_service_class;
+                carrierConfig = CarrierConfigManager
+                        .KEY_CARRIER_NETWORK_SERVICE_WWAN_CLASS_OVERRIDE_STRING;
+                break;
+            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
+                resourceId = com.android.internal.R.string.config_wlan_network_service_class;
+                carrierConfig = CarrierConfigManager
+                        .KEY_CARRIER_NETWORK_SERVICE_WLAN_CLASS_OVERRIDE_STRING;
+                break;
+            default:
+                throw new IllegalStateException("Transport type not WWAN or WLAN. type="
+                        + mTransportType);
+        }
+
+        // Read class name from resource overlay
+        className = mPhone.getContext().getResources().getString(resourceId);
+
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
+            // If carrier config overrides it, use the one from carrier config
+            className = b.getString(carrierConfig, className);
+        }
+
+        return className;
+    }
     private void logd(String msg) {
         Rlog.d(mTag, msg);
     }
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 67a2244..387858d 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -4004,6 +4004,29 @@
     }
 
     /**
+     * Check if the device can only make the emergency call. The device is emergency call only if
+     * none of the phone is in service, and one of them has the capability to make the emergency
+     * call.
+     *
+     * @return {@code True} if the device is emergency call only, otherwise return {@code False}.
+     */
+    public static boolean isEmergencyCallOnly() {
+        boolean isEmergencyCallOnly = false;
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (phone != null) {
+                ServiceState ss = phone.getServiceStateTracker().getServiceState();
+                // One of the phone is in service, hence the device is not emergency call only.
+                if (ss.getState() == ServiceState.STATE_IN_SERVICE
+                        || ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+                    return false;
+                }
+                isEmergencyCallOnly |= ss.isEmergencyOnly();
+            }
+        }
+        return isEmergencyCallOnly;
+    }
+
+    /**
      * Get data connection tracker based on the transport type
      *
      * @param transportType Transport type defined in AccessNetworkConstants.TransportType
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index bb425a2..8422d50 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -156,8 +156,7 @@
             "carrierActionDisableMeteredApn";
     static final String REASON_CSS_INDICATOR_CHANGED = "cssIndicatorChanged";
     static final String REASON_RELEASED_BY_CONNECTIVITY_SERVICE = "releasedByConnectivityService";
-    static final String REASON_APN_ADDED_TO_WHITELIST = "apnAddedToWhiteList";
-    static final String REASON_APN_REMOVED_FROM_WHITELIST = "apnRemovedFromWhiteList";
+    static final String REASON_DATA_ENABLED_OVERRIDE = "dataEnabledOverride";
 
     // 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/PhoneSwitcher.java b/src/java/com/android/internal/telephony/PhoneSwitcher.java
index f889578..f8f3232 100644
--- a/src/java/com/android/internal/telephony/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/PhoneSwitcher.java
@@ -54,6 +54,7 @@
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -818,7 +819,8 @@
     // requests.
     private void updatePreferredDataPhoneId() {
         Phone voicePhone = findPhoneById(mPhoneIdInVoiceCall);
-        if (voicePhone != null && voicePhone.isUserDataEnabled()) {
+        if (voicePhone != null && voicePhone.getDataEnabledSettings().isDataEnabled(
+                ApnSetting.TYPE_DEFAULT)) {
             // If a phone is in call and user enabled its mobile data, we
             // should switch internet connection to it. Because the other modem
             // will lose data connection anyway.
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index b6fdbd0..79f2356 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -956,7 +956,6 @@
                     uusInfo, result);
             return;
         }
-
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_DIAL, result,
@@ -1015,7 +1014,9 @@
             try {
                 radioProxy14.emergencyDial(rr.mSerial, dialInfo,
                         emergencyNumberInfo.getEmergencyServiceCategoryBitmaskInternalDial(),
-                        (ArrayList) emergencyNumberInfo.getEmergencyUrns(),
+                        emergencyNumberInfo.getEmergencyUrns() != null
+                                ? new ArrayList(emergencyNumberInfo.getEmergencyUrns())
+                                : new ArrayList<>(),
                         emergencyNumberInfo.getEmergencyCallRouting(),
                         hasKnownUserIntentEmergency,
                         emergencyNumberInfo.getEmergencyNumberSourceBitmask()
@@ -5237,6 +5238,8 @@
                 return "GET_CURRENT_CALLS";
             case RIL_REQUEST_DIAL:
                 return "DIAL";
+            case RIL_REQUEST_EMERGENCY_DIAL:
+                return "EMERGENCY_DIAL";
             case RIL_REQUEST_GET_IMSI:
                 return "GET_IMSI";
             case RIL_REQUEST_HANGUP:
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 4c414d4..8d06ec1 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -124,10 +124,13 @@
     private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4;
 
     /** Send the user confirmed SMS */
-    static final int EVENT_SEND_CONFIRMED_SMS = 5;  // accessed from inner class
+    static final int EVENT_SEND_CONFIRMED_SMS = 5; // accessed from inner class
 
     /** Don't send SMS (user did not confirm). */
-    static final int EVENT_STOP_SENDING = 7;        // accessed from inner class
+    static final int EVENT_STOP_SENDING = 6; // accessed from inner class
+
+    /** Don't send SMS for this app (User had already denied eariler.) */
+    static final int EVENT_SENDING_NOT_ALLOWED = 7;
 
     /** Confirmation required for third-party apps sending to an SMS short code. */
     private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8;
@@ -317,6 +320,16 @@
             break;
         }
 
+        case EVENT_SENDING_NOT_ALLOWED:
+        {
+            SmsTracker tracker = (SmsTracker) msg.obj;
+            Rlog.d(TAG, "SMSDispatcher: EVENT_SENDING_NOT_ALLOWED - "
+                    + "sending SHORT_CODE_NEVER_ALLOWED error code.");
+            tracker.onFailed(
+                    mContext, RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, 0 /*errorCode*/);
+            break;
+        }
+
         case EVENT_STOP_SENDING:
         {
             SmsTracker tracker = (SmsTracker) msg.obj;
@@ -1270,9 +1283,7 @@
 
                 case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW:
                     Rlog.w(TAG, "User denied this app from sending to premium SMS");
-                    Message msg = obtainMessage(EVENT_STOP_SENDING, tracker);
-                    msg.arg1 = ConfirmDialogListener.SHORT_CODE_MSG;
-                    msg.arg2 = ConfirmDialogListener.NEVER_ALLOW;
+                    Message msg = obtainMessage(EVENT_SENDING_NOT_ALLOWED, tracker);
                     sendMessage(msg);
                     return false;   // reject this message
 
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 958f905..3ec24d5 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -2628,7 +2628,7 @@
                 final boolean forceDisplayNoService = mPhone.getContext().getResources().getBoolean(
                         com.android.internal.R.bool.config_display_no_service_when_sim_unready)
                         && !mIsSimReady;
-                if (mEmergencyOnly && !forceDisplayNoService) {
+                if (!forceDisplayNoService && Phone.isEmergencyCallOnly()) {
                     // No service but emergency call allowed
                     plmn = Resources.getSystem().
                             getText(com.android.internal.R.string.emergency_calls_only).toString();
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index e6aad7b..e94346a 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -18,9 +18,9 @@
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
-import static android.telephony.data.ApnSetting.TYPE_MMS;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.app.AppOpsManager;
@@ -41,7 +41,6 @@
 import android.provider.Settings;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
-import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.RadioAccessFamily;
 import android.telephony.Rlog;
@@ -50,7 +49,6 @@
 import android.telephony.TelephonyManager;
 import android.telephony.UiccAccessRule;
 import android.telephony.UiccSlotInfo;
-import android.telephony.data.ApnSetting;
 import android.telephony.euicc.EuiccManager;
 import android.text.TextUtils;
 import android.util.LocalLog;
@@ -58,6 +56,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.dataconnection.DataEnabledOverride;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.UiccCard;
@@ -1715,11 +1714,11 @@
 
     public void syncGroupedSetting(int refSubId) {
         // Currently it only syncs allow MMS. Sync other settings as well if needed.
-        int apnWhiteList = Integer.valueOf(getSubscriptionProperty(
-                refSubId, SubscriptionManager.WHITE_LISTED_APN_DATA, mContext.getOpPackageName()));
+        String dataEnabledOverrideRules = getSubscriptionProperty(
+                refSubId, SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES);
 
         ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.WHITE_LISTED_APN_DATA, apnWhiteList);
+        value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, dataEnabledOverrideRules);
         databaseUpdateHelper(value, refSubId, true);
     }
 
@@ -2540,11 +2539,11 @@
     }
 
     /**
-     * Store properties associated with SubscriptionInfo in database
+     * Get properties associated with SubscriptionInfo from database
+     *
      * @param subId Subscription Id of Subscription
      * @param propKey Column name in SubscriptionInfo database
      * @return Value associated with subId and propKey column in database
-     * @hide
      */
     @Override
     public String getSubscriptionProperty(int subId, String propKey, String callingPackage) {
@@ -2552,14 +2551,29 @@
                 mContext, subId, callingPackage, "getSubscriptionProperty")) {
             return null;
         }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return getSubscriptionProperty(subId, propKey);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Get properties associated with SubscriptionInfo from database. Note this is the version
+     * without permission check for telephony internal use only.
+     *
+     * @param subId Subscription Id of Subscription
+     * @param propKey Column name in SubscriptionInfo database
+     * @return Value associated with subId and propKey column in database
+     */
+    public String getSubscriptionProperty(int subId, String propKey) {
         String resultValue = null;
-        ContentResolver resolver = mContext.getContentResolver();
-        Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
+        try (Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
                 new String[]{propKey},
                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
-                new String[]{subId + ""}, null);
-
-        try {
+                new String[]{subId + ""}, null)) {
             if (cursor != null) {
                 if (cursor.moveToFirst()) {
                     switch (propKey) {
@@ -2586,6 +2600,9 @@
                         case SubscriptionManager.WHITE_LISTED_APN_DATA:
                             resultValue = cursor.getInt(0) + "";
                             break;
+                        case SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES:
+                            resultValue = cursor.getString(0);
+                            break;
                         default:
                             if(DBG) logd("Invalid column name");
                             break;
@@ -2596,11 +2613,8 @@
             } else {
                 if(DBG) logd("Query failed");
             }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
         }
+
         if (DBG) logd("getSubscriptionProperty Query value = " + resultValue);
         return resultValue;
     }
@@ -3419,9 +3433,9 @@
                                 + logicalSlotIndex);
             }
 
-            // Getting physicalSlotIndex
+            // Getting and validating the physicalSlotIndex.
             int physicalSlotIndex = getPhysicalSlotIndexFromLogicalSlotIndex(logicalSlotIndex);
-            if (!SubscriptionManager.isValidSlotIndex(physicalSlotIndex)) {
+            if (physicalSlotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
                 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
             }
 
@@ -3608,6 +3622,8 @@
         }
     }
 
+    // TODO: This method should belong to Telephony manager like other data enabled settings and
+    // override APIs. Remove this once TelephonyManager API is added.
     @Override
     public boolean setAlwaysAllowMmsData(int subId, boolean alwaysAllow) {
         if (DBG) logd("[setAlwaysAllowMmsData]+ alwaysAllow:" + alwaysAllow + " subId:" + subId);
@@ -3618,51 +3634,49 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             validateSubId(subId);
-
-            ContentValues value = new ContentValues(1);
-            int apnWhiteList = Integer.valueOf(getSubscriptionProperty(subId,
-                    SubscriptionManager.WHITE_LISTED_APN_DATA, mContext.getOpPackageName()));
-            apnWhiteList = alwaysAllow ? (apnWhiteList | TYPE_MMS) : (apnWhiteList & ~TYPE_MMS);
-            value.put(SubscriptionManager.WHITE_LISTED_APN_DATA, apnWhiteList);
-            if (DBG) logd("[setAlwaysAllowMmsData]- alwaysAllow:" + alwaysAllow + " set");
-
-            boolean result = databaseUpdateHelper(value, subId, true) > 0;
-
-            if (result) {
-                // Refresh the Cache of Active Subscription Info List
-                refreshCachedActiveSubscriptionInfoList();
-                notifySubscriptionInfoChanged();
-                Phone phone = PhoneFactory.getPhone(getPhoneId(subId));
-                if (phone != null) {
-                    phone.getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                            .notifyApnWhiteListChange(TYPE_MMS, alwaysAllow);
-                }
-            }
-
-            return result;
+            Phone phone = PhoneFactory.getPhone(getPhoneId(subId));
+            if (phone == null) return false;
+            return phone.getDataEnabledSettings().setAlwaysAllowMmsData(alwaysAllow);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
     }
 
     /**
-     *  whether apnType is whitelisted. Being white listed means data connection is allowed
-     *  even if user data is turned off.
+     * Set allowing mobile data during voice call.
+     *
+     * @param subId Subscription index
+     * @param rules Data enabled override rules in string format. See {@link DataEnabledOverride}
+     * for details.
+     * @return {@code true} if settings changed, otherwise {@code false}.
      */
-    public boolean isApnWhiteListed(int subId, String callingPackage, int apnType) {
-        return (getWhiteListedApnDataTypes(subId, callingPackage) & apnType) == apnType;
-    }
+    public boolean setDataEnabledOverrideRules(int subId, @NonNull String rules) {
+        if (DBG) logd("[setDataEnabledOverrideRules]+ rules:" + rules + " subId:" + subId);
 
-    private @ApnSetting.ApnType int getWhiteListedApnDataTypes(int subId, String callingPackage) {
-        String whiteListedApnData = getSubscriptionProperty(subId,
-                SubscriptionManager.WHITE_LISTED_APN_DATA, callingPackage);
+        validateSubId(subId);
+        ContentValues value = new ContentValues(1);
+        value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, rules);
 
-        try {
-            return Integer.valueOf(whiteListedApnData);
-        } catch (NumberFormatException e) {
-            loge("[getWhiteListedApnDataTypes] couldn't parse apn data:" + whiteListedApnData);
+        boolean result = databaseUpdateHelper(value, subId, true) > 0;
+
+        if (result) {
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
+            notifySubscriptionInfoChanged();
         }
 
-        return ApnSetting.TYPE_NONE;
+        return result;
+    }
+
+    /**
+     * Get data enabled override rules.
+     *
+     * @param subId Subscription index
+     * @return Data enabled override rules in string
+     */
+    @NonNull
+    public String getDataEnabledOverrideRules(int subId) {
+        return TextUtils.emptyIfNull(getSubscriptionProperty(subId,
+                SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES));
     }
 }
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
index 63986ad..6b85792 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
@@ -39,6 +39,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.cdnr.EfData.EFSource;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.IccRecords;
@@ -380,8 +381,8 @@
                 com.android.internal.R.bool.config_display_no_service_when_sim_unready)
                 && !isSimReady;
         ServiceState ss = getServiceState();
-        if (ss.getVoiceRegState() == ServiceState.STATE_POWER_OFF || !ss.isEmergencyOnly()
-                || forceDisplayNoService) {
+        if (ss.getVoiceRegState() == ServiceState.STATE_POWER_OFF
+                || forceDisplayNoService || !Phone.isEmergencyCallOnly()) {
             plmn = mContext.getResources().getString(
                     com.android.internal.R.string.lockscreen_carrier_default);
         } else {
diff --git a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
index 421feb9..8b99bfb 100644
--- a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
@@ -238,7 +238,9 @@
      * configuration from carrier config if it exists. If not, read it from resources.
      */
     private void bindQualifiedNetworksService() {
+        Intent intent = null;
         String packageName = getQualifiedNetworksServicePackageName();
+        String className = getQualifiedNetworksServiceClassName();
 
         if (DBG) log("Qualified network service package = " + packageName);
         if (TextUtils.isEmpty(packageName)) {
@@ -246,6 +248,15 @@
             return;
         }
 
+        if (TextUtils.isEmpty(className)) {
+            intent = new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE);
+            intent.setPackage(packageName);
+        } else {
+            ComponentName cm = new ComponentName(packageName, className);
+            intent = new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE)
+                    .setComponent(cm);
+        }
+
         if (TextUtils.equals(packageName, mTargetBindingPackageName)) {
             if (DBG) log("Service " + packageName + " already bound or being bound.");
             return;
@@ -266,11 +277,8 @@
         try {
             mServiceConnection = new QualifiedNetworksServiceConnection();
             log("bind to " + packageName);
-            if (!mPhone.getContext().bindService(
-                    new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE)
-                            .setPackage(packageName),
-                    mServiceConnection,
-                    Context.BIND_AUTO_CREATE)) {
+            if (!mPhone.getContext().bindService(intent, mServiceConnection,
+                        Context.BIND_AUTO_CREATE)) {
                 loge("Cannot bind to the qualified networks service.");
                 return;
             }
@@ -306,6 +314,30 @@
         return packageName;
     }
 
+    /**
+     * Get the qualified network service class name.
+     *
+     * @return class name of the qualified networks service package.
+     */
+    private String getQualifiedNetworksServiceClassName() {
+        // Read package name from the resource
+        String className = mPhone.getContext().getResources().getString(
+                com.android.internal.R.string.config_qualified_networks_service_class);
+
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+        if (b != null) {
+            // If carrier config overrides it, use the one from carrier config
+            String carrierConfigClassName =  b.getString(CarrierConfigManager
+                    .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING);
+            if (!TextUtils.isEmpty(carrierConfigClassName)) {
+                if (DBG) log("Found carrier config override " + carrierConfigClassName);
+                className = carrierConfigClassName;
+            }
+        }
+
+        return className;
+    }
 
     private @NonNull List<QualifiedNetworks> getQualifiedNetworksList() {
         List<QualifiedNetworks> qualifiedNetworksList = new ArrayList<>();
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java b/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java
new file mode 100644
index 0000000..2ed9bfe
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright 2019 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.dataconnection;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.ApnSetting.ApnType;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.dataconnection.DataEnabledOverride.OverrideConditions.Condition;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class represents the rules for overriding data enabled settings in different conditions.
+ * When data is disabled by the user, data can still be turned on temporarily when conditions
+ * satisfy any rule here.
+ */
+public class DataEnabledOverride {
+
+    private final Set<OverrideRule> mRules = new HashSet<>();
+
+    /**
+     * The rule for allowing data during voice call.
+     */
+    private static final OverrideRule OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL =
+            new OverrideRule(ApnSetting.TYPE_ALL, OverrideConditions.CONDITION_IN_VOICE_CALL
+                    | OverrideConditions.CONDITION_NON_DEFAULT);
+
+    /**
+     * The rule for always allowing mms. Without adding any condition to the rule, any condition can
+     * satisfy this rule for mms.
+     */
+    private static final OverrideRule OVERRIDE_RULE_ALWAYS_ALLOW_MMS =
+            new OverrideRule(ApnSetting.TYPE_MMS, OverrideConditions.CONDITION_UNCONDITIONALLY);
+
+    /**
+     * Data enabled override rule
+     */
+    private static class OverrideRule {
+        /**
+         * APN type of the rule. The rule is APN type specific. The override is applicable to the
+         * specified APN type as well. For now we only support one APN type per rule. Can be
+         * expanded to multiple APN types in the future.
+         */
+        private final @ApnType int mApnType;
+
+        /** The required conditions for overriding */
+        private final OverrideConditions mRequiredConditions;
+
+        /**
+         * Constructor
+         *
+         * @param rule The override rule string. For example, {@code mms=nonDefault} or
+         * {@code default=voiceCall & nonDefault}
+         */
+        OverrideRule(@NonNull String rule) {
+            String[] tokens = rule.trim().split("\\s*=\\s*");
+            if (tokens.length != 2) {
+                throw new IllegalArgumentException("Invalid data enabled override rule format: "
+                        + rule);
+            }
+
+            if (TextUtils.isEmpty(tokens[0])) {
+                throw new IllegalArgumentException("APN type can't be empty");
+            }
+
+            mApnType = ApnSetting.getApnTypesBitmaskFromString(tokens[0]);
+            if (mApnType == ApnSetting.TYPE_NONE) {
+                throw new IllegalArgumentException("Invalid APN type. Rule=" + rule);
+            }
+
+            mRequiredConditions = new OverrideConditions(tokens[1]);
+        }
+
+        /**
+         * Constructor
+         *
+         * @param apnType APN type of the rule
+         * @param requiredConditions The required conditions for the rule
+         */
+        private OverrideRule(int apnType, int requiredConditions) {
+            mApnType = apnType;
+            mRequiredConditions = new OverrideConditions(requiredConditions);
+        }
+
+        /**
+         * Check if this rule can be satisfied by the given APN type and provided conditions.
+         *
+         * @param apnType APN type to check
+         * @param providedConditions The provided conditions to check
+         * @return {@code true} if satisfied
+         */
+        boolean isSatisfiedByConditions(@ApnType int apnType, @Condition int providedConditions) {
+            return (mApnType == apnType || mApnType == ApnSetting.TYPE_ALL)
+                    && mRequiredConditions.allMet(providedConditions);
+        }
+
+        @Override
+        public String toString() {
+            return ApnSetting.getApnTypeString(mApnType) + "=" + mRequiredConditions;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            OverrideRule that = (OverrideRule) o;
+            return mApnType == that.mApnType
+                    && Objects.equals(mRequiredConditions, that.mRequiredConditions);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mApnType, mRequiredConditions);
+        }
+    }
+
+    /**
+     * Represent the conditions for overriding data enabled settings
+     */
+    static class OverrideConditions {
+        // Possible values for data enabled override condition. Note these flags are bitmasks.
+        /** Unconditionally override enabled settings */
+        static final int CONDITION_UNCONDITIONALLY = 0;
+
+        /** Enable data only on subscription that is not user selected default data subscription */
+        static final int CONDITION_NON_DEFAULT = 1 << 0;
+
+        /** Enable data only when device has ongoing voice call */
+        static final int CONDITION_IN_VOICE_CALL = 1 << 1;
+
+        /** Enable data unconditionally in string format */
+        static final String CONDITION_UNCONDITIONALLY_STRING = "unconditionally";
+
+        /** Enable data only on subscription that is not default in string format */
+        static final String CONDITION_NON_DEFAULT_STRING = "nonDefault";
+
+        /** Enable data only when device has ongoing voice call in string format */
+        static final String CONDITION_VOICE_CALL_STRING = "inVoiceCall";
+
+        /** @hide */
+        @IntDef(flag = true, prefix = { "OVERRIDE_CONDITION_" }, value = {
+                CONDITION_NON_DEFAULT,
+                CONDITION_IN_VOICE_CALL,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Condition {}
+
+        private static final Map<Integer, String> OVERRIDE_CONDITION_INT_MAP = new ArrayMap<>();
+        private static final Map<String, Integer> OVERRIDE_CONDITION_STRING_MAP = new ArrayMap<>();
+
+        static {
+            OVERRIDE_CONDITION_INT_MAP.put(CONDITION_NON_DEFAULT,
+                    CONDITION_NON_DEFAULT_STRING);
+            OVERRIDE_CONDITION_INT_MAP.put(CONDITION_IN_VOICE_CALL,
+                    CONDITION_VOICE_CALL_STRING);
+
+            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_UNCONDITIONALLY_STRING,
+                    CONDITION_UNCONDITIONALLY);
+            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_NON_DEFAULT_STRING,
+                    CONDITION_NON_DEFAULT);
+            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_VOICE_CALL_STRING,
+                    CONDITION_IN_VOICE_CALL);
+        }
+
+        private final @Condition int mConditions;
+
+        /**
+         * Conditions for overriding data enabled setting
+         *
+         * @param conditions Conditions in string format
+         */
+        OverrideConditions(@NonNull String conditions) {
+            mConditions = getBitmaskFromString(conditions);
+        }
+
+        /**
+         * Conditions for overriding data enabled setting
+         *
+         * @param conditions Conditions in bitmask
+         */
+        OverrideConditions(@Condition int conditions) {
+            mConditions = conditions;
+        }
+
+        private static String getStringFromBitmask(@Condition int conditions) {
+            if (conditions == CONDITION_UNCONDITIONALLY) {
+                return CONDITION_UNCONDITIONALLY_STRING;
+            }
+            List<String> conditionsStrings = new ArrayList<>();
+            for (Integer condition : OVERRIDE_CONDITION_INT_MAP.keySet()) {
+                if ((conditions & condition) == condition) {
+                    conditionsStrings.add(OVERRIDE_CONDITION_INT_MAP.get(condition));
+                }
+            }
+            return TextUtils.join("&", conditionsStrings);
+        }
+
+        private static @Condition int getBitmaskFromString(@NonNull String str) {
+            if (TextUtils.isEmpty(str)) {
+                throw new IllegalArgumentException("Empty rule string");
+            }
+
+            String[] conditionStrings = str.trim().split("\\s*&\\s*");
+            int bitmask = 0;
+
+            for (String conditionStr : conditionStrings) {
+                if (!TextUtils.isEmpty(conditionStr)) {
+                    if (!OVERRIDE_CONDITION_STRING_MAP.containsKey(conditionStr)) {
+                        throw new IllegalArgumentException("Invalid conditions: " + str);
+                    }
+                    bitmask |= OVERRIDE_CONDITION_STRING_MAP.get(conditionStr);
+                }
+            }
+
+            return bitmask;
+        }
+
+        /**
+         * Check if provided conditions can meet all conditions in the rule.
+         *
+         * @param providedConditions The provided conditions
+         * @return {@code true} if all conditions are met.
+         */
+        boolean allMet(@Condition int providedConditions) {
+            return (providedConditions & mConditions) == mConditions;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            OverrideConditions that = (OverrideConditions) o;
+            return mConditions == that.mConditions;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mConditions);
+        }
+
+        @Override
+        public String toString() {
+            return getStringFromBitmask(mConditions);
+        }
+    }
+
+    /**
+     * Constructor
+     *
+     * @param rules Data enabled override rules
+     */
+    public DataEnabledOverride(@NonNull String rules) {
+        updateRules(rules);
+    }
+
+    /**
+     * Update the data enabled override rules.
+     *
+     * @param newRules New override rules
+     */
+    @VisibleForTesting
+    public void updateRules(@NonNull String newRules) {
+        mRules.clear();
+        String[] rulesString = newRules.trim().split("\\s*,\\s*");
+        for (String rule : rulesString) {
+            if (!TextUtils.isEmpty(rule)) {
+                mRules.add(new OverrideRule(rule));
+            }
+        }
+    }
+
+    /**
+     * Set always allowing MMS
+     *
+     * @param allow {@code true} if always allowing, otherwise {@code false}.
+     */
+    public void setAlwaysAllowMms(boolean allow) {
+        if (allow) {
+            mRules.add(OVERRIDE_RULE_ALWAYS_ALLOW_MMS);
+        } else {
+            mRules.remove(OVERRIDE_RULE_ALWAYS_ALLOW_MMS);
+        }
+    }
+
+    /**
+     * Set allowing mobile data during voice call.
+     *
+     * @param allow {@code true} if allowing using data during voice call, {@code false} if
+     * disallowed.
+     */
+    public void setDataAllowedInVoiceCall(boolean allow) {
+        if (allow) {
+            mRules.add(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL);
+        } else {
+            mRules.remove(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL);
+        }
+    }
+
+    /**
+     * Check if data is allowed during voice call.
+     *
+     * @return {@code true} if data is allowed during voice call.
+     */
+    public boolean isDataAllowedInVoiceCall() {
+        return mRules.contains(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL);
+    }
+
+    private boolean canSatisfyAnyRule(@ApnType int apnType,
+                                      @Condition int providedConditions) {
+        for (OverrideRule rule : mRules) {
+            if (rule.isSatisfiedByConditions(apnType, providedConditions)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private @Condition int getCurrentConditions(Phone phone) {
+        int conditions = 0;
+
+        if (phone != null) {
+            // Check if the device is on voice call
+            if (phone.getCallTracker().getState() != PhoneConstants.State.IDLE) {
+                conditions |= OverrideConditions.CONDITION_IN_VOICE_CALL;
+            }
+
+            if (phone.getSubId() != SubscriptionController.getInstance().getDefaultDataSubId()) {
+                conditions |= OverrideConditions.CONDITION_NON_DEFAULT;
+            }
+        }
+
+        return conditions;
+    }
+
+    /**
+     * Check for given APN type if we should enable data.
+     *
+     * @param phone Phone object
+     * @param apnType APN type
+     * @return {@code true} if data should be enabled for the current condition.
+     */
+    public boolean shouldOverrideDataEnabledSettings(Phone phone, @ApnType int apnType) {
+        return canSatisfyAnyRule(apnType, getCurrentConditions(phone));
+    }
+
+    /**
+     * Get data enabled override rules.
+     *
+     * @return Get data enabled override rules in string format
+     */
+    @NonNull
+    public String getRules() {
+        List<String> ruleStrings = new ArrayList<>();
+        for (OverrideRule rule : mRules) {
+            ruleStrings.add(rule.toString());
+        }
+        return TextUtils.join(",", ruleStrings);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        DataEnabledOverride that = (DataEnabledOverride) o;
+        return mRules.equals(that.mRules);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRules);
+    }
+
+    @Override
+    public String toString() {
+        return "DataEnabledOverride: [rules=\"" + getRules() + "\"]";
+    }
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
index 1f5dba9..b0be081 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
@@ -27,6 +27,8 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.data.ApnSetting;
 import android.util.LocalLog;
 import android.util.Pair;
 
@@ -97,30 +99,70 @@
 
     private final Phone mPhone;
 
+    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
     private ContentResolver mResolver = null;
 
     private final RegistrantList mOverallDataEnabledChangedRegistrants = new RegistrantList();
 
+    // TODO: Merge this with mOverallDataEnabledChangedRegistrants. In the future, notifying data
+    // enabled changed with APN types bitmask
+    private final RegistrantList mOverallDataEnabledOverrideChangedRegistrants =
+            new RegistrantList();
+
     private final LocalLog mSettingChangeLocalLog = new LocalLog(50);
 
+    private DataEnabledOverride mDataEnabledOverride;
+
+    // for msim, user data enabled setting depends on subId.
+    private final SubscriptionManager.OnSubscriptionsChangedListener
+            mOnSubscriptionsChangeListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                @Override
+                public void onSubscriptionsChanged() {
+                    synchronized (this) {
+                        if (mSubId != mPhone.getSubId()) {
+                            log("onSubscriptionsChanged subId: " + mSubId + " to: "
+                                    + mPhone.getSubId());
+                            mSubId = mPhone.getSubId();
+                            mDataEnabledOverride = getDataEnabledOverride();
+                            updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED);
+                            mPhone.notifyUserMobileDataStateChanged(isUserDataEnabled());
+                        }
+                    }
+                }
+            };
+
     @Override
     public String toString() {
         return "[mInternalDataEnabled=" + mInternalDataEnabled
                 + ", isUserDataEnabled=" + isUserDataEnabled()
                 + ", isProvisioningDataEnabled=" + isProvisioningDataEnabled()
                 + ", mPolicyDataEnabled=" + mPolicyDataEnabled
-                + ", mCarrierDataEnabled=" + mCarrierDataEnabled + "]";
+                + ", mCarrierDataEnabled=" + mCarrierDataEnabled
+                + ", mIsDataEnabled=" + mIsDataEnabled
+                + ", " + mDataEnabledOverride
+                + "]";
     }
 
     public DataEnabledSettings(Phone phone) {
         mPhone = phone;
         mResolver = mPhone.getContext().getContentResolver();
+        SubscriptionManager subscriptionManager = (SubscriptionManager) mPhone.getContext()
+                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        subscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+        mDataEnabledOverride = getDataEnabledOverride();
         updateDataEnabled();
     }
 
+    private DataEnabledOverride getDataEnabledOverride() {
+        return new DataEnabledOverride(SubscriptionController.getInstance()
+                .getDataEnabledOverrideRules(mPhone.getSubId()));
+    }
+
     public synchronized void setInternalDataEnabled(boolean enabled) {
-        localLog("InternalDataEnabled", enabled);
         if (mInternalDataEnabled != enabled) {
+            localLog("InternalDataEnabled", enabled);
             mInternalDataEnabled = enabled;
             updateDataEnabledAndNotify(REASON_INTERNAL_DATA_ENABLED);
         }
@@ -133,10 +175,10 @@
         // Can't disable data for stand alone opportunistic subscription.
         if (isStandAloneOpportunistic(mPhone.getSubId(), mPhone.getContext()) && !enabled) return;
 
-        localLog("UserDataEnabled", enabled);
         boolean changed = GlobalSettingsHelper.setInt(mPhone.getContext(),
                 Settings.Global.MOBILE_DATA, mPhone.getSubId(), (enabled ? 1 : 0));
         if (changed) {
+            localLog("UserDataEnabled", enabled);
             mPhone.notifyUserMobileDataStateChanged(enabled);
             updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED);
             MultiSimSettingController.getInstance().notifyUserDataEnabled(mPhone.getSubId(),
@@ -155,9 +197,59 @@
                 Settings.Global.MOBILE_DATA, mPhone.getSubId(), defaultVal);
     }
 
+    /**
+     * Set whether always allowing MMS data connection.
+     *
+     * @param alwaysAllow {@code true} if MMS data is always allowed.
+     *
+     * @return {@code false} if the setting is changed.
+     */
+    public synchronized boolean setAlwaysAllowMmsData(boolean alwaysAllow) {
+        localLog("setAlwaysAllowMmsData", alwaysAllow);
+        mDataEnabledOverride.setAlwaysAllowMms(alwaysAllow);
+        boolean changed = SubscriptionController.getInstance()
+                .setDataEnabledOverrideRules(mPhone.getSubId(), mDataEnabledOverride.getRules());
+        if (changed) {
+            updateDataEnabled();
+            notifyDataEnabledOverrideChanged();
+        }
+
+        return changed;
+    }
+
+    /**
+     * Set allowing mobile data during voice call.
+     *
+     * @param allow {@code true} if allowing using data during voice call, {@code false} if
+     * disallowed
+     *
+     * @return {@code false} if the setting is changed.
+     */
+    public synchronized boolean setAllowDataDuringVoiceCall(boolean allow) {
+        localLog("setAllowDataDuringVoiceCall", allow);
+        mDataEnabledOverride.setDataAllowedInVoiceCall(allow);
+        boolean changed = SubscriptionController.getInstance()
+                .setDataEnabledOverrideRules(mPhone.getSubId(), mDataEnabledOverride.getRules());
+        if (changed) {
+            updateDataEnabled();
+            notifyDataEnabledOverrideChanged();
+        }
+
+        return changed;
+    }
+
+    /**
+     * Check if data is allowed during voice call.
+     *
+     * @return {@code true} if data is allowed during voice call.
+     */
+    public synchronized boolean isDataAllowedInVoiceCall() {
+        return mDataEnabledOverride.isDataAllowedInVoiceCall();
+    }
+
     public synchronized void setPolicyDataEnabled(boolean enabled) {
-        localLog("PolicyDataEnabled", enabled);
         if (mPolicyDataEnabled != enabled) {
+            localLog("PolicyDataEnabled", enabled);
             mPolicyDataEnabled = enabled;
             updateDataEnabledAndNotify(REASON_POLICY_DATA_ENABLED);
         }
@@ -168,8 +260,8 @@
     }
 
     public synchronized void setCarrierDataEnabled(boolean enabled) {
-        localLog("CarrierDataEnabled", enabled);
         if (mCarrierDataEnabled != enabled) {
+            localLog("CarrierDataEnabled", enabled);
             mCarrierDataEnabled = enabled;
             updateDataEnabledAndNotify(REASON_DATA_ENABLED_BY_CARRIER);
         }
@@ -205,7 +297,8 @@
         if (isProvisioning()) {
             mIsDataEnabled = isProvisioningDataEnabled();
         } else {
-            mIsDataEnabled = mInternalDataEnabled && isUserDataEnabled()
+            mIsDataEnabled = mInternalDataEnabled && (isUserDataEnabled() || mDataEnabledOverride
+                    .shouldOverrideDataEnabledSettings(mPhone, ApnSetting.TYPE_ALL))
                     && mPolicyDataEnabled && mCarrierDataEnabled;
         }
     }
@@ -235,13 +328,12 @@
     }
 
     public synchronized void setDataRoamingEnabled(boolean enabled) {
-        localLog("setDataRoamingEnabled", enabled);
-
         // will trigger handleDataOnRoamingChange() through observer
         boolean changed = GlobalSettingsHelper.setBoolean(mPhone.getContext(),
                 Settings.Global.DATA_ROAMING, mPhone.getSubId(), enabled);
 
         if (changed) {
+            localLog("setDataRoamingEnabled", enabled);
             MultiSimSettingController.getInstance().notifyRoamingDataEnabled(mPhone.getSubId(),
                     enabled);
         }
@@ -284,6 +376,30 @@
         mOverallDataEnabledChangedRegistrants.remove(h);
     }
 
+    private void notifyDataEnabledOverrideChanged() {
+        mOverallDataEnabledOverrideChangedRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * Register for data enabled override changed event.
+     *
+     * @param h The handler
+     * @param what The event
+     */
+    public void registerForDataEnabledOverrideChanged(Handler h, int what) {
+        mOverallDataEnabledOverrideChangedRegistrants.addUnique(h, what, null);
+        notifyDataEnabledOverrideChanged();
+    }
+
+    /**
+     * Unregistered for data enabled override changed event.
+     *
+     * @param h The handler
+     */
+    public void unregisterForDataEnabledOverrideChanged(Handler h) {
+        mOverallDataEnabledOverrideChangedRegistrants.remove(h);
+    }
+
     private static boolean isStandAloneOpportunistic(int subId, Context context) {
         SubscriptionInfo info = SubscriptionController.getInstance().getActiveSubscriptionInfo(
                 subId, context.getOpPackageName());
@@ -292,11 +408,12 @@
 
     public synchronized boolean isDataEnabled(int apnType) {
         boolean userDataEnabled = isUserDataEnabled();
-        boolean isApnWhiteListed = SubscriptionController.getInstance().isApnWhiteListed(
-                mPhone.getSubId(), mPhone.getContext().getOpPackageName(), apnType);
+        // Check if we should temporarily enable data in certain conditions.
+        boolean isDataEnabledOverridden = mDataEnabledOverride
+                .shouldOverrideDataEnabledSettings(mPhone, apnType);
 
         return (mInternalDataEnabled && mPolicyDataEnabled && mCarrierDataEnabled
-                && (userDataEnabled || isApnWhiteListed));
+                && (userDataEnabled || isDataEnabledOverridden));
     }
 
     private void log(String s) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
index 716331c..90b4600 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
@@ -285,12 +285,22 @@
     }
 
     private void bindDataService() {
+        Intent intent = null;
         String packageName = getDataServicePackageName();
+        String className = getDataServiceClassName();
         if (TextUtils.isEmpty(packageName)) {
             loge("Can't find the binding package");
             return;
         }
 
+        if (TextUtils.isEmpty(className)) {
+            intent = new Intent(DataService.SERVICE_INTERFACE);
+            intent.setPackage(packageName);
+        } else {
+            ComponentName cm = new ComponentName(packageName, className);
+            intent = new Intent(DataService.SERVICE_INTERFACE).setComponent(cm);
+        }
+
         if (TextUtils.equals(packageName, mTargetBindingPackageName)) {
             if (DBG) log("Service " + packageName + " already bound or being bound.");
             return;
@@ -316,9 +326,7 @@
         try {
             mServiceConnection = new CellularDataServiceConnection();
             if (!mPhone.getContext().bindService(
-                    new Intent(DataService.SERVICE_INTERFACE).setPackage(packageName),
-                    mServiceConnection,
-                    Context.BIND_AUTO_CREATE)) {
+                    intent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
                 loge("Cannot bind to the data service.");
                 return;
             }
@@ -399,6 +407,55 @@
         return packageName;
     }
 
+    /**
+     * Get the data service class name for our current transport type.
+     *
+     * @return class name of the data service package for the the current transportType.
+     */
+    private String getDataServiceClassName() {
+        return getDataServiceClassName(mTransportType);
+    }
+
+
+    /**
+     * Get the data service class by transport type.
+     *
+     * @param transportType either WWAN or WLAN
+     * @return class name of the data service package for the specified transportType.
+     */
+    private String getDataServiceClassName(int transportType) {
+        String className;
+        int resourceId;
+        String carrierConfig;
+        switch (transportType) {
+            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
+                resourceId = com.android.internal.R.string.config_wwan_data_service_class;
+                carrierConfig = CarrierConfigManager
+                        .KEY_CARRIER_DATA_SERVICE_WWAN_CLASS_OVERRIDE_STRING;
+                break;
+            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
+                resourceId = com.android.internal.R.string.config_wlan_data_service_class;
+                carrierConfig = CarrierConfigManager
+                        .KEY_CARRIER_DATA_SERVICE_WLAN_CLASS_OVERRIDE_STRING;
+                break;
+            default:
+                throw new IllegalStateException("Transport type not WWAN or WLAN. type="
+                        + transportType);
+        }
+
+        // Read package name from resource overlay
+        className = mPhone.getContext().getResources().getString(resourceId);
+
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
+            // If carrier config overrides it, use the one from carrier config
+            className = b.getString(carrierConfig, className);
+        }
+
+        return className;
+    }
+
     private void sendCompleteMessage(Message msg, int code) {
         if (msg != null) {
             msg.arg1 = code;
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 686dc9a..9bf8c8f 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -69,6 +69,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
 import android.telephony.DataFailCause;
+import android.telephony.DataFailCause.FailCause;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PcoData;
 import android.telephony.Rlog;
@@ -161,15 +162,15 @@
     public @interface RequestNetworkType {}
 
     /**
-     * Normal request for {@link #requestNetwork(NetworkRequest, int, LocalLog)}. For request
+     * Normal request for {@link #requestNetwork(NetworkRequest, int, Message)}. For request
      * network, this adds the request to the {@link ApnContext}. If there were no network request
      * attached to the {@link ApnContext} earlier, this request setups a data connection.
      */
     public static final int REQUEST_TYPE_NORMAL = 1;
 
     /**
-     * Handover request for {@link #requestNetwork(NetworkRequest, int, LocalLog)} or
-     * {@link #releaseNetwork(NetworkRequest, int, LocalLog)}. For request network, this
+     * Handover request for {@link #requestNetwork(NetworkRequest, int, Message)} or
+     * {@link #releaseNetwork(NetworkRequest, int)}. For request network, this
      * initiates the handover data setup process. The existing data connection will be seamlessly
      * handover to the new network. For release network, this performs a data connection softly
      * clean up at the underlying layer (versus normal data release).
@@ -192,7 +193,7 @@
     public static final int RELEASE_TYPE_NORMAL = 1;
 
     /**
-     * Detach request for {@link #releaseNetwork(NetworkRequest, int, LocalLog)} only. This
+     * Detach request for {@link #releaseNetwork(NetworkRequest, int)} only. This
      * forces the APN context detach from the data connection. If this {@link ApnContext} is the
      * last one attached to the data connection, the data connection will be torn down, otherwise
      * the data connection remains active.
@@ -200,7 +201,7 @@
     public static final int RELEASE_TYPE_DETACH = 2;
 
     /**
-     * Handover request for {@link #releaseNetwork(NetworkRequest, int, LocalLog)}. For release
+     * Handover request for {@link #releaseNetwork(NetworkRequest, int)}. For release
      * network, this performs a data connection softly clean up at the underlying layer (versus
      * normal data release).
      */
@@ -211,6 +212,12 @@
     static final String DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE = "extra_transport_type";
     static final String DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE = "extra_request_type";
     static final String DATA_COMPLETE_MSG_EXTRA_SUCCESS = "extra_success";
+    /**
+     * The flag indicates whether after handover failure, the data connection should remain on the
+     * original transport.
+     */
+    static final String DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK =
+            "extra_handover_failure_fallback";
 
     private final String mLogTag;
 
@@ -708,6 +715,8 @@
 
         mDataEnabledSettings.registerForDataEnabledChanged(this,
                 DctConstants.EVENT_DATA_ENABLED_CHANGED, null);
+        mDataEnabledSettings.registerForDataEnabledOverrideChanged(this,
+                DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED);
 
         mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
 
@@ -863,6 +872,9 @@
         mPhone.getCallTracker().unregisterForVoiceCallStarted(this);
         unregisterServiceStateTrackerEvents();
         mDataServiceManager.unregisterForServiceBindingChanged(this);
+
+        mDataEnabledSettings.unregisterForDataEnabledChanged(this);
+        mDataEnabledSettings.unregisterForDataEnabledOverrideChanged(this);
     }
 
     /**
@@ -1829,7 +1841,7 @@
         }
     }
 
-    boolean isPermanentFailure(@DataFailCause.FailCause int dcFailCause) {
+    boolean isPermanentFailure(@FailCause int dcFailCause) {
         return (DataFailCause.isPermanentFailure(mPhone.getContext(), dcFailCause,
                 mPhone.getSubId())
                 && (mAttached.get() == false || dcFailCause != DataFailCause.SIGNAL_LOST));
@@ -2183,7 +2195,7 @@
                 SystemClock.elapsedRealtime() + delay, alarmIntent);
     }
 
-    private void notifyNoData(@DataFailCause.FailCause int lastFailCauseCode,
+    private void notifyNoData(@FailCause int lastFailCauseCode,
                               ApnContext apnContext) {
         if (DBG) log( "notifyNoData: type=" + apnContext.getApnType());
         if (isPermanentFailure(lastFailCauseCode)
@@ -2313,13 +2325,19 @@
 
     private void sendRequestNetworkCompleteMsg(Message message, boolean success,
                                                @TransportType int transport,
-                                               @RequestNetworkType int requestType) {
+                                               @RequestNetworkType int requestType,
+                                               @FailCause int cause) {
         if (message == null) return;
 
         Bundle b = message.getData();
         b.putBoolean(DATA_COMPLETE_MSG_EXTRA_SUCCESS, success);
         b.putInt(DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE, requestType);
         b.putInt(DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE, transport);
+        // TODO: For now this is the only fail cause that we know modem keeps data connection on
+        // original transport. Might add more complicated logic or mapping in the future.
+        b.putBoolean(DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK,
+                (requestType == REQUEST_TYPE_HANDOVER
+                        && cause == DataFailCause.HANDOFF_PREFERENCE_CHANGED));
         message.sendToTarget();
     }
 
@@ -2334,7 +2352,8 @@
         ApnContext apnContext = mApnContextsByType.get(apnType);
         if (apnContext == null) {
             loge("onEnableApn(" + apnType + "): NO ApnContext");
-            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType);
+            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType,
+                    DataFailCause.NONE);
             return;
         }
 
@@ -2349,7 +2368,8 @@
             str = "onEnableApn: dependency is not met.";
             if (DBG) log(str);
             apnContext.requestLog(str);
-            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType);
+            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType,
+                    DataFailCause.NONE);
             return;
         }
 
@@ -2363,16 +2383,15 @@
                     return;
                 case CONNECTED:
                     if (DBG) log("onEnableApn: 'CONNECTED' so return");
-                    apnContext.requestLog("onEnableApn state=CONNECTED, so return");
-
+                    // Don't add to local log since this is so common
                     sendRequestNetworkCompleteMsg(onCompleteMsg, true, mTransportType,
-                            requestType);
+                            requestType, DataFailCause.NONE);
                     return;
                 case DISCONNECTING:
                     if (DBG) log("onEnableApn: 'DISCONNECTING' so return");
                     apnContext.requestLog("onEnableApn state=DISCONNECTING, so return");
                     sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType,
-                            requestType);
+                            requestType, DataFailCause.NONE);
                     return;
                 case IDLE:
                     // fall through: this is unexpected but if it happens cleanup and try setup
@@ -2399,7 +2418,7 @@
             addRequestNetworkCompleteMsg(onCompleteMsg, apnType);
         } else {
             sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType,
-                    requestType);
+                    requestType, DataFailCause.NONE);
         }
     }
 
@@ -2689,7 +2708,7 @@
         List<Message> messageList = mRequestNetworkCompletionMsgs.get(apnType);
         if (messageList != null) {
             for (Message msg : messageList) {
-                sendRequestNetworkCompleteMsg(msg, success, mTransportType, requestType);
+                sendRequestNetworkCompleteMsg(msg, success, mTransportType, requestType, cause);
             }
             messageList.clear();
         }
@@ -3774,10 +3793,8 @@
                     onDataEnabledChanged(enabled, reason);
                 }
                 break;
-            case DctConstants.EVENT_APN_WHITE_LIST_CHANGE:
-                int apnType = msg.arg1;
-                boolean enable = msg.arg2 == 1;
-                onApnWhiteListChange(apnType, enable);
+            case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
+                onDataEnabledOverrideRulesChanged();
                 break;
             default:
                 Rlog.e("DcTracker", "Unhandled event=" + msg);
@@ -4265,30 +4282,22 @@
         setActivity(activity);
     }
 
-    public void notifyApnWhiteListChange(int apnType, boolean enable) {
-        Message msg = obtainMessage(DctConstants.EVENT_APN_WHITE_LIST_CHANGE);
-        msg.arg1 = apnType;
-        msg.arg2 = enable ? 1 : 0;
-        sendMessage(msg);
-    }
-
-    private void onApnWhiteListChange(int apnType, boolean enable) {
+    private void onDataEnabledOverrideRulesChanged() {
         if (DBG) {
-            log("onApnWhiteListChange: enable=" + enable + ", apnType=" + apnType);
+            log("onDataEnabledOverrideRulesChanged");
         }
 
-        final ApnContext apnContext = mApnContextsByType.get(apnType);
-        if (apnContext == null) return;
-
-        if (isDataAllowed(apnContext, REQUEST_TYPE_NORMAL, null)) {
-            if (apnContext.getDataConnection() != null) {
-                apnContext.getDataConnection().reevaluateRestrictedState();
+        for (ApnContext apnContext : mPrioritySortedApnContexts) {
+            if (isDataAllowed(apnContext, REQUEST_TYPE_NORMAL, null)) {
+                if (apnContext.getDataConnection() != null) {
+                    apnContext.getDataConnection().reevaluateRestrictedState();
+                }
+                setupDataOnConnectableApn(apnContext, Phone.REASON_DATA_ENABLED_OVERRIDE,
+                        RetryFailures.ALWAYS);
+            } else if (shouldCleanUpConnection(apnContext, true)) {
+                apnContext.setReason(Phone.REASON_DATA_ENABLED_OVERRIDE);
+                cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, apnContext);
             }
-            setupDataOnConnectableApn(apnContext, Phone.REASON_APN_ADDED_TO_WHITELIST,
-                    RetryFailures.ALWAYS);
-        } else if (shouldCleanUpConnection(apnContext, true)) {
-            apnContext.setReason(Phone.REASON_APN_REMOVED_FROM_WHITELIST);
-            cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, apnContext);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
index d86e676..28aa3c7 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
@@ -181,9 +181,12 @@
                                 DcTracker.DATA_COMPLETE_MSG_EXTRA_SUCCESS);
                         int transport = bundle.getInt(
                                 DcTracker.DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE);
+                        boolean fallback = bundle.getBoolean(
+                                DcTracker.DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK);
                         HandoverParams handoverParams = mPendingHandovers.remove(msg);
                         if (handoverParams != null) {
-                            onDataHandoverSetupCompleted(nr, success, transport, handoverParams);
+                            onDataHandoverSetupCompleted(nr, success, transport, fallback,
+                                    handoverParams);
                         } else {
                             logl("Handover completed but cannot find handover entry!");
                         }
@@ -375,34 +378,37 @@
 
         if (!handoverPending) {
             log("No handover request pending. Handover process is now completed");
-            handoverParams.callback.onCompleted(true);
+            handoverParams.callback.onCompleted(true, false);
         }
     }
 
     private void onDataHandoverSetupCompleted(NetworkRequest networkRequest, boolean success,
-                                              int targetTransport, HandoverParams handoverParams) {
+                                              int targetTransport, boolean fallback,
+                                              HandoverParams handoverParams) {
         log("onDataHandoverSetupCompleted: " + networkRequest + ", success=" + success
                 + ", targetTransport="
-                + AccessNetworkConstants.transportTypeToString(targetTransport));
+                + AccessNetworkConstants.transportTypeToString(targetTransport)
+                + ", fallback=" + fallback);
 
-        // At this point, handover setup has been completed on the target transport. No matter
-        // succeeded or not, remove the request from the source transport because even the setup
-        // failed on target transport, we can retry again there.
+        // At this point, handover setup has been completed on the target transport.
+        // If it succeeded, or it failed without falling back to the original transport,
+        // we should release the request from the original transport.
+        if (!fallback) {
+            int originTransport = (targetTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                    ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+                    : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+            int releaseType = success
+                    ? DcTracker.RELEASE_TYPE_HANDOVER
+                    // If handover fails, we need to tear down the existing connection, so the
+                    // new data connection can be re-established on the new transport. If we leave
+                    // the existing data connection in current transport, then DCT and qualified
+                    // network service will be out of sync.
+                    : DcTracker.RELEASE_TYPE_NORMAL;
+            releaseNetworkInternal(networkRequest, releaseType, originTransport);
+            mNetworkRequests.put(networkRequest, targetTransport);
+        }
 
-        int originTransport = (targetTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
-                : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-        int releaseType = success
-                ? DcTracker.RELEASE_TYPE_HANDOVER
-                // If handover fails, we need to tear down the existing connection, so the
-                // new data connection can be re-established on the new transport. If we leave
-                // the existing data connection in current transport, then DCT and qualified
-                // network service will be out of sync.
-                : DcTracker.RELEASE_TYPE_NORMAL;
-        releaseNetworkInternal(networkRequest, releaseType, originTransport);
-        mNetworkRequests.put(networkRequest, targetTransport);
-
-        handoverParams.callback.onCompleted(success);
+        handoverParams.callback.onCompleted(success, fallback);
     }
 
     protected void log(String s) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
index fd3b31d..ea11223 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
@@ -199,8 +199,10 @@
              * Called when handover is completed.
              *
              * @param success {@true} if handover succeeded, otherwise failed.
+             * @param fallback {@true} if handover failed, the data connection fallback to the
+             * original transport
              */
-            void onCompleted(boolean success);
+            void onCompleted(boolean success, boolean fallback);
         }
 
         public final @ApnType int apnType;
@@ -347,28 +349,33 @@
                             + AccessNetworkConstants.transportTypeToString(targetTransport));
                     mPendingHandoverApns.put(networks.apnType, targetTransport);
                     mHandoverNeededEventRegistrants.notifyResult(
-                            new HandoverParams(networks.apnType, targetTransport, success -> {
-                                // The callback for handover completed.
-                                if (success) {
-                                    logl("Handover succeeded.");
-                                } else {
-                                    logl("APN type "
-                                            + ApnSetting.getApnTypeString(networks.apnType)
-                                            + " handover to "
-                                            + AccessNetworkConstants.transportTypeToString(
-                                                    targetTransport) + " failed.");
-                                }
-                                // No matter succeeded or not, we need to set the current transport
-                                // to the new one. If failed, there will be retry afterwards anyway.
-                                setCurrentTransport(networks.apnType, targetTransport);
-                                mPendingHandoverApns.delete(networks.apnType);
+                            new HandoverParams(networks.apnType, targetTransport,
+                                    (success, fallback) -> {
+                                        // The callback for handover completed.
+                                        if (success) {
+                                            logl("Handover succeeded.");
+                                        } else {
+                                            logl("APN type "
+                                                    + ApnSetting.getApnTypeString(networks.apnType)
+                                                    + " handover to "
+                                                    + AccessNetworkConstants.transportTypeToString(
+                                                    targetTransport) + " failed."
+                                                    + ", fallback=" + fallback);
+                                        }
+                                        if (success || !fallback) {
+                                            // If handover succeeds or failed without falling back
+                                            // to the original transport, we should move to the new
+                                            // transport (even if it is failed).
+                                            setCurrentTransport(networks.apnType, targetTransport);
+                                        }
+                                        mPendingHandoverApns.delete(networks.apnType);
 
-                                // If there are still pending available network changes, we need to
-                                // process the rest.
-                                if (mAvailableNetworksList.size() > 0) {
-                                    sendEmptyMessage(EVENT_UPDATE_AVAILABLE_NETWORKS);
-                                }
-                            }));
+                                        // If there are still pending available network changes, we
+                                        // need to process the rest.
+                                        if (mAvailableNetworksList.size() > 0) {
+                                            sendEmptyMessage(EVENT_UPDATE_AVAILABLE_NETWORKS);
+                                        }
+                                    }));
                 }
                 mCurrentAvailableNetworks.put(networks.apnType, networks.qualifiedNetworks);
             } else {
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
index 6e622aa..2c822ba 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -43,10 +43,13 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.phone.ecc.nano.ProtobufEccData;
 import com.android.phone.ecc.nano.ProtobufEccData.EccInfo;
 
+import libcore.io.IoUtils;
+
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
@@ -58,8 +61,6 @@
 import java.util.List;
 import java.util.zip.GZIPInputStream;
 
-import libcore.io.IoUtils;
-
 /**
  * Emergency Number Tracker that handles update of emergency number list from RIL and emergency
  * number database. This is multi-sim based and each Phone has a EmergencyNumberTracker.
@@ -358,6 +359,7 @@
         if (!emergencyNumberListRadio.equals(mEmergencyNumberListFromRadio)) {
             try {
                 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(emergencyNumberListRadio);
+                writeUpdatedEmergencyNumberListMetrics(emergencyNumberListRadio);
                 mEmergencyNumberListFromRadio = emergencyNumberListRadio;
                 if (!DBG) {
                     mEmergencyNumberListRadioLocalLog.log("updateRadioEmergencyNumberList:"
@@ -382,6 +384,7 @@
 
         mCountryIso = countryIso.toLowerCase();
         cacheEmergencyDatabaseByCountry(countryIso);
+        writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase);
         if (!DBG) {
             mEmergencyNumberListDatabaseLocalLog.log(
                     "updateEmergencyNumberListDatabaseAndNotify:"
@@ -835,6 +838,17 @@
         Rlog.e(TAG, str);
     }
 
+    private void writeUpdatedEmergencyNumberListMetrics(
+            List<EmergencyNumber> updatedEmergencyNumberList) {
+        if (updatedEmergencyNumberList == null) {
+            return;
+        }
+        for (EmergencyNumber num : updatedEmergencyNumberList) {
+            TelephonyMetrics.getInstance().writeEmergencyNumberUpdateEvent(
+                    mPhone.getPhoneId(), num);
+        }
+    }
+
     /**
      * Dump Emergency Number List info in the tracking
      *
diff --git a/src/java/com/android/internal/telephony/ims/RcsMessageStoreController.java b/src/java/com/android/internal/telephony/ims/RcsMessageStoreController.java
index e7d3f48..4669141 100644
--- a/src/java/com/android/internal/telephony/ims/RcsMessageStoreController.java
+++ b/src/java/com/android/internal/telephony/ims/RcsMessageStoreController.java
@@ -74,16 +74,16 @@
 
 import static com.android.internal.telephony.ims.RcsMessageStoreUtil.getMessageTableUri;
 import static com.android.internal.telephony.ims.RcsParticipantQueryHelper.getUriForParticipant;
-import static com.android.internal.telephony.ims.RcsThreadQueryHelper.get1To1ThreadUri;
-import static com.android.internal.telephony.ims.RcsThreadQueryHelper.getAllParticipantsInThreadUri;
-import static com.android.internal.telephony.ims.RcsThreadQueryHelper.getGroupThreadUri;
-import static com.android.internal.telephony.ims.RcsThreadQueryHelper.getParticipantInThreadUri;
+import static com.android.internal.telephony.ims.RcsThreadHelper.get1To1ThreadUri;
+import static com.android.internal.telephony.ims.RcsThreadHelper.getGroupThreadUri;
+import static com.android.internal.telephony.ims.RcsThreadHelper.getParticipantInThreadUri;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -106,6 +106,8 @@
 import android.telephony.ims.aidl.IRcs;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
 
 /**
  * Backing implementation of {@link RcsMessageStore}.
@@ -122,7 +124,7 @@
     private final RcsParticipantQueryHelper mParticipantQueryHelper;
     private final RcsMessageQueryHelper mMessageQueryHelper;
     private final RcsEventQueryHelper mEventQueryHelper;
-    private final RcsThreadQueryHelper mThreadQueryHelper;
+    private final RcsThreadHelper mThreadHelper;
     private final RcsMessageStoreUtil mMessageStoreUtil;
 
     /**
@@ -142,42 +144,36 @@
         return sInstance;
     }
 
-    interface ThrowingSupplier<T> {
-        T get() throws RemoteException;
-    }
-
-    interface ThrowingRunnable {
-        void run() throws RemoteException;
-    }
-
+    /**
+     * This call cannot be nested with either {@link #performWriteOperation} or {@link
+     * #performReadOperation} as the permission check will then fail since we have cleared the
+     * calling identity.
+     */
     private void performWriteOperation(String callingPackage, ThrowingRunnable fn) {
-        RcsPermissions.checkWritePermissions(mContext, callingPackage);
-
-        try {
+        performWriteOperation(callingPackage, () -> {
             fn.run();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
+            return null;
+        });
     }
 
-    private <T> T performCreateOperation(String callingPackage, ThrowingSupplier<T> fn) {
+    /**
+     * This call cannot be nested with either {@link #performWriteOperation} or {@link
+     * #performReadOperation} as the permission check will then fail since we have cleared the
+     * calling identity.
+     */
+    private <T> T performWriteOperation(String callingPackage, ThrowingSupplier<T> fn) {
         RcsPermissions.checkWritePermissions(mContext, callingPackage);
-
-        try {
-            return fn.get();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
+        return Binder.withCleanCallingIdentity(fn);
     }
 
+    /**
+     * This call cannot be nested with either {@link #performWriteOperation} or {@link
+     * #performReadOperation} as the permission check will then fail since we have cleared the
+     * calling identity.
+     */
     private <T> T performReadOperation(String callingPackage, ThrowingSupplier<T> fn) {
         RcsPermissions.checkReadPermissions(mContext, callingPackage);
-
-        try {
-            return fn.get();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
+        return Binder.withCleanCallingIdentity(fn);
     }
 
     @VisibleForTesting
@@ -186,14 +182,14 @@
         mContentResolver = context.getContentResolver();
         mParticipantQueryHelper = new RcsParticipantQueryHelper(mContentResolver);
         mMessageQueryHelper = new RcsMessageQueryHelper(mContentResolver);
-        mThreadQueryHelper = new RcsThreadQueryHelper(mContentResolver, mParticipantQueryHelper);
+        mThreadHelper = new RcsThreadHelper(mContentResolver, mParticipantQueryHelper);
         mEventQueryHelper = new RcsEventQueryHelper(mContentResolver);
         mMessageStoreUtil = new RcsMessageStoreUtil(mContentResolver);
     }
 
     @Override
     public boolean deleteThread(int threadId, int threadType, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             int deletionCount = mContentResolver.delete(
                     threadType == THREAD_TYPE_GROUP ? RCS_GROUP_THREAD_URI : RCS_1_TO_1_THREAD_URI,
                     RCS_THREAD_ID_COLUMN + "=?",
@@ -217,7 +213,7 @@
         return performReadOperation(callingPackage, () -> {
             Bundle bundle = new Bundle();
             bundle.putParcelable(THREAD_QUERY_PARAMETERS_KEY, queryParameters);
-            return mThreadQueryHelper.performThreadQuery(bundle);
+            return mThreadHelper.performThreadQuery(bundle);
         });
     }
 
@@ -227,7 +223,7 @@
         return performReadOperation(callingPackage, () -> {
             Bundle bundle = new Bundle();
             bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
-            return mThreadQueryHelper.performThreadQuery(bundle);
+            return mThreadHelper.performThreadQuery(bundle);
         });
     }
 
@@ -293,15 +289,15 @@
 
     @Override
     public int createRcs1To1Thread(int recipientId, String callingPackage) {
-        return performCreateOperation(callingPackage,
-                () -> mThreadQueryHelper.create1To1Thread(recipientId));
+        return performWriteOperation(callingPackage,
+                () -> mThreadHelper.create1To1Thread(recipientId));
     }
 
     @Override
     public int createGroupThread(int[] participantIds, String groupName, Uri groupIcon,
             String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            int groupThreadId = mThreadQueryHelper.createGroupThread(groupName, groupIcon);
+        return performWriteOperation(callingPackage, () -> {
+            int groupThreadId = mThreadHelper.createGroupThread(groupName, groupIcon);
             if (groupThreadId <= 0) {
                 throw new RemoteException("Could not create RcsGroupThread.");
             }
@@ -311,7 +307,7 @@
             //  under one transaction
             if (participantIds != null) {
                 for (int participantId : participantIds) {
-                    addParticipantToGroupThread(groupThreadId, participantId, callingPackage);
+                    mThreadHelper.addParticipantToGroupThread(groupThreadId, participantId);
                 }
             }
 
@@ -326,7 +322,7 @@
      */
     @Override
     public int createRcsParticipant(String canonicalAddress, String alias, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues contentValues = new ContentValues();
 
             long canonicalAddressId = Telephony.RcsColumns.RcsCanonicalAddressHelper
@@ -494,11 +490,7 @@
     public void addParticipantToGroupThread(int rcsThreadId, int participantId,
             String callingPackage) {
         performWriteOperation(callingPackage, () -> {
-            ContentValues contentValues = new ContentValues(2);
-            contentValues.put(RCS_THREAD_ID_COLUMN, rcsThreadId);
-            contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
-
-            mContentResolver.insert(getAllParticipantsInThreadUri(rcsThreadId), contentValues);
+            mThreadHelper.addParticipantToGroupThread(rcsThreadId, participantId);
         });
     }
 
@@ -515,7 +507,7 @@
     public int addIncomingMessage(int rcsThreadId,
             RcsIncomingMessageCreationParams rcsIncomingMessageCreationParams,
             String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues contentValues = new ContentValues();
 
             contentValues.put(ARRIVAL_TIMESTAMP_COLUMN,
@@ -536,7 +528,7 @@
     public int addOutgoingMessage(int rcsThreadId,
             RcsOutgoingMessageCreationParams rcsOutgoingMessageCreationParameters,
             String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues contentValues = new ContentValues();
 
             mMessageQueryHelper.createContentValuesForGenericMessage(contentValues, rcsThreadId,
@@ -795,7 +787,7 @@
     @Override
     public int storeFileTransfer(int messageId, boolean isIncoming,
             RcsFileTransferCreationParams fileTransferCreationParameters, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues contentValues = mMessageQueryHelper.getContentValuesForFileTransfer(
                     fileTransferCreationParameters);
             Uri uri = mContentResolver.insert(
@@ -979,7 +971,7 @@
     @Override
     public int createGroupThreadNameChangedEvent(long timestamp, int threadId,
             int originationParticipantId, String newName, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues eventSpecificValues = new ContentValues();
             eventSpecificValues.put(NEW_NAME_COLUMN, newName);
 
@@ -991,7 +983,7 @@
     @Override
     public int createGroupThreadIconChangedEvent(long timestamp, int threadId,
             int originationParticipantId, Uri newIcon, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues eventSpecificValues = new ContentValues();
             eventSpecificValues.put(NEW_ICON_URI_COLUMN,
                     newIcon == null ? null : newIcon.toString());
@@ -1004,7 +996,7 @@
     @Override
     public int createGroupThreadParticipantJoinedEvent(long timestamp, int threadId,
             int originationParticipantId, int participantId, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues eventSpecificValues = new ContentValues();
             eventSpecificValues.put(DESTINATION_PARTICIPANT_ID_COLUMN, participantId);
 
@@ -1017,7 +1009,7 @@
     @Override
     public int createGroupThreadParticipantLeftEvent(long timestamp, int threadId,
             int originationParticipantId, int participantId, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues eventSpecificValues = new ContentValues();
             eventSpecificValues.put(DESTINATION_PARTICIPANT_ID_COLUMN, participantId);
 
@@ -1029,7 +1021,7 @@
     @Override
     public int createParticipantAliasChangedEvent(long timestamp, int participantId,
             String newAlias, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
+        return performWriteOperation(callingPackage, () -> {
             ContentValues contentValues = new ContentValues(4);
             contentValues.put(TIMESTAMP_COLUMN, timestamp);
             contentValues.put(SOURCE_PARTICIPANT_ID_COLUMN, participantId);
diff --git a/src/java/com/android/internal/telephony/ims/RcsThreadQueryHelper.java b/src/java/com/android/internal/telephony/ims/RcsThreadHelper.java
similarity index 93%
rename from src/java/com/android/internal/telephony/ims/RcsThreadQueryHelper.java
rename to src/java/com/android/internal/telephony/ims/RcsThreadHelper.java
index d4bc299..6d95430 100644
--- a/src/java/com/android/internal/telephony/ims/RcsThreadQueryHelper.java
+++ b/src/java/com/android/internal/telephony/ims/RcsThreadHelper.java
@@ -48,13 +48,13 @@
  * A helper class focused on querying RCS threads from the
  * {@link com.android.providers.telephony.RcsProvider}
  */
-class RcsThreadQueryHelper {
+class RcsThreadHelper {
     private static final int THREAD_ID_INDEX_IN_INSERTION_URI = 1;
 
     private final ContentResolver mContentResolver;
     private final RcsParticipantQueryHelper mParticipantQueryHelper;
 
-    RcsThreadQueryHelper(ContentResolver contentResolver,
+    RcsThreadHelper(ContentResolver contentResolver,
             RcsParticipantQueryHelper participantQueryHelper) {
         mContentResolver = contentResolver;
         mParticipantQueryHelper = participantQueryHelper;
@@ -130,6 +130,14 @@
         return threadId;
     }
 
+    void addParticipantToGroupThread(int rcsThreadId, int participantId) {
+        ContentValues contentValues = new ContentValues(2);
+        contentValues.put(RCS_THREAD_ID_COLUMN, rcsThreadId);
+        contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
+
+        mContentResolver.insert(getAllParticipantsInThreadUri(rcsThreadId), contentValues);
+    }
+
     static Uri get1To1ThreadUri(int rcsThreadId) {
         return Uri.withAppendedPath(RCS_1_TO_1_THREAD_URI, Integer.toString(rcsThreadId));
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 286dd13..8c5be19 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -98,6 +98,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.uicc.IccRecords;
@@ -233,6 +234,16 @@
         return mCurrentSubscriberUris;
     }
 
+    @Override
+    public EmergencyNumberTracker getEmergencyNumberTracker() {
+        return mDefaultPhone.getEmergencyNumberTracker();
+    }
+
+    @Override
+    public ServiceStateTracker getServiceStateTracker() {
+        return mDefaultPhone.getServiceStateTracker();
+    }
+
     // Create Cf (Call forward) so that dialling number &
     // mIsCfu (true if reason is call forward unconditional)
     // mOnComplete (Message object passed by client) can be packed &
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index b8eb808..2f53e91 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -92,9 +92,11 @@
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.LocaleTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneInternalInterface;
+import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
@@ -2344,7 +2346,8 @@
 
             String callId = imsCall.getSession().getCallId();
             mMetrics.writeOnImsCallTerminated(mPhone.getPhoneId(), imsCall.getCallSession(),
-                    reasonInfo, mCallQualityMetrics.get(callId));
+                    reasonInfo, mCallQualityMetrics.get(callId), conn.getEmergencyNumberInfo(),
+                    getNetworkCountryIso());
             pruneCallQualityMetricsHistory();
             mPhone.notifyImsReason(reasonInfo);
 
@@ -4196,6 +4199,21 @@
         mAlwaysPlayRemoteHoldTone = shouldPlayRemoteHoldTone;
     }
 
+    private String getNetworkCountryIso() {
+        String countryIso = "";
+        if (mPhone != null) {
+            ServiceStateTracker sst = mPhone.getServiceStateTracker();
+            if (sst != null) {
+                LocaleTracker lt = sst.getLocaleTracker();
+                if (lt != null) {
+                    countryIso = lt.getCurrentCountry();
+                }
+            }
+        }
+        return countryIso;
+    }
+
+    @Override
     public ImsPhone getPhone() {
         return mPhone;
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index 085952b..e33bd6d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -248,7 +248,7 @@
 
         mIsEmergency = isEmergency;
         if (isEmergency) {
-            setEmergencyCallInfo();
+            setEmergencyCallInfo(mOwner);
         }
 
         fetchDtmfToneDelay(phone);
diff --git a/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java b/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
index 9a0b7e0..d4bacc6 100644
--- a/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.metrics;
 
+import com.android.internal.telephony.nano.TelephonyProto.EmergencyNumberInfo;
 import com.android.internal.telephony.nano.TelephonyProto.ImsCapabilities;
 import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
 import com.android.internal.telephony.nano.TelephonyProto.ImsReasonInfo;
@@ -156,4 +157,17 @@
         mEvent.callQualitySummaryUl = callQualitySummary;
         return this;
     }
+
+    /** Set if the Ims call is emergency. */
+    public CallSessionEventBuilder setIsImsEmergencyCall(boolean isImsEmergencyCall) {
+        mEvent.isImsEmergencyCall = isImsEmergencyCall;
+        return this;
+    }
+
+    /** Set the Ims emergency call information. */
+    public CallSessionEventBuilder setImsEmergencyNumberInfo(
+            EmergencyNumberInfo imsEmergencyNumberInfo) {
+        mEvent.imsEmergencyNumberInfo = imsEmergencyNumberInfo;
+        return this;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java b/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
index 8a97579..73439c3 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.metrics;
 
+import static com.android.internal.telephony.nano.TelephonyProto.EmergencyNumberInfo;
 import static com.android.internal.telephony.nano.TelephonyProto.ImsCapabilities;
 import static com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
 import static com.android.internal.telephony.nano.TelephonyProto.RilDataCall;
@@ -143,6 +144,16 @@
         return this;
     }
 
+    /**
+     * Set and build EMERGENCY_NUMBER_REPORT event
+     */
+    public TelephonyEventBuilder setUpdatedEmergencyNumber(
+            EmergencyNumberInfo emergencyNumberInfo) {
+        mEvent.type = TelephonyEvent.Type.EMERGENCY_NUMBER_REPORT;
+        mEvent.updatedEmergencyNumber = emergencyNumberInfo;
+        return this;
+    }
+
     public TelephonyEventBuilder setCarrierKeyChange(CarrierKeyChange carrierKeyChange) {
         mEvent.type = TelephonyEvent.Type.CARRIER_KEY_CHANGED;
         mEvent.carrierKeyChange = carrierKeyChange;
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
index cacb894..6d56d1d 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -42,6 +42,7 @@
 import android.os.SystemProperties;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.CallQuality;
+import android.telephony.DisconnectCause;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
@@ -52,6 +53,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataService;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
@@ -73,6 +75,7 @@
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.nano.TelephonyProto;
 import com.android.internal.telephony.nano.TelephonyProto.ActiveSubscriptionInfo;
+import com.android.internal.telephony.nano.TelephonyProto.EmergencyNumberInfo;
 import com.android.internal.telephony.nano.TelephonyProto.ImsCapabilities;
 import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
 import com.android.internal.telephony.nano.TelephonyProto.ModemPowerStats;
@@ -110,6 +113,7 @@
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
 
 /**
  * Telephony metrics holds all metrics events and convert it into telephony proto buf.
@@ -296,6 +300,8 @@
                 return "CARRIER_ID_MATCHING";
             case TelephonyEvent.Type.NITZ_TIME:
                 return "NITZ_TIME";
+            case TelephonyEvent.Type.EMERGENCY_NUMBER_REPORT:
+                return "EMERGENCY_NUMBER_REPORT";
             default:
                 return Integer.toString(event);
         }
@@ -1407,13 +1413,14 @@
      * @param phoneId    Phone id
      * @param connections Array of GsmCdmaConnection objects
      */
-    public void writeRilCallList(int phoneId, ArrayList<GsmCdmaConnection> connections) {
+    public void writeRilCallList(int phoneId, ArrayList<GsmCdmaConnection> connections,
+                                 String countryIso) {
         logv("Logging CallList Changed Connections Size = " + connections.size());
         InProgressCallSession callSession = startNewCallSessionIfNeeded(phoneId);
         if (callSession == null) {
             Rlog.e(TAG, "writeRilCallList: Call session is missing");
         } else {
-            RilCall[] calls = convertConnectionsToRilCalls(connections);
+            RilCall[] calls = convertConnectionsToRilCalls(connections, countryIso);
             callSession.addEvent(
                     new CallSessionEventBuilder(
                             TelephonyCallSession.Event.Type.RIL_CALL_LIST_CHANGED)
@@ -1433,17 +1440,31 @@
         return true;
     }
 
-    private RilCall[] convertConnectionsToRilCalls(ArrayList<GsmCdmaConnection> mConnections) {
+    private RilCall[] convertConnectionsToRilCalls(ArrayList<GsmCdmaConnection> mConnections,
+                                                   String countryIso) {
         RilCall[] calls = new RilCall[mConnections.size()];
         for (int i = 0; i < mConnections.size(); i++) {
             calls[i] = new RilCall();
             calls[i].index = i;
-            convertConnectionToRilCall(mConnections.get(i), calls[i]);
+            convertConnectionToRilCall(mConnections.get(i), calls[i], countryIso);
         }
         return calls;
     }
 
-    private void convertConnectionToRilCall(GsmCdmaConnection conn, RilCall call) {
+    private EmergencyNumberInfo convertEmergencyNumberToEmergencyNumberInfo(EmergencyNumber num) {
+        EmergencyNumberInfo emergencyNumberInfo = new EmergencyNumberInfo();
+        emergencyNumberInfo.address = num.getNumber();
+        emergencyNumberInfo.countryIso = num.getCountryIso();
+        emergencyNumberInfo.mnc = num.getMnc();
+        emergencyNumberInfo.serviceCategoriesBitmask = num.getEmergencyServiceCategoryBitmask();
+        emergencyNumberInfo.urns = num.getEmergencyUrns().stream().toArray(String[]::new);
+        emergencyNumberInfo.numberSourcesBitmask = num.getEmergencyNumberSourceBitmask();
+        emergencyNumberInfo.routing = num.getEmergencyCallRouting();
+        return emergencyNumberInfo;
+    }
+
+    private void convertConnectionToRilCall(GsmCdmaConnection conn, RilCall call,
+                                            String countryIso) {
         if (conn.isIncoming()) {
             call.type = Type.MT;
         } else {
@@ -1484,6 +1505,18 @@
         call.callEndReason = conn.getDisconnectCause();
         call.isMultiparty = conn.isMultiparty();
         call.preciseDisconnectCause = conn.getPreciseDisconnectCause();
+
+        // Emergency call metrics when call ends
+        if (conn.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED
+                && conn.isEmergencyCall() && conn.getEmergencyNumberInfo() != null) {
+            /** Only collect this emergency number information per sample percentage */
+            if (ThreadLocalRandom.current().nextDouble(0, 100)
+                    < getSamplePercentageForEmergencyCall(countryIso)) {
+                call.isEmergencyCall = conn.isEmergencyCall();
+                call.emergencyNumberInfo = convertEmergencyNumberToEmergencyNumberInfo(
+                        conn.getEmergencyNumberInfo());
+            }
+        }
     }
 
     /**
@@ -1504,7 +1537,7 @@
             RilCall[] calls = new RilCall[1];
             calls[0] = new RilCall();
             calls[0].index = -1;
-            convertConnectionToRilCall(conn, calls[0]);
+            convertConnectionToRilCall(conn, calls[0], "");
             callSession.addEvent(callSession.startElapsedTimeMs,
                     new CallSessionEventBuilder(TelephonyCallSession.Event.Type.RIL_REQUEST)
                             .setRilRequest(TelephonyCallSession.Event.RilRequest.RIL_REQUEST_DIAL)
@@ -1533,7 +1566,8 @@
      * @param conn Connection object associated with the call that is being hung-up
      * @param callId Call id
      */
-    public void writeRilHangup(int phoneId, GsmCdmaConnection conn, int callId) {
+    public void writeRilHangup(int phoneId, GsmCdmaConnection conn, int callId,
+                               String countryIso) {
         InProgressCallSession callSession = mInProgressCallSessions.get(phoneId);
         if (callSession == null) {
             Rlog.e(TAG, "writeRilHangup: Call session is missing");
@@ -1541,7 +1575,7 @@
             RilCall[] calls = new RilCall[1];
             calls[0] = new RilCall();
             calls[0].index = callId;
-            convertConnectionToRilCall(conn, calls[0]);
+            convertConnectionToRilCall(conn, calls[0], countryIso);
             callSession.addEvent(
                     new CallSessionEventBuilder(TelephonyCallSession.Event.Type.RIL_REQUEST)
                             .setRilRequest(TelephonyCallSession.Event.RilRequest.RIL_REQUEST_HANGUP)
@@ -2034,26 +2068,32 @@
      * @param reasonInfo Call end reason
      */
     public void writeOnImsCallTerminated(int phoneId, ImsCallSession session,
-                                         ImsReasonInfo reasonInfo, CallQualityMetrics cqm) {
+                                         ImsReasonInfo reasonInfo, CallQualityMetrics cqm,
+                                         EmergencyNumber emergencyNumber, String countryIso) {
         InProgressCallSession callSession = mInProgressCallSessions.get(phoneId);
         if (callSession == null) {
             Rlog.e(TAG, "Call session is missing");
         } else {
+            CallSessionEventBuilder callSessionEvent = new CallSessionEventBuilder(
+                    TelephonyCallSession.Event.Type.IMS_CALL_TERMINATED);
+            callSessionEvent.setCallIndex(getCallId(session));
+            callSessionEvent.setImsReasonInfo(toImsReasonInfoProto(reasonInfo));
+
             if (cqm != null) {
-                callSession.addEvent(
-                        new CallSessionEventBuilder(
-                                TelephonyCallSession.Event.Type.IMS_CALL_TERMINATED)
-                        .setCallIndex(getCallId(session))
-                        .setImsReasonInfo(toImsReasonInfoProto(reasonInfo))
-                        .setCallQualitySummaryDl(cqm.getCallQualitySummaryDl())
-                        .setCallQualitySummaryUl(cqm.getCallQualitySummaryUl()));
-            } else {
-                callSession.addEvent(
-                        new CallSessionEventBuilder(
-                                TelephonyCallSession.Event.Type.IMS_CALL_TERMINATED)
-                        .setCallIndex(getCallId(session))
-                        .setImsReasonInfo(toImsReasonInfoProto(reasonInfo)));
+                callSessionEvent.setCallQualitySummaryDl(cqm.getCallQualitySummaryDl())
+                        .setCallQualitySummaryUl(cqm.getCallQualitySummaryUl());
             }
+
+            if (emergencyNumber != null) {
+                /** Only collect this emergency number information per sample percentage */
+                if (ThreadLocalRandom.current().nextDouble(0, 100)
+                        < getSamplePercentageForEmergencyCall(countryIso)) {
+                    callSessionEvent.setIsImsEmergencyCall(true);
+                    callSessionEvent.setImsEmergencyNumberInfo(
+                            convertEmergencyNumberToEmergencyNumberInfo(emergencyNumber));
+                }
+            }
+            callSession.addEvent(callSessionEvent);
         }
     }
 
@@ -2420,6 +2460,23 @@
     }
 
     /**
+     * Write emergency number update event
+     *
+     * @param emergencyNumber Updated emergency number
+     */
+    public void writeEmergencyNumberUpdateEvent(int phoneId, EmergencyNumber emergencyNumber) {
+        if (emergencyNumber == null) {
+            return;
+        }
+        final EmergencyNumberInfo emergencyNumberInfo =
+                convertEmergencyNumberToEmergencyNumberInfo(emergencyNumber);
+
+        TelephonyEvent event = new TelephonyEventBuilder(phoneId).setUpdatedEmergencyNumber(
+                emergencyNumberInfo).build();
+        addTelephonyEvent(event);
+    }
+
+    /**
      * Convert SMS format
      */
     private int convertSmsFormat(String format) {
@@ -2582,6 +2639,38 @@
                                            ImsReasonInfo reasonInfo) {}
     public void writeOnRilTimeoutResponse(int phoneId, int rilSerial, int rilRequest) {}
 
+    /**
+     * Get the sample percentage of collecting metrics based on countries' population.
+     *
+     * The larger population the country has, the lower percentage we use to collect this
+     * metrics. Since the exact population changes frequently, buckets of the population are used
+     * instead of its exact number. Seven different levels of sampling percentage are assigned
+     * based on the scale of population for countries.
+     */
+    private double getSamplePercentageForEmergencyCall(String countryIso) {
+        String countriesFor1Percentage = "cn,in";
+        String countriesFor5Percentage = "us,id,br,pk,ng,bd,ru,mx,jp,et,ph,eg,vn,cd,tr,ir,de";
+        String countriesFor15Percentage = "th,gb,fr,tz,it,za,mm,ke,kr,co,es,ug,ar,ua,dz,sd,iq";
+        String countriesFor25Percentage = "pl,ca,af,ma,sa,pe,uz,ve,my,ao,mz,gh,np,ye,mg,kp,cm";
+        String countriesFor35Percentage = "au,tw,ne,lk,bf,mw,ml,ro,kz,sy,cl,zm,gt,zw,nl,ec,sn";
+        String countriesFor45Percentage = "kh,td,so,gn,ss,rw,bj,tn,bi,be,cu,bo,ht,gr,do,cz,pt";
+        if (countriesFor1Percentage.contains(countryIso)) {
+            return 1;
+        } else if (countriesFor5Percentage.contains(countryIso)) {
+            return 5;
+        } else if (countriesFor15Percentage.contains(countryIso)) {
+            return 15;
+        } else if (countriesFor25Percentage.contains(countryIso)) {
+            return 25;
+        } else if (countriesFor35Percentage.contains(countryIso)) {
+            return 35;
+        } else if (countriesFor45Percentage.contains(countryIso)) {
+            return 45;
+        } else {
+            return 50;
+        }
+    }
+
     private static int mapSimStateToProto(int simState) {
         switch (simState) {
             case TelephonyManager.SIM_STATE_ABSENT:
diff --git a/src/java/com/android/internal/telephony/uicc/ShowInstallAppNotificationReceiver.java b/src/java/com/android/internal/telephony/uicc/ShowInstallAppNotificationReceiver.java
index 6c89448..7891c7c 100644
--- a/src/java/com/android/internal/telephony/uicc/ShowInstallAppNotificationReceiver.java
+++ b/src/java/com/android/internal/telephony/uicc/ShowInstallAppNotificationReceiver.java
@@ -38,7 +38,7 @@
     public void onReceive(Context context, Intent intent) {
         String pkgName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
 
-        if (!UiccProfile.isPackageInstalled(context, pkgName)) {
+        if (!UiccProfile.isPackageBundled(context, pkgName)) {
             InstallCarrierAppUtils.showNotification(context, pkgName);
             InstallCarrierAppUtils.registerPackageInstallReceiver(context);
         }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 116591a..2358c43 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -34,6 +34,7 @@
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
+import android.os.UserManager;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -140,15 +141,28 @@
             new ContentObserver(new Handler()) {
                 @Override
                 public void onChange(boolean selfChange) {
-                    mContext.getContentResolver().unregisterContentObserver(this);
-                    for (String pkgName : getUninstalledCarrierPackages()) {
-                        InstallCarrierAppUtils.showNotification(mContext, pkgName);
-                        InstallCarrierAppUtils.registerPackageInstallReceiver(mContext);
+                    synchronized (mLock) {
+                        mContext.getContentResolver().unregisterContentObserver(this);
+                        mProvisionCompleteContentObserverRegistered = false;
+                        showCarrierAppNotificationsIfPossible();
                     }
                 }
             };
+    private boolean mProvisionCompleteContentObserverRegistered;
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                mContext.unregisterReceiver(this);
+                mUserUnlockReceiverRegistered = false;
+                showCarrierAppNotificationsIfPossible();
+            }
+        }
+    };
+    private boolean mUserUnlockReceiverRegistered;
+
+    private final BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
@@ -253,7 +267,7 @@
 
         IntentFilter intentfilter = new IntentFilter();
         intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        c.registerReceiver(mReceiver, intentfilter);
+        c.registerReceiver(mCarrierConfigChangedReceiver, intentfilter);
     }
 
     /**
@@ -271,11 +285,22 @@
             unregisterAllAppEvents();
             unregisterCurrAppEvents();
 
+            if (mProvisionCompleteContentObserverRegistered) {
+                mContext.getContentResolver()
+                        .unregisterContentObserver(mProvisionCompleteContentObserver);
+                mProvisionCompleteContentObserverRegistered = false;
+            }
+
+            if (mUserUnlockReceiverRegistered) {
+                mContext.unregisterReceiver(mUserUnlockReceiver);
+                mUserUnlockReceiverRegistered = false;
+            }
+
             InstallCarrierAppUtils.hideAllNotifications(mContext);
             InstallCarrierAppUtils.unregisterPackageInstallReceiver(mContext);
 
             mCi.unregisterForOffOrNotAvailable(mHandler);
-            mContext.unregisterReceiver(mReceiver);
+            mContext.unregisterReceiver(mCarrierConfigChangedReceiver);
 
             if (mCatService != null) mCatService.dispose();
             for (UiccCardApplication app : mUiccApplications) {
@@ -1158,10 +1183,13 @@
         }
     }
 
-    static boolean isPackageInstalled(Context context, String pkgName) {
+    static boolean isPackageBundled(Context context, String pkgName) {
         PackageManager pm = context.getPackageManager();
         try {
-            pm.getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES);
+            // We also match hidden-until-installed apps. The assumption here is that some other
+            // mechanism (like CarrierAppUtils) would automatically enable such an app, so we
+            // shouldn't prompt the user about it.
+            pm.getApplicationInfo(pkgName, PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS);
             if (DBG) log(pkgName + " is installed.");
             return true;
         } catch (PackageManager.NameNotFoundException e) {
@@ -1188,21 +1216,47 @@
 
         synchronized (mLock) {
             mCarrierPrivilegeRegistrants.notifyRegistrants();
-            boolean isProvisioned = Settings.Global.getInt(
-                    mContext.getContentResolver(),
-                    Settings.Global.DEVICE_PROVISIONED, 1) == 1;
-            // Only show dialog if the phone is through with Setup Wizard.  Otherwise, wait for
-            // completion and show a notification instead
-            if (isProvisioned) {
+            boolean isProvisioned = isProvisioned();
+            boolean isUnlocked = isUserUnlocked();
+            // Only show dialog if the phone is through with Setup Wizard and is unlocked.
+            // Otherwise, wait for completion and unlock and show a notification instead.
+            if (isProvisioned && isUnlocked) {
                 for (String pkgName : getUninstalledCarrierPackages()) {
                     promptInstallCarrierApp(pkgName);
                 }
             } else {
-                final Uri uri = Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED);
-                mContext.getContentResolver().registerContentObserver(
-                        uri,
-                        false,
-                        mProvisionCompleteContentObserver);
+                if (!isProvisioned) {
+                    final Uri uri = Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED);
+                    mContext.getContentResolver().registerContentObserver(
+                            uri,
+                            false,
+                            mProvisionCompleteContentObserver);
+                    mProvisionCompleteContentObserverRegistered = true;
+                }
+                if (!isUnlocked) {
+                    mContext.registerReceiver(
+                            mUserUnlockReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+                    mUserUnlockReceiverRegistered = true;
+                }
+            }
+        }
+    }
+
+    private boolean isProvisioned() {
+        return Settings.Global.getInt(
+                mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 1) == 1;
+    }
+
+    private boolean isUserUnlocked() {
+        return mContext.getSystemService(UserManager.class).isUserUnlocked();
+    }
+
+    private void showCarrierAppNotificationsIfPossible() {
+        if (isProvisioned() && isUserUnlocked()) {
+            for (String pkgName : getUninstalledCarrierPackages()) {
+                InstallCarrierAppUtils.showNotification(mContext, pkgName);
+                InstallCarrierAppUtils.registerPackageInstallReceiver(mContext);
             }
         }
     }
@@ -1226,7 +1280,7 @@
         for (UiccAccessRule accessRule : accessRules) {
             String certHexString = accessRule.getCertificateHexString().toUpperCase();
             String pkgName = certPackageMap.get(certHexString);
-            if (!TextUtils.isEmpty(pkgName) && !isPackageInstalled(mContext, pkgName)) {
+            if (!TextUtils.isEmpty(pkgName) && !isPackageBundled(mContext, pkgName)) {
                 uninstalledCarrierPackages.add(pkgName);
             }
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
index e7e694c..5e2affb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
@@ -127,7 +127,7 @@
         assertNull(connection1.getEmergencyNumberInfo());
         assertFalse(connection1.hasKnownUserIntentEmergency());
 
-        connection2.setEmergencyCallInfo();
+        connection2.setEmergencyCallInfo(mPhone.getCallTracker());
         connection2.setHasKnownUserIntentEmergency(true);
         connection1.migrateFrom(connection2);
 
@@ -141,7 +141,7 @@
     @Test
     public void testEmergencyCallParameters() {
         Connection connection = new TestConnection(TEST_PHONE_TYPE);
-        connection.setEmergencyCallInfo();
+        connection.setEmergencyCallInfo(mPhone.getCallTracker());
         assertTrue(connection.isEmergencyCall());
         assertEquals(getTestEmergencyNumber(), connection.getEmergencyNumberInfo());
         connection.setHasKnownUserIntentEmergency(true);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 5d1ece3..e6be985 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -265,6 +265,8 @@
                 return Context.APP_OPS_SERVICE;
             } else if (serviceClass == TelecomManager.class) {
                 return Context.TELECOM_SERVICE;
+            } else if (serviceClass == UserManager.class) {
+                return Context.USER_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index 49ca3c6..9753722 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -108,7 +108,8 @@
                     + " INTEGER DEFAULT " + SubscriptionManager.PROFILE_CLASS_DEFAULT + ","
                     + SubscriptionManager.SUBSCRIPTION_TYPE + " INTEGER DEFAULT 0,"
                     + SubscriptionManager.WHITE_LISTED_APN_DATA + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.GROUP_OWNER + " TEXT"
+                    + SubscriptionManager.GROUP_OWNER + " TEXT,"
+                    + SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES + " TEXT"
                     + ");";
         }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
index 9a5b481..f9176af 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
@@ -646,6 +646,7 @@
         doReturn(0).when(mPhone).getPhoneId();
         doReturn(1).when(mPhone2).getPhoneId();
         doReturn(true).when(mPhone2).isUserDataEnabled();
+        doReturn(mDataEnabledSettings).when(mPhone2).getDataEnabledSettings();
         for (int i = 0; i < numPhones; i++) {
             mSlotIndexToSubId[i] = new int[1];
             mSlotIndexToSubId[i][0] = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index eaeea94..0d1551c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -24,12 +24,14 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.Manifest;
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Intent;
 import android.os.Bundle;
@@ -38,11 +40,15 @@
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.UiccSlotInfo;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.filters.FlakyTest;
 
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccSlot;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -63,6 +69,8 @@
     private MockContentResolver mMockContentResolver;
     private FakeTelephonyProvider mFakeTelephonyProvider;
     @Mock
+    private UiccSlot mUiccSlot;
+    @Mock
     private ITelephonyRegistry.Stub mTelephonyRegisteryMock;
     @Mock
     private MultiSimSettingController mMultiSimSettingControllerMock;
@@ -928,4 +936,70 @@
         // Make sure the return sub ids are sorted by slot index
         assertTrue("active sub ids = " + subIds, Arrays.equals(subIds, new int[]{2, 1}));
     }
+
+    @Test
+    public void testGetEnabledSubscriptionIdSingleSIM() {
+        // A single SIM device may have logical slot 0 mapped to physical slot 1
+        // (i.e. logical slot -1 mapped to physical slot 0)
+        UiccSlotInfo slot0 = getFakeUiccSlotInfo(false, -1);
+        UiccSlotInfo slot1 = getFakeUiccSlotInfo(true, 0);
+        UiccSlotInfo [] uiccSlotInfos = {slot0, slot1};
+        UiccSlot [] uiccSlots = {mUiccSlot, mUiccSlot};
+
+        doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo();
+        doReturn(uiccSlots).when(mUiccController).getUiccSlots();
+        assertEquals(2, UiccController.getInstance().getUiccSlots().length);
+
+        ContentResolver resolver = mContext.getContentResolver();
+        // logical 0 should find physical 1, has settings enabled subscription 0
+        Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 1, 0);
+
+        int enabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(0);
+        assertEquals(0, enabledSubscription);
+    }
+
+    @Test
+    public void testGetEnabledSubscriptionIdDualSIM() {
+        doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount();
+        doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount();
+        // A dual SIM device may have logical slot 0 mapped to physical slot 0
+        // (i.e. logical slot 1 mapped to physical slot 1)
+        UiccSlotInfo slot0 = getFakeUiccSlotInfo(true, 0);
+        UiccSlotInfo slot1 = getFakeUiccSlotInfo(true, 1);
+        UiccSlotInfo [] uiccSlotInfos = {slot0, slot1};
+        UiccSlot [] uiccSlots = {mUiccSlot, mUiccSlot};
+
+        doReturn(2).when(mTelephonyManager).getPhoneCount();
+        doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo();
+        doReturn(uiccSlots).when(mUiccController).getUiccSlots();
+        assertEquals(2, UiccController.getInstance().getUiccSlots().length);
+
+        ContentResolver resolver = mContext.getContentResolver();
+        // logical 0 should find physical 0, has settings enabled subscription 0
+        Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 0, 0);
+        Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 1, 1);
+
+        int enabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(0);
+        int secondEabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(1);
+        assertEquals(0, enabledSubscription);
+        assertEquals(1, secondEabledSubscription);
+    }
+
+
+    private UiccSlotInfo getFakeUiccSlotInfo(boolean active, int logicalSlotIndex) {
+        return new UiccSlotInfo(active, false, "fake card Id",
+                UiccSlotInfo.CARD_STATE_INFO_PRESENT, logicalSlotIndex, true, true);
+    }
+
+    // TODO: Move this test once SubscriptionManager.setAlwaysAllowMmsData is moved to telephony
+    // manager.
+    @Test
+    @SmallTest
+    public void testSetAlwaysAllowMmsData() throws Exception {
+        mSubscriptionControllerUT.setAlwaysAllowMmsData(0, true);
+        verify(mDataEnabledSettings).setAlwaysAllowMmsData(eq(true));
+        clearInvocations(mDataEnabledSettings);
+        mSubscriptionControllerUT.setAlwaysAllowMmsData(0, false);
+        verify(mDataEnabledSettings).setAlwaysAllowMmsData(eq(false));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index fc424d5..b0f0948 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -63,6 +63,7 @@
 import com.android.ims.ImsManager;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.cdma.EriManager;
+import com.android.internal.telephony.dataconnection.DataEnabledOverride;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.dataconnection.TransportManager;
@@ -229,6 +230,8 @@
     @Mock
     protected DataEnabledSettings mDataEnabledSettings;
     @Mock
+    protected DataEnabledOverride mDataEnabledOverride;
+    @Mock
     protected PhoneConfigurationManager mPhoneConfigurationManager;
     @Mock
     protected CellularNetworkValidator mCellularNetworkValidator;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledOverrideTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledOverrideTest.java
new file mode 100644
index 0000000..7791147
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledOverrideTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 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.dataconnection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+
+import android.telephony.data.ApnSetting;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DataEnabledOverrideTest extends TelephonyTest {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testCreateByRules() throws Exception {
+        DataEnabledOverride deo1 = new DataEnabledOverride(
+                "mms=nonDefault, default=inVoiceCall&nonDefault");
+        DataEnabledOverride deo2 = new DataEnabledOverride(
+                "mms=nonDefault, default=inVoiceCall");
+        DataEnabledOverride deo3 = new DataEnabledOverride(
+                "default=inVoiceCall&nonDefault, mms=nonDefault");
+        assertEquals(deo1, deo3);
+        assertNotEquals(deo1, deo2);
+    }
+
+    @Test
+    @SmallTest
+    public void testOverrideEnabled() throws Exception {
+        DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                "mms=nonDefault, default=inVoiceCall&nonDefault");
+        doReturn(1).when(mPhone).getSubId();
+        doReturn(2).when(mSubscriptionController).getDefaultSmsSubId();
+        assertTrue(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_MMS));
+
+        doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
+
+        assertFalse(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_DEFAULT));
+
+        doReturn(PhoneConstants.State.OFFHOOK).when(mCT).getState();
+
+        assertTrue(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_DEFAULT));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetRules() throws Exception {
+        DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                "mms=nonDefault, default=inVoiceCall&nonDefault");
+        String rules = dataEnabledOverride.getRules();
+        assertEquals(dataEnabledOverride, new DataEnabledOverride(rules));
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateRules() throws Exception {
+        DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                "mms=nonDefault, default=inVoiceCall&nonDefault");
+        doReturn(1).when(mPhone).getSubId();
+        doReturn(2).when(mSubscriptionController).getDefaultSmsSubId();
+        assertTrue(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_MMS));
+
+        doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
+
+        assertFalse(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_DEFAULT));
+
+        doReturn(PhoneConstants.State.OFFHOOK).when(mCT).getState();
+
+        assertTrue(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_DEFAULT));
+
+        dataEnabledOverride.updateRules("");
+
+        assertFalse(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_MMS));
+        assertFalse(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_DEFAULT));
+    }
+
+    @Test
+    @SmallTest
+    public void testAlwaysEnabled() throws Exception {
+        DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                "mms   =unconditionally,    default=  unconditionally ,   ");
+        assertTrue(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_MMS));
+        assertTrue(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_DEFAULT));
+
+        dataEnabledOverride.updateRules("");
+
+        assertFalse(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_MMS));
+        assertFalse(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_DEFAULT));
+    }
+
+    @Test
+    @SmallTest
+    public void testAllApnTypesInRule() throws Exception {
+        DataEnabledOverride dataEnabledOverride = new DataEnabledOverride("*=inVoiceCall");
+        doReturn(PhoneConstants.State.OFFHOOK).when(mCT).getState();
+
+        assertTrue(dataEnabledOverride.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_FOTA));
+    }
+
+    @Test
+    @SmallTest
+    public void testInvalidRules() throws Exception {
+        try {
+            DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                    "default=xyz");
+            fail("Invalid conditions but not threw IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+
+        }
+
+        try {
+            DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                    "mms=");
+            fail("Invalid conditions but not threw IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+
+        }
+
+        try {
+            DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                    "abc=nonDefault");
+            fail("Invalid APN type but not threw IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+
+        }
+
+        try {
+            DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                    " =nonDefault");
+            fail("Invalid APN type but not threw IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+
+        }
+
+        try {
+            DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(
+                    "Invalid rule");
+            fail("Invalid rule but not threw IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetAlwaysAllowMms() throws Exception {
+        DataEnabledOverride deo = new DataEnabledOverride("");
+        deo.setAlwaysAllowMms(true);
+        assertTrue(deo.shouldOverrideDataEnabledSettings(mPhone, ApnSetting.TYPE_MMS));
+        deo.setAlwaysAllowMms(false);
+        assertFalse(deo.shouldOverrideDataEnabledSettings(mPhone, ApnSetting.TYPE_MMS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDataAllowedInVoiceCall() throws Exception {
+        DataEnabledOverride deo = new DataEnabledOverride("");
+        deo.setDataAllowedInVoiceCall(true);
+        assertFalse(deo.getRules(), deo.shouldOverrideDataEnabledSettings(mPhone,
+                ApnSetting.TYPE_DEFAULT));
+        assertTrue(deo.isDataAllowedInVoiceCall());
+        doReturn(1).when(mPhone).getSubId();
+        doReturn(2).when(mSubscriptionController).getDefaultSmsSubId();
+        doReturn(PhoneConstants.State.OFFHOOK).when(mCT).getState();
+        assertTrue(deo.getRules(), deo.shouldOverrideDataEnabledSettings(mPhone,
+                ApnSetting.TYPE_DEFAULT));
+        deo.setDataAllowedInVoiceCall(false);
+        assertFalse(deo.shouldOverrideDataEnabledSettings(mPhone, ApnSetting.TYPE_DEFAULT));
+        assertFalse(deo.isDataAllowedInVoiceCall());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledSettingsTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledSettingsTest.java
new file mode 100644
index 0000000..3bc6f47
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledSettingsTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 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.dataconnection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.os.HandlerThread;
+import android.telephony.data.ApnSetting;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Objects;
+
+public class DataEnabledSettingsTest extends TelephonyTest {
+
+    private DataEnabledSettings mDataEnabledSettingsUT;
+
+    private DataEnabledSettingsTestHandler mDataEnabledSettingsTestHandler;
+
+    private String mRules = "";
+
+    private class DataEnabledSettingsTestHandler extends HandlerThread {
+
+        private DataEnabledSettingsTestHandler(String name) {
+            super(name);
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            mDataEnabledSettingsUT = new DataEnabledSettings(mPhone);
+            setReady(true);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        doReturn(mRules).when(mSubscriptionController).getDataEnabledOverrideRules(anyInt());
+
+        doAnswer(invocation -> {
+            String rules = (String) invocation.getArguments()[1];
+            boolean changed = !Objects.equals(mRules, rules);
+            mRules = rules;
+            return changed;
+        }).when(mSubscriptionController).setDataEnabledOverrideRules(anyInt(), anyString());
+
+        mDataEnabledSettingsTestHandler = new DataEnabledSettingsTestHandler(
+                getClass().getSimpleName());
+        mDataEnabledSettingsTestHandler.start();
+        waitUntilReady();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDataAllowedInVoiceCall() throws Exception {
+        mDataEnabledSettingsUT.setAllowDataDuringVoiceCall(true);
+        ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
+        verify(mSubscriptionController).setDataEnabledOverrideRules(anyInt(),
+                stringCaptor.capture());
+        assertEquals("*=nonDefault&inVoiceCall", stringCaptor.getValue());
+
+        clearInvocations(mSubscriptionController);
+
+        mDataEnabledSettingsUT.setAllowDataDuringVoiceCall(false);
+        verify(mSubscriptionController).setDataEnabledOverrideRules(anyInt(),
+                stringCaptor.capture());
+        assertEquals("", stringCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetAlwaysAllowMmsData() throws Exception {
+        mDataEnabledSettingsUT.setUserDataEnabled(false);
+        assertTrue(mDataEnabledSettingsUT.setAlwaysAllowMmsData(true));
+        ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
+        verify(mSubscriptionController).setDataEnabledOverrideRules(anyInt(),
+                stringCaptor.capture());
+        assertEquals("mms=unconditionally", stringCaptor.getValue());
+        assertTrue(mDataEnabledSettingsUT.isDataEnabled(ApnSetting.TYPE_MMS));
+
+        clearInvocations(mSubscriptionController);
+
+        assertTrue(mDataEnabledSettingsUT.setAlwaysAllowMmsData(false));
+        verify(mSubscriptionController).setDataEnabledOverrideRules(anyInt(),
+                stringCaptor.capture());
+        assertEquals("", stringCaptor.getValue());
+        assertFalse(mDataEnabledSettingsUT.isDataEnabled(ApnSetting.TYPE_MMS));
+
+        mDataEnabledSettingsUT.setUserDataEnabled(true);
+        assertTrue(mDataEnabledSettingsUT.isDataEnabled(ApnSetting.TYPE_MMS));
+    }
+}
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 6b5e193..336186d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -115,6 +115,7 @@
     public static final String FAKE_APN3 = "FAKE APN 3";
     public static final String FAKE_APN4 = "FAKE APN 4";
     public static final String FAKE_APN5 = "FAKE APN 5";
+    public static final String FAKE_APN6 = "FAKE APN 6";
     public static final String FAKE_IFNAME = "FAKE IFNAME";
     public static final String FAKE_PCSCF_ADDRESS = "22.33.44.55";
     public static final String FAKE_GATEWAY = "11.22.33.44";
@@ -398,6 +399,39 @@
                             -1                      // skip_464xlat
                     });
 
+                    mc.addRow(new Object[]{
+                            2168,                   // id
+                            FAKE_PLMN,              // numeric
+                            "sp-mode",              // name
+                            FAKE_APN6,              // apn
+                            "",                     // proxy
+                            "",                     // port
+                            "",                     // mmsc
+                            "",                     // mmsproxy
+                            "",                     // mmsport
+                            "",                     // user
+                            "",                     // password
+                            -1,                     // authtype
+                            "mms",                  // types
+                            "IP",                   // protocol
+                            "IP",                   // roaming_protocol
+                            1,                      // carrier_enabled
+                            ServiceState.RIL_RADIO_TECHNOLOGY_LTE, // bearer
+                            0,                      // bearer_bitmask
+                            0,                      // profile_id
+                            1,                      // modem_cognitive
+                            0,                      // max_conns
+                            0,                      // wait_time
+                            0,                      // max_conns_time
+                            0,                      // mtu
+                            "",                     // mvno_type
+                            "",                     // mnvo_match_data
+                            NETWORK_TYPE_LTE_BITMASK, // network_type_bitmask
+                            0,                      // apn_set_id
+                            -1,                     // carrier_id
+                            -1                      // skip_464xlat
+                    });
+
                     return mc;
                 }
             } else if (uri.isPathPrefixMatch(
@@ -735,11 +769,7 @@
 
     @Test
     @MediumTest
-    @Ignore
-    @FlakyTest
-    public void testApnWhiteList() throws Exception {
-        //step 1: setup two DataCalls one for Metered: default, another one for Non-metered: IMS
-        //set Default and MMS to be metered in the CarrierConfigManager
+    public void testTrySetupDataMmsAllowedDataDisabled() throws Exception {
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
         mDct.enableApn(ApnSetting.TYPE_MMS, DcTracker.REQUEST_TYPE_NORMAL, null);
@@ -751,44 +781,31 @@
 
         logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        waitForMs(500);
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
                 eq(AccessNetworkType.EUTRAN), dpCaptor.capture(),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
                 any(Message.class));
-        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 5, 1, NETWORK_TYPE_LTE_BITMASK);
+        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, NETWORK_TYPE_LTE_BITMASK);
 
         logd("Sending DATA_DISABLED_CMD for default data");
         doReturn(false).when(mDataEnabledSettings).isDataEnabled();
         doReturn(false).when(mDataEnabledSettings).isDataEnabled(anyInt());
-        doReturn(true).when(mDataEnabledSettings).isDataEnabled(ApnSetting.TYPE_MMS);
-        AsyncResult ar = new AsyncResult(null,
-                new Pair<>(false, DataEnabledSettings.REASON_USER_DATA_ENABLED), null);
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_CHANGED, ar));
+        mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED).sendToTarget();
         waitForMs(200);
 
         // expected tear down all metered DataConnections
-        verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(
-                eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
-                any(Message.class));
-        assertEquals(DctConstants.State.CONNECTED, mDct.getOverallState());
-        assertEquals(DctConstants.State.IDLE, mDct.getState(PhoneConstants.APN_TYPE_DEFAULT));
-        assertEquals(DctConstants.State.CONNECTED, mDct.getState(PhoneConstants.APN_TYPE_MMS));
-
-        clearInvocations(mSimulatedCommandsVerifier);
-        mDct.notifyApnWhiteListChange(ApnSetting.TYPE_MMS, false);
-        waitForMs(200);
-
-        verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(
+        verify(mSimulatedCommandsVerifier, times(2)).deactivateDataCall(
                 eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
                 any(Message.class));
         assertEquals(DctConstants.State.IDLE, mDct.getState(PhoneConstants.APN_TYPE_DEFAULT));
         assertEquals(DctConstants.State.IDLE, mDct.getState(PhoneConstants.APN_TYPE_MMS));
 
         clearInvocations(mSimulatedCommandsVerifier);
-        mDct.notifyApnWhiteListChange(ApnSetting.TYPE_MMS, true);
+        doReturn(true).when(mDataEnabledSettings).isDataEnabled(ApnSetting.TYPE_MMS);
+        mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED).sendToTarget();
         waitForMs(200);
 
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
index 540eb78..fc80e76 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
@@ -129,7 +129,7 @@
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, params.targetTransport);
 
         // Notify handover succeeded.
-        params.callback.onCompleted(true);
+        params.callback.onCompleted(true, false);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
 
@@ -158,7 +158,7 @@
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
         // Notify handover succeeded.
-        params.callback.onCompleted(true);
+        params.callback.onCompleted(true, false);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
     }
@@ -256,7 +256,7 @@
         assertEquals(1, listQueue.size());
 
         // Notify handover succeeded.
-        params.callback.onCompleted(true);
+        params.callback.onCompleted(true, false);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
         waitForMs(100);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
index a0c15c9..5bdc61c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
@@ -766,6 +766,12 @@
         verify(mImsManager, times(1)).setWfcMode(anyInt(), anyBoolean());
     }
 
+    @Test
+    public void testNonNullTrackersInImsPhone() throws Exception {
+        assertNotNull(mImsPhoneUT.getEmergencyNumberTracker());
+        assertNotNull(mImsPhoneUT.getServiceStateTracker());
+    }
+
     private ServiceState getServiceStateDataAndVoice(int rat, int regState, boolean isRoaming) {
         ServiceState ss = new ServiceState();
         ss.setStateOutOfService();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
index ff976d2..a6db17e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
@@ -41,6 +41,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.feature.MmTelFeature;
@@ -73,6 +74,7 @@
 import org.mockito.Mock;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Arrays;
 
 public class TelephonyMetricsTest extends TelephonyTest {
@@ -224,6 +226,39 @@
         assertEquals("gid1Test", log.events[0].carrierIdMatching.result.gid1);
     }
 
+    // Test write Emergency Number update event
+    @Test
+    @SmallTest
+    public void testWriteEmergencyNumberUpdateEvent() throws Exception {
+        EmergencyNumber number = new EmergencyNumber(
+                "911",
+                "us",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        mMetrics.writeEmergencyNumberUpdateEvent(mPhone.getPhoneId(), number);
+        TelephonyLog log = buildProto();
+
+        assertEquals(1, log.events.length);
+        assertEquals(0, log.callSessions.length);
+        assertEquals(0, log.smsSessions.length);
+
+        assertEquals(mPhone.getPhoneId(), log.events[0].phoneId);
+        assertEquals(TelephonyEvent.Type.EMERGENCY_NUMBER_REPORT, log.events[0].type);
+        assertEquals("911", log.events[0].updatedEmergencyNumber.address);
+        assertEquals("30", log.events[0].updatedEmergencyNumber.mnc);
+        assertEquals(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                log.events[0].updatedEmergencyNumber.serviceCategoriesBitmask);
+        assertEquals(0, log.events[0].updatedEmergencyNumber.urns.length);
+        assertEquals(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                log.events[0].updatedEmergencyNumber.numberSourcesBitmask);
+        assertEquals(EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL,
+                log.events[0].updatedEmergencyNumber.routing);
+    }
+
     // Test write on IMS call start
     @Test
     @SmallTest
@@ -516,7 +551,7 @@
         doReturn(Call.State.DIALING).when(mConnection).getState();
         mMetrics.writeRilDial(mPhone.getPhoneId(), mConnection, 2, mUusInfo);
         doReturn(Call.State.DISCONNECTED).when(mConnection).getState();
-        mMetrics.writeRilHangup(mPhone.getPhoneId(), mConnection, 3);
+        mMetrics.writeRilHangup(mPhone.getPhoneId(), mConnection, 3, "");
         mMetrics.writePhoneState(mPhone.getPhoneId(), PhoneConstants.State.IDLE);
         TelephonyLog log = buildProto();