Merge "Enhance ImsManager tests to include tests for roaming setting"
diff --git a/Android.bp b/Android.bp
index 80070b7..8aea8ad 100644
--- a/Android.bp
+++ b/Android.bp
@@ -40,6 +40,8 @@
         "src/java/**/*.logtags",
     ],
 
+    jarjar_rules: ":framework-hidl-jarjar",
+
     libs: [
         "voip-common",
         "ims-common",
@@ -58,6 +60,7 @@
         "android.hardware.radio.config-V1.2-java",
         "android.hardware.radio.deprecated-V1.0-java",
         "android.hidl.base-V1.0-java",
+	"android-support-annotations",
     ],
 
     product_variables: {
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index 31d8ea8..9e1fa0c 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -599,6 +599,10 @@
   PDP_TYPE_IPV4V6 = 3;
 
   PDP_TYPE_PPP = 4;
+
+  PDP_TYPE_NON_IP = 5;
+
+  PDP_TYPE_UNSTRUCTURED = 6;
 }
 
 // The information about packet data connection
@@ -764,6 +768,8 @@
 
       PDP_FAIL_PDP_WITHOUT_ACTIVE_TFT = 46;
 
+      PDP_FAIL_ACTIVATION_REJECTED_BCM_VIOLATION = 48;
+
       PDP_FAIL_ONLY_IPV4_ALLOWED = 50;
 
       PDP_FAIL_ONLY_IPV6_ALLOWED = 51;
@@ -776,6 +782,16 @@
 
       PDP_FAIL_MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 55;
 
+      PDP_FAIL_COLLISION_WITH_NETWORK_INITIATED_REQUEST = 56;
+
+      PDP_FAIL_ONLY_IPV4V6_ALLOWED = 57;
+
+      PDP_FAIL_ONLY_NON_IP_ALLOWED = 58;
+
+      PDP_FAIL_UNSUPPORTED_QCI_VALUE = 59;
+
+      PDP_FAIL_BEARER_HANDLING_NOT_SUPPORTED = 60;
+
       PDP_FAIL_MAX_ACTIVE_PDP_CONTEXT_REACHED = 65;
 
       PDP_FAIL_UNSUPPORTED_APN_IN_CURRENT_PLMN = 66;
@@ -820,6 +836,518 @@
 
       PDP_FAIL_AUTH_FAILURE_ON_EMERGENCY_CALL = 122;
 
+      PDP_FAIL_INVALID_DNS_ADDR = 123;
+
+      PDP_FAIL_INVALID_PCSCF_OR_DNS_ADDRESS = 124;
+
+      PDP_FAIL_CALL_PREEMPT_BY_EMERGENCY_APN = 127;
+
+      PDP_FAIL_UE_INITIATED_DETACH_OR_DISCONNECT = 128;
+
+      PDP_FAIL_MIP_FA_REASON_UNSPECIFIED = 2000;
+
+      PDP_FAIL_MIP_FA_ADMIN_PROHIBITED = 2001;
+
+      PDP_FAIL_MIP_FA_INSUFFICIENT_RESOURCES = 2002;
+
+      PDP_FAIL_MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2003;
+
+      PDP_FAIL_MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 2004;
+
+      PDP_FAIL_MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 2005;
+
+      PDP_FAIL_MIP_FA_MALFORMED_REQUEST = 2006;
+
+      PDP_FAIL_MIP_FA_MALFORMED_REPLY = 2007;
+
+      PDP_FAIL_MIP_FA_ENCAPSULATION_UNAVAILABLE = 2008;
+
+      PDP_FAIL_MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 2009;
+
+      PDP_FAIL_MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 2010;
+
+      PDP_FAIL_MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 2011;
+
+      PDP_FAIL_MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 2012;
+
+      PDP_FAIL_MIP_FA_MISSING_NAI = 2013;
+
+      PDP_FAIL_MIP_FA_MISSING_HOME_AGENT = 2014;
+
+      PDP_FAIL_MIP_FA_MISSING_HOME_ADDRESS = 2015;
+
+      PDP_FAIL_MIP_FA_UNKNOWN_CHALLENGE = 2016;
+
+      PDP_FAIL_MIP_FA_MISSING_CHALLENGE = 2017;
+
+      PDP_FAIL_MIP_FA_STALE_CHALLENGE = 2018;
+
+      PDP_FAIL_MIP_HA_REASON_UNSPECIFIED = 2019;
+
+      PDP_FAIL_MIP_HA_ADMIN_PROHIBITED = 2020;
+
+      PDP_FAIL_MIP_HA_INSUFFICIENT_RESOURCES = 2021;
+
+      PDP_FAIL_MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2022;
+
+      PDP_FAIL_MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 2023;
+
+      PDP_FAIL_MIP_HA_REGISTRATION_ID_MISMATCH = 2024;
+
+      PDP_FAIL_MIP_HA_MALFORMED_REQUEST = 2025;
+
+      PDP_FAIL_MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 2026;
+
+      PDP_FAIL_MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 2027;
+
+      PDP_FAIL_MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 2028;
+
+      PDP_FAIL_MIP_HA_ENCAPSULATION_UNAVAILABLE = 2029;
+
+      PDP_FAIL_CLOSE_IN_PROGRESS = 2030;
+
+      PDP_FAIL_NETWORK_INITIATED_TERMINATION = 2031;
+
+      PDP_FAIL_MODEM_APP_PREEMPTED = 2032;
+
+      PDP_FAIL_PDN_IPV4_CALL_DISALLOWED = 2033;
+
+      PDP_FAIL_PDN_IPV4_CALL_THROTTLED = 2034;
+
+      PDP_FAIL_PDN_IPV6_CALL_DISALLOWED = 2035;
+
+      PDP_FAIL_PDN_IPV6_CALL_THROTTLED = 2036;
+
+      PDP_FAIL_MODEM_RESTART = 2037;
+
+      PDP_FAIL_PDP_PPP_NOT_SUPPORTED = 2038;
+
+      PDP_FAIL_UNPREFERRED_RAT = 2039;
+
+      PDP_FAIL_PHYSICAL_LINK_CLOSE_IN_PROGRESS = 2040;
+
+      PDP_FAIL_APN_PENDING_HANDOVER = 2041;
+
+      PDP_FAIL_PROFILE_BEARER_INCOMPATIBLE = 2042;
+
+      PDP_FAIL_SIM_CARD_CHANGED = 2043;
+
+      PDP_FAIL_LOW_POWER_MODE_OR_POWERING_DOWN = 2044;
+
+      PDP_FAIL_APN_DISABLED = 2045;
+
+      PDP_FAIL_MAX_PPP_INACTIVITY_TIMER_EXPIRED = 2046;
+
+      PDP_FAIL_IPV6_ADDRESS_TRANSFER_FAILED = 2047;
+
+      PDP_FAIL_TRAT_SWAP_FAILED = 2048;
+
+      PDP_FAIL_EHRPD_TO_HRPD_FALLBACK = 2049;
+
+      PDP_FAIL_MIP_CONFIG_FAILURE = 2050;
+
+      PDP_FAIL_PDN_INACTIVITY_TIMER_EXPIRED = 2051;
+
+      PDP_FAIL_MAX_IPV4_CONNECTIONS = 2052;
+
+      PDP_FAIL_MAX_IPV6_CONNECTIONS = 2053;
+
+      PDP_FAIL_APN_MISMATCH = 2054;
+
+      PDP_FAIL_IP_VERSION_MISMATCH = 2055;
+
+      PDP_FAIL_DUN_CALL_DISALLOWED = 2056;
+
+      PDP_FAIL_INTERNAL_EPC_NONEPC_TRANSITION = 2057;
+
+      PDP_FAIL_INTERFACE_IN_USE = 2058;
+
+      PDP_FAIL_APN_DISALLOWED_ON_ROAMING = 2059;
+
+      PDP_FAIL_APN_PARAMETERS_CHANGED = 2060;
+
+      PDP_FAIL_NULL_APN_DISALLOWED = 2061;
+
+      PDP_FAIL_THERMAL_MITIGATION = 2062;
+
+      PDP_FAIL_DATA_SETTINGS_DISABLED = 2063;
+
+      PDP_FAIL_DATA_ROAMING_SETTINGS_DISABLED = 2064;
+
+      PDP_FAIL_DDS_SWITCHED = 2065;
+
+      PDP_FAIL_FORBIDDEN_APN_NAME = 2066;
+
+      PDP_FAIL_DDS_SWITCH_IN_PROGRESS = 2067;
+
+      PDP_FAIL_CALL_DISALLOWED_IN_ROAMING = 2068;
+
+      PDP_FAIL_NON_IP_NOT_SUPPORTED = 2069;
+
+      PDP_FAIL_PDN_NON_IP_CALL_THROTTLED = 2070;
+
+      PDP_FAIL_PDN_NON_IP_CALL_DISALLOWED = 2071;
+
+      PDP_FAIL_CDMA_LOCK = 2072;
+
+      PDP_FAIL_CDMA_INTERCEPT = 2073;
+
+      PDP_FAIL_CDMA_REORDER = 2074;
+
+      PDP_FAIL_CDMA_RELEASE_DUE_TO_SO_REJECTION = 2075;
+
+      PDP_FAIL_CDMA_INCOMING_CALL = 2076;
+
+      PDP_FAIL_CDMA_ALERT_STOP = 2077;
+
+      PDP_FAIL_CHANNEL_ACQUISITION_FAILURE = 2078;
+
+      PDP_FAIL_MAX_ACCESS_PROBE = 2079;
+
+      PDP_FAIL_CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 2080;
+
+      PDP_FAIL_NO_RESPONSE_FROM_BASE_STATION = 2081;
+
+      PDP_FAIL_REJECTED_BY_BASE_STATION = 2082;
+
+      PDP_FAIL_CONCURRENT_SERVICES_INCOMPATIBLE = 2083;
+
+      PDP_FAIL_NO_CDMA_SERVICE = 2084;
+
+      PDP_FAIL_RUIM_NOT_PRESENT = 2085;
+
+      PDP_FAIL_CDMA_RETRY_ORDER = 2086;
+
+      PDP_FAIL_ACCESS_BLOCK = 2087;
+
+      PDP_FAIL_ACCESS_BLOCK_ALL = 2088;
+
+      PDP_FAIL_IS707B_MAX_ACCESS_PROBES = 2089;
+
+      PDP_FAIL_THERMAL_EMERGENCY = 2090;
+
+      PDP_FAIL_CONCURRENT_SERVICES_NOT_ALLOWED = 2091;
+
+      PDP_FAIL_INCOMING_CALL_REJECTED = 2092;
+
+      PDP_FAIL_NO_SERVICE_ON_GATEWAY = 2093;
+
+      PDP_FAIL_NO_GPRS_CONTEXT = 2094;
+
+      PDP_FAIL_ILLEGAL_MS = 2095;
+
+      PDP_FAIL_ILLEGAL_ME = 2096;
+
+      PDP_FAIL_GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 2097;
+
+      PDP_FAIL_GPRS_SERVICES_NOT_ALLOWED = 2098;
+
+      PDP_FAIL_MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 2099;
+
+      PDP_FAIL_IMPLICITLY_DETACHED = 2100;
+
+      PDP_FAIL_PLMN_NOT_ALLOWED = 2101;
+
+      PDP_FAIL_LOCATION_AREA_NOT_ALLOWED = 2102;
+
+      PDP_FAIL_GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 2103;
+
+      PDP_FAIL_PDP_DUPLICATE = 2104;
+
+      PDP_FAIL_UE_RAT_CHANGE = 2105;
+
+      PDP_FAIL_CONGESTION = 2106;
+
+      PDP_FAIL_NO_PDP_CONTEXT_ACTIVATED = 2107;
+
+      PDP_FAIL_ACCESS_CLASS_DSAC_REJECTION = 2108;
+
+      PDP_FAIL_PDP_ACTIVATE_MAX_RETRY_FAILED = 2109;
+
+      PDP_FAIL_RADIO_ACCESS_BEARER_FAILURE = 2110;
+
+      PDP_FAIL_ESM_UNKNOWN_EPS_BEARER_CONTEXT = 2111;
+
+      PDP_FAIL_DRB_RELEASED_BY_RRC = 2112;
+
+      PDP_FAIL_CONNECTION_RELEASED = 2113;
+
+      PDP_FAIL_EMM_DETACHED = 2114;
+
+      PDP_FAIL_EMM_ATTACH_FAILED = 2115;
+
+      PDP_FAIL_EMM_ATTACH_STARTED = 2116;
+
+      PDP_FAIL_LTE_NAS_SERVICE_REQUEST_FAILED = 2117;
+
+      PDP_FAIL_DUPLICATE_BEARER_ID = 2118;
+
+      PDP_FAIL_ESM_COLLISION_SCENARIOS = 2119;
+
+      PDP_FAIL_ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 2120;
+
+      PDP_FAIL_ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 2121;
+
+      PDP_FAIL_ESM_BAD_OTA_MESSAGE = 2122;
+
+      PDP_FAIL_ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 2123;
+
+      PDP_FAIL_ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 2124;
+
+      PDP_FAIL_DS_EXPLICIT_DEACTIVATION = 2125;
+
+      PDP_FAIL_ESM_LOCAL_CAUSE_NONE = 2126;
+
+      PDP_FAIL_LTE_THROTTLING_NOT_REQUIRED = 2127;
+
+      PDP_FAIL_ACCESS_CONTROL_LIST_CHECK_FAILURE = 2128;
+
+      PDP_FAIL_SERVICE_NOT_ALLOWED_ON_PLMN = 2129;
+
+      PDP_FAIL_EMM_T3417_EXPIRED = 2130;
+
+      PDP_FAIL_EMM_T3417_EXT_EXPIRED = 2131;
+
+      PDP_FAIL_RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 2132;
+
+      PDP_FAIL_RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 2133;
+
+      PDP_FAIL_RRC_UPLINK_CONNECTION_RELEASE = 2134;
+
+      PDP_FAIL_RRC_UPLINK_RADIO_LINK_FAILURE = 2135;
+
+      PDP_FAIL_RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 2136;
+
+      PDP_FAIL_RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 2137;
+
+      PDP_FAIL_RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 2138;
+
+      PDP_FAIL_RRC_CONNECTION_ACCESS_BARRED = 2139;
+
+      PDP_FAIL_RRC_CONNECTION_CELL_RESELECTION = 2140;
+
+      PDP_FAIL_RRC_CONNECTION_CONFIG_FAILURE = 2141;
+
+      PDP_FAIL_RRC_CONNECTION_TIMER_EXPIRED = 2142;
+
+      PDP_FAIL_RRC_CONNECTION_LINK_FAILURE = 2143;
+
+      PDP_FAIL_RRC_CONNECTION_CELL_NOT_CAMPED = 2144;
+
+      PDP_FAIL_RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 2145;
+
+      PDP_FAIL_RRC_CONNECTION_REJECT_BY_NETWORK = 2146;
+
+      PDP_FAIL_RRC_CONNECTION_NORMAL_RELEASE = 2147;
+
+      PDP_FAIL_RRC_CONNECTION_RADIO_LINK_FAILURE = 2148;
+
+      PDP_FAIL_RRC_CONNECTION_REESTABLISHMENT_FAILURE = 2149;
+
+      PDP_FAIL_RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 2150;
+
+      PDP_FAIL_RRC_CONNECTION_ABORT_REQUEST = 2151;
+
+      PDP_FAIL_RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 2152;
+
+      PDP_FAIL_NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 2153;
+
+      PDP_FAIL_NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 2154;
+
+      PDP_FAIL_ESM_PROCEDURE_TIME_OUT = 2155;
+
+      PDP_FAIL_INVALID_CONNECTION_ID = 2156;
+
+      PDP_FAIL_MAXIMIUM_NSAPIS_EXCEEDED = 2157;
+
+      PDP_FAIL_INVALID_PRIMARY_NSAPI = 2158;
+
+      PDP_FAIL_CANNOT_ENCODE_OTA_MESSAGE = 2159;
+
+      PDP_FAIL_RADIO_ACCESS_BEARER_SETUP_FAILURE = 2160;
+
+      PDP_FAIL_PDP_ESTABLISH_TIMEOUT_EXPIRED = 2161;
+
+      PDP_FAIL_PDP_MODIFY_TIMEOUT_EXPIRED = 2162;
+
+      PDP_FAIL_PDP_INACTIVE_TIMEOUT_EXPIRED = 2163;
+
+      PDP_FAIL_PDP_LOWERLAYER_ERROR = 2164;
+
+      PDP_FAIL_PDP_MODIFY_COLLISION = 2165;
+
+      PDP_FAIL_MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 2166;
+
+      PDP_FAIL_NAS_REQUEST_REJECTED_BY_NETWORK = 2167;
+
+      PDP_FAIL_RRC_CONNECTION_INVALID_REQUEST = 2168;
+
+      PDP_FAIL_RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 2169;
+
+      PDP_FAIL_RRC_CONNECTION_RF_UNAVAILABLE = 2170;
+
+      PDP_FAIL_RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 2171;
+
+      PDP_FAIL_RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 2172;
+
+      PDP_FAIL_RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 2173;
+
+      PDP_FAIL_RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 2174;
+
+      PDP_FAIL_RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 2175;
+
+      PDP_FAIL_IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 2176;
+
+      PDP_FAIL_IMEI_NOT_ACCEPTED = 2177;
+
+      PDP_FAIL_EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 2178;
+
+      PDP_FAIL_EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 2179;
+
+      PDP_FAIL_MSC_TEMPORARILY_NOT_REACHABLE = 2180;
+
+      PDP_FAIL_CS_DOMAIN_NOT_AVAILABLE = 2181;
+
+      PDP_FAIL_ESM_FAILURE = 2182;
+
+      PDP_FAIL_MAC_FAILURE = 2183;
+
+      PDP_FAIL_SYNCHRONIZATION_FAILURE = 2184;
+
+      PDP_FAIL_UE_SECURITY_CAPABILITIES_MISMATCH = 2185;
+
+      PDP_FAIL_SECURITY_MODE_REJECTED = 2186;
+
+      PDP_FAIL_UNACCEPTABLE_NON_EPS_AUTHENTICATION = 2187;
+
+      PDP_FAIL_CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 2188;
+
+      PDP_FAIL_NO_EPS_BEARER_CONTEXT_ACTIVATED = 2189;
+
+      PDP_FAIL_INVALID_EMM_STATE = 2190;
+
+      PDP_FAIL_NAS_LAYER_FAILURE = 2191;
+
+      PDP_FAIL_MULTIPLE_PDP_CALL_NOT_ALLOWED = 2192;
+
+      PDP_FAIL_EMBMS_NOT_ENABLED = 2193;
+
+      PDP_FAIL_IRAT_HANDOVER_FAILED = 2194;
+
+      PDP_FAIL_EMBMS_REGULAR_DEACTIVATION = 2195;
+
+      PDP_FAIL_TEST_LOOPBACK_REGULAR_DEACTIVATION = 2196;
+
+      PDP_FAIL_LOWER_LAYER_REGISTRATION_FAILURE = 2197;
+
+      PDP_FAIL_DATA_PLAN_EXPIRED = 2198;
+
+      PDP_FAIL_UMTS_HANDOVER_TO_IWLAN = 2199;
+
+      PDP_FAIL_EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 2200;
+
+      PDP_FAIL_EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 2201;
+
+      PDP_FAIL_EVDO_HDR_CHANGED = 2202;
+
+      PDP_FAIL_EVDO_HDR_EXITED = 2203;
+
+      PDP_FAIL_EVDO_HDR_NO_SESSION = 2204;
+
+      PDP_FAIL_EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 2205;
+
+      PDP_FAIL_EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 2206;
+
+      PDP_FAIL_FAILED_TO_ACQUIRE_COLOCATED_HDR = 2207;
+
+      PDP_FAIL_OTASP_COMMIT_IN_PROGRESS = 2208;
+
+      PDP_FAIL_NO_HYBRID_HDR_SERVICE = 2209;
+
+      PDP_FAIL_HDR_NO_LOCK_GRANTED = 2210;
+
+      PDP_FAIL_DBM_OR_SMS_IN_PROGRESS = 2211;
+
+      PDP_FAIL_HDR_FADE = 2212;
+
+      PDP_FAIL_HDR_ACCESS_FAILURE = 2213;
+
+      PDP_FAIL_UNSUPPORTED_1X_PREV = 2214;
+
+      PDP_FAIL_LOCAL_END = 2215;
+
+      PDP_FAIL_NO_SERVICE = 2216;
+
+      PDP_FAIL_FADE = 2217;
+
+      PDP_FAIL_NORMAL_RELEASE = 2218;
+
+      PDP_FAIL_ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 2219;
+
+      PDP_FAIL_REDIRECTION_OR_HANDOFF_IN_PROGRESS = 2220;
+
+      PDP_FAIL_EMERGENCY_MODE = 2221;
+
+      PDP_FAIL_PHONE_IN_USE = 2222;
+
+      PDP_FAIL_INVALID_MODE = 2223;
+
+      PDP_FAIL_INVALID_SIM_STATE = 2224;
+
+      PDP_FAIL_NO_COLLOCATED_HDR = 2225;
+
+      PDP_FAIL_UE_IS_ENTERING_POWERSAVE_MODE = 2226;
+
+      PDP_FAIL_DUAL_SWITCH = 2227;
+
+      PDP_FAIL_PPP_TIMEOUT = 2228;
+
+      PDP_FAIL_PPP_AUTH_FAILURE = 2229;
+
+      PDP_FAIL_PPP_OPTION_MISMATCH = 2230;
+
+      PDP_FAIL_PPP_PAP_FAILURE = 2231;
+
+      PDP_FAIL_PPP_CHAP_FAILURE = 2232;
+
+      PDP_FAIL_PPP_CLOSE_IN_PROGRESS = 2233;
+
+      PDP_FAIL_LIMITED_TO_IPV4 = 2234;
+
+      PDP_FAIL_LIMITED_TO_IPV6 = 2235;
+
+      PDP_FAIL_VSNCP_TIMEOUT = 2236;
+
+      PDP_FAIL_VSNCP_GEN_ERROR = 2237;
+
+      PDP_FAIL_VSNCP_APN_UNATHORIZED = 2238;
+
+      PDP_FAIL_VSNCP_PDN_LIMIT_EXCEEDED = 2239;
+
+      PDP_FAIL_VSNCP_NO_PDN_GATEWAY_ADDRESS = 2240;
+
+      PDP_FAIL_VSNCP_PDN_GATEWAY_UNREACHABLE = 2241;
+
+      PDP_FAIL_VSNCP_PDN_GATEWAY_REJECT = 2242;
+
+      PDP_FAIL_VSNCP_INSUFFICIENT_PARAMETERS = 2243;
+
+      PDP_FAIL_VSNCP_RESOURCE_UNAVAILABLE = 2244;
+
+      PDP_FAIL_VSNCP_ADMINISTRATIVELY_PROHIBITED = 2245;
+
+      PDP_FAIL_VSNCP_PDN_ID_IN_USE = 2246;
+
+      PDP_FAIL_VSNCP_SUBSCRIBER_LIMITATION = 2247;
+
+      PDP_FAIL_VSNCP_PDN_EXISTS_FOR_THIS_APN = 2248;
+
+      PDP_FAIL_VSNCP_RECONNECT_NOT_ALLOWED = 2249;
+
+      PDP_FAIL_IPV6_PREFIX_UNAVAILABLE = 2250;
+
+      PDP_FAIL_HANDOFF_PREFERENCE_CHANGED = 2251;
+
       // Not mentioned in the specification
       PDP_FAIL_VOICE_REGISTRATION_FAIL = -1;
 
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index bcc8e1a..73b0e7d 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -21,7 +21,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.WorkSource;
-import android.service.carrier.CarrierIdentifier;
+import android.telephony.CarrierRestrictionRules;
 import android.telephony.ClientRequestStats;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
@@ -2067,11 +2067,11 @@
      * Set allowed carriers
      *
      * @param carriers Allowed carriers
-     * @param result Callback message contains the number of carriers set successfully
+     * @param result Callback message contains the result of the operation
      * @param workSource calling WorkSource
      */
-    default void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result,
-            WorkSource workSource) {}
+    default void setAllowedCarriers(CarrierRestrictionRules carrierRestrictionRules,
+            Message result, WorkSource workSource) {}
 
     /**
      * Get allowed carriers
@@ -2258,6 +2258,14 @@
      */
     void stopNattKeepalive(int sessionHandle, Message result);
 
+    /**
+     * Enable or disable the logical modem.
+     *
+     * @param enable whether to enable or disable the modem
+     * @param result a Message to return to the requester
+     */
+    default void enableModem(boolean enable, Message result) {};
+
     default List<ClientRequestStats> getClientRequestStats() {
         return null;
     }
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index e85db77..4a2fac4 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.CallQuality;
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.DataFailCause;
@@ -270,7 +271,8 @@
                 mRegistry.notifyPreciseCallState(
                         convertPreciseCallState(ringingCall.getState()),
                         convertPreciseCallState(foregroundCall.getState()),
-                        convertPreciseCallState(backgroundCall.getState()));
+                        convertPreciseCallState(backgroundCall.getState()),
+                        sender.getPhoneId());
             } catch (RemoteException ex) {
                 // system process is dead
             }
@@ -373,6 +375,17 @@
         }
     }
 
+    @Override
+    public void notifyCallQualityChanged(Phone sender, CallQuality callQuality) {
+        try {
+            if (mRegistry != null) {
+                mRegistry.notifyCallQualityChanged(callQuality, sender.getPhoneId());
+            }
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
     /**
      * Convert the {@link Phone.DataActivityState} enum into the TelephonyManager.DATA_* constants
      * for the public API.
diff --git a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
index 04d09a5..072181b 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -84,12 +84,13 @@
 
     private final Map<NetworkRegStateCallback, Message> mCallbackTable = new Hashtable();
 
-    public void getNetworkRegistrationState(int domain, Message onCompleteMessage) {
+    public void getNetworkRegistrationState(@NetworkRegistrationState.Domain int domain,
+                                            Message onCompleteMessage) {
         if (onCompleteMessage == null) return;
 
-        logd("getNetworkRegistrationState domain " + domain);
         if (!isServiceConnected()) {
-            logd("service not connected.");
+            loge("service not connected. Domain = "
+                    + ((domain == NetworkRegistrationState.DOMAIN_CS) ? "CS" : "PS"));
             onCompleteMessage.obj = new AsyncResult(onCompleteMessage.obj, null,
                     new IllegalStateException("Service not connected."));
             onCompleteMessage.sendToTarget();
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 9d084dd..8951cd7 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -39,10 +39,10 @@
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
-import android.service.carrier.CarrierIdentifier;
 import android.telecom.VideoProfile;
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.ClientRequestStats;
@@ -3658,9 +3658,9 @@
     /**
      * Set allowed carriers
      */
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message response,
-            WorkSource workSource) {
-        mCi.setAllowedCarriers(carriers, response, workSource);
+    public void setAllowedCarriers(CarrierRestrictionRules carrierRestrictionRules,
+            Message response, WorkSource workSource) {
+        mCi.setAllowedCarriers(carrierRestrictionRules, response, workSource);
     }
 
     /** Sets the SignalStrength reporting criteria. */
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index bf85c18..20d11e7 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.content.Context;
+import android.os.Message;
 import android.telephony.PhoneCapability;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
@@ -82,23 +83,16 @@
     /**
      * Enable or disable phone
      *
-     * @param phoneId which phone to operate on
+     * @param phone which phone to operate on
      * @param enable true or false
-     *
+     * @param result the message to sent back when it's done.
      */
-    public void enablePhone(int phoneId, boolean enable) {
-        // TODO: send command to modem once interface is ready.
-    }
-
-    /**
-     * Enable or disable phone
-     *
-     * @param phoneId which phone to operate on
-     * @param enable true or false
-     *
-     */
-    public void enablePhone(int[] phoneId, boolean[] enable) {
-        // TODO: send command to modem once interface is ready.
+    public void enablePhone(Phone phone, boolean enable, Message result) {
+        if (phone == null) {
+            log("enablePhone failed phone is null");
+            return;
+        }
+        phone.mCi.enableModem(enable, result);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index ce03ad8..a1c1186 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.telephony.CallQuality;
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.DataFailCause;
@@ -80,4 +81,7 @@
 
     /** Notify of change to EmergencyNumberList. */
     void notifyEmergencyNumberList();
+
+    /** Notify of a change to the call quality of an active foreground call. */
+    void notifyCallQualityChanged(Phone sender, CallQuality callQuality);
 }
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 7df50ea..7d2a3ce 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -48,10 +48,14 @@
 import android.hardware.radio.V1_0.SmsWriteArgs;
 import android.hardware.radio.V1_0.UusInfo;
 import android.hardware.radio.V1_2.AccessNetwork;
+import android.hardware.radio.V1_4.CarrierRestrictionsWithPriority;
+import android.hardware.radio.V1_4.SimLockMultiSimPolicy;
 import android.hardware.radio.deprecated.V1_0.IOemHook;
 import android.net.ConnectivityManager;
 import android.net.KeepalivePacketData;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkUtils;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Handler;
@@ -65,6 +69,7 @@
 import android.os.WorkSource;
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
 import android.telephony.ClientRequestStats;
 import android.telephony.ImsiEncryptionInfo;
@@ -80,6 +85,7 @@
 import android.telephony.TelephonyHistogram;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
 import android.telephony.emergency.EmergencyNumber;
@@ -612,7 +618,7 @@
         resetProxyAndRequestList();
     }
 
-    private String convertNullToEmptyString(String string) {
+    private static String convertNullToEmptyString(String string) {
         return string != null ? string : "";
     }
 
@@ -864,6 +870,38 @@
     }
 
     @Override
+    public void enableModem(boolean enable, Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        if (mRadioVersion.less(RADIO_HAL_VERSION_1_3)) {
+            if (RILJ_LOGV) riljLog("enableModem: not supported.");
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+            return;
+        }
+
+        android.hardware.radio.V1_3.IRadio radioProxy13 =
+                (android.hardware.radio.V1_3.IRadio) radioProxy;
+        if (radioProxy13 != null) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_MODEM, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " enable = "
+                        + enable);
+            }
+
+            try {
+                radioProxy13.enableModem(rr.mSerial, enable);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "enableModem", e);
+            }
+        }
+    }
+
+    @Override
     public void dial(String address, boolean isEmergencyCall, EmergencyNumber emergencyNumberInfo,
                      int clirMode, UUSInfo uusInfo, Message result) {
         if (isEmergencyCall && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
@@ -1399,7 +1437,7 @@
                     radioProxy12.setupDataCall_1_2(rr.mSerial, accessNetworkType, dpi,
                             dataProfile.isPersistent(), allowRoaming, isRoaming, reason,
                             addresses, dnses);
-                } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_0)) {
+                } else {
                     // IRadio V1.0 and IRadio V1.1
 
                     // Convert to HAL data profile
@@ -1931,8 +1969,6 @@
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
-                android.hardware.radio.V1_2.IRadio radioProxy12 =
-                        (android.hardware.radio.V1_2.IRadio) radioProxy;
 
                 android.hardware.radio.V1_2.NetworkScanRequest request =
                         new android.hardware.radio.V1_2.NetworkScanRequest();
@@ -1962,7 +1998,15 @@
                 }
 
                 try {
-                    radioProxy12.startNetworkScan_1_2(rr.mSerial, request);
+                    if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+                        android.hardware.radio.V1_4.IRadio radioProxy14 =
+                                (android.hardware.radio.V1_4.IRadio) radioProxy;
+                        radioProxy14.startNetworkScan_1_4(rr.mSerial, request);
+                    } else {
+                        android.hardware.radio.V1_2.IRadio radioProxy12 =
+                                (android.hardware.radio.V1_2.IRadio) radioProxy;
+                        radioProxy12.startNetworkScan_1_2(rr.mSerial, request);
+                    }
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
                 }
@@ -3843,62 +3887,121 @@
 
     }
 
+    /**
+     * Convert a list of CarrierIdentifier into a list of Carrier defined in 1.0/types.hal.
+     * @param carriers List of CarrierIdentifier
+     * @return List of converted objects
+     */
+    @VisibleForTesting
+    public static ArrayList<Carrier> createCarrierRestrictionList(
+            List<CarrierIdentifier> carriers) {
+        ArrayList<Carrier> result = new ArrayList<>();
+        for (CarrierIdentifier ci : carriers) {
+            Carrier c = new Carrier();
+            c.mcc = convertNullToEmptyString(ci.getMcc());
+            c.mnc = convertNullToEmptyString(ci.getMnc());
+            int matchType = CarrierIdentifier.MatchType.ALL;
+            String matchData = null;
+            if (!TextUtils.isEmpty(ci.getSpn())) {
+                matchType = CarrierIdentifier.MatchType.SPN;
+                matchData = ci.getSpn();
+            } else if (!TextUtils.isEmpty(ci.getImsi())) {
+                matchType = CarrierIdentifier.MatchType.IMSI_PREFIX;
+                matchData = ci.getImsi();
+            } else if (!TextUtils.isEmpty(ci.getGid1())) {
+                matchType = CarrierIdentifier.MatchType.GID1;
+                matchData = ci.getGid1();
+            } else if (!TextUtils.isEmpty(ci.getGid2())) {
+                matchType = CarrierIdentifier.MatchType.GID2;
+                matchData = ci.getGid2();
+            }
+            c.matchType = matchType;
+            c.matchData = convertNullToEmptyString(matchData);
+            result.add(c);
+        }
+        return result;
+    }
+
     @Override
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result,
-            WorkSource workSource) {
-        checkNotNull(carriers, "Allowed carriers list cannot be null.");
+    public void setAllowedCarriers(CarrierRestrictionRules carrierRestrictionRules,
+            Message result, WorkSource workSource) {
+        riljLog("RIL.java - setAllowedCarriers");
+
+        checkNotNull(carrierRestrictionRules, "Carrier restriction cannot be null.");
         workSource = getDeafultWorkSourceIfInvalid(workSource);
 
         IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_CARRIERS, result,
-                    workSource);
+        if (radioProxy == null) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                String logStr = "";
-                for (int i = 0; i < carriers.size(); i++) {
-                    logStr = logStr + carriers.get(i) + " ";
-                }
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " carriers = "
-                        + logStr);
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_CARRIERS, result, workSource);
 
-            boolean allAllowed;
-            if (carriers.size() == 0) {
-                allAllowed = true;
-            } else {
-                allAllowed = false;
-            }
-            CarrierRestrictions carrierList = new CarrierRestrictions();
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " params: "
+                    + carrierRestrictionRules);
+        }
 
-            for (CarrierIdentifier ci : carriers) { /* allowed carriers */
-                Carrier c = new Carrier();
-                c.mcc = convertNullToEmptyString(ci.getMcc());
-                c.mnc = convertNullToEmptyString(ci.getMnc());
-                int matchType = CarrierIdentifier.MatchType.ALL;
-                String matchData = null;
-                if (!TextUtils.isEmpty(ci.getSpn())) {
-                    matchType = CarrierIdentifier.MatchType.SPN;
-                    matchData = ci.getSpn();
-                } else if (!TextUtils.isEmpty(ci.getImsi())) {
-                    matchType = CarrierIdentifier.MatchType.IMSI_PREFIX;
-                    matchData = ci.getImsi();
-                } else if (!TextUtils.isEmpty(ci.getGid1())) {
-                    matchType = CarrierIdentifier.MatchType.GID1;
-                    matchData = ci.getGid1();
-                } else if (!TextUtils.isEmpty(ci.getGid2())) {
-                    matchType = CarrierIdentifier.MatchType.GID2;
-                    matchData = ci.getGid2();
-                }
-                c.matchType = matchType;
-                c.matchData = convertNullToEmptyString(matchData);
-                carrierList.allowedCarriers.add(c);
-            }
+        // Extract multisim policy
+        int policy = SimLockMultiSimPolicy.NO_MULTISIM_POLICY;
+        switch (carrierRestrictionRules.getMultiSimPolicy()) {
+            case CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT:
+                policy = SimLockMultiSimPolicy.ONE_VALID_SIM_MUST_BE_PRESENT;
+                break;
+        }
 
-            /* TODO: add excluded carriers */
+        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+            riljLog("RIL.java - Using IRadio 1.4 or greater");
+
+            android.hardware.radio.V1_4.IRadio radioProxy14 =
+                    (android.hardware.radio.V1_4.IRadio) radioProxy;
+
+            // Prepare structure with allowed list, excluded list and priority
+            CarrierRestrictionsWithPriority carrierRestrictions =
+                    new CarrierRestrictionsWithPriority();
+            carrierRestrictions.allowedCarriers =
+                    createCarrierRestrictionList(carrierRestrictionRules.getAllowedCarriers());
+            carrierRestrictions.excludedCarriers =
+                    createCarrierRestrictionList(carrierRestrictionRules.getExcludedCarriers());
+            carrierRestrictions.allowedCarriersPrioritized =
+                    (carrierRestrictionRules.getDefaultCarrierRestriction()
+                        == CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED);
 
             try {
-                radioProxy.setAllowedCarriers(rr.mSerial, allAllowed, carrierList);
+                radioProxy14.setAllowedCarriers_1_4(rr.mSerial, carrierRestrictions, policy);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "setAllowedCarriers_1_4", e);
+            }
+        } else {
+            boolean isAllCarriersAllowed = carrierRestrictionRules.isAllCarriersAllowed();
+
+            boolean supported = (isAllCarriersAllowed
+                    || (carrierRestrictionRules.getExcludedCarriers().isEmpty()
+                        && (carrierRestrictionRules.getDefaultCarrierRestriction()
+                            == CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED)));
+            supported = supported && (policy == SimLockMultiSimPolicy.NO_MULTISIM_POLICY);
+
+            if (!supported) {
+                // Feature is not supported by IRadio interface
+                riljLoge("setAllowedCarriers does not support excluded list on IRadio version"
+                        + " less than 1.4");
+                if (result != null) {
+                    AsyncResult.forMessage(result, null,
+                            CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                    result.sendToTarget();
+                }
+                return;
+            }
+            riljLog("RIL.java - Using IRadio 1.3 or lower");
+
+            // Prepare structure with allowed list
+            CarrierRestrictions carrierRestrictions = new CarrierRestrictions();
+            carrierRestrictions.allowedCarriers =
+                    createCarrierRestrictionList(carrierRestrictionRules.getAllowedCarriers());
+
+            try {
+                radioProxy.setAllowedCarriers(rr.mSerial, isAllCarriersAllowed,
+                        carrierRestrictions);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setAllowedCarriers", e);
             }
@@ -3910,13 +4013,30 @@
         workSource = getDeafultWorkSourceIfInvalid(workSource);
 
         IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_ALLOWED_CARRIERS, result,
-                    workSource);
+        if (radioProxy == null) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_ALLOWED_CARRIERS, result,
+                workSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+        }
+
+        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+            riljLog("RIL.java - Using IRadio 1.4 or greater");
+
+            android.hardware.radio.V1_4.IRadio radioProxy14 =
+                    (android.hardware.radio.V1_4.IRadio) radioProxy;
+
+            try {
+                radioProxy14.getAllowedCarriers_1_4(rr.mSerial);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "getAllowedCarriers_1_4", e);
             }
+        } else {
+            riljLog("RIL.java - Using IRadio 1.3 or lower");
 
             try {
                 radioProxy.getAllowedCarriers(rr.mSerial);
@@ -5162,6 +5282,8 @@
                 return "RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA";
             case RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA:
                 return "RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA";
+            case RIL_REQUEST_ENABLE_MODEM:
+                return "RIL_REQUEST_ENABLE_MODEM";
             default: return "<unknown request>";
         }
     }
@@ -5510,6 +5632,136 @@
     }
 
     /**
+     * Convert SetupDataCallResult defined in 1.0 or 1.4/types.hal into DataCallResponse
+     * @param dcResult setup data call result
+     * @return converted DataCallResponse object
+     */
+    @VisibleForTesting
+    public static DataCallResponse convertDataCallResult(Object dcResult) {
+        if (dcResult == null) return null;
+
+        int status, suggestedRetryTime, cid, active, mtu;
+        String type, ifname;
+        String[] addresses = null;
+        String[] dnses = null;
+        String[] gateways = null;
+        List<String> pcscfs;
+        if (dcResult instanceof android.hardware.radio.V1_0.SetupDataCallResult) {
+            final android.hardware.radio.V1_0.SetupDataCallResult result =
+                    (android.hardware.radio.V1_0.SetupDataCallResult) dcResult;
+            status = result.status;
+            suggestedRetryTime = result.suggestedRetryTime;
+            cid = result.cid;
+            active = result.active;
+            type = result.type;
+            ifname = result.ifname;
+            if (!TextUtils.isEmpty(result.addresses)) {
+                addresses = result.addresses.split("\\s+");
+            }
+            if (!TextUtils.isEmpty(result.dnses)) {
+                dnses = result.dnses.split("\\s+");
+            }
+            if (!TextUtils.isEmpty(result.gateways)) {
+                gateways = result.gateways.split("\\s+");
+            }
+            pcscfs = new ArrayList<>(Arrays.asList(result.pcscf.trim().split("\\s+")));
+            mtu = result.mtu;
+        } else if (dcResult instanceof android.hardware.radio.V1_4.SetupDataCallResult) {
+            final android.hardware.radio.V1_4.SetupDataCallResult result =
+                    (android.hardware.radio.V1_4.SetupDataCallResult) dcResult;
+            status = result.cause;
+            suggestedRetryTime = result.suggestedRetryTime;
+            cid = result.cid;
+            active = result.active;
+            type = ApnSetting.getProtocolStringFromInt(result.type);
+            ifname = result.ifname;
+            addresses = result.addresses.stream().toArray(String[]::new);
+            dnses = result.dnses.stream().toArray(String[]::new);
+            gateways = result.gateways.stream().toArray(String[]::new);
+            pcscfs = result.pcscf;
+            mtu = result.mtu;
+        } else {
+            Rlog.e(RILJ_LOG_TAG, "Unsupported SetupDataCallResult " + dcResult);
+            return null;
+        }
+
+        // Process address
+        List<LinkAddress> laList = new ArrayList<>();
+        if (addresses != null) {
+            for (String address : addresses) {
+                address = address.trim();
+                if (address.isEmpty()) continue;
+
+                try {
+                    LinkAddress la;
+                    // Check if the address contains prefix length. If yes, LinkAddress
+                    // can parse that.
+                    if (address.split("/").length == 2) {
+                        la = new LinkAddress(address);
+                    } else {
+                        InetAddress ia = NetworkUtils.numericToInetAddress(address);
+                        la = new LinkAddress(ia, (ia instanceof Inet4Address) ? 32 : 128);
+                    }
+
+                    laList.add(la);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(RILJ_LOG_TAG, "Unknown address: " + address, e);
+                }
+            }
+        }
+
+        // Process dns
+        List<InetAddress> dnsList = new ArrayList<>();
+        if (dnses != null) {
+            for (String dns : dnses) {
+                dns = dns.trim();
+                InetAddress ia;
+                try {
+                    ia = NetworkUtils.numericToInetAddress(dns);
+                    dnsList.add(ia);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(RILJ_LOG_TAG, "Unknown dns: " + dns, e);
+                }
+            }
+        }
+
+        // Process gateway
+        List<InetAddress> gatewayList = new ArrayList<>();
+        if (gateways != null) {
+            for (String gateway : gateways) {
+                gateway = gateway.trim();
+                InetAddress ia;
+                try {
+                    ia = NetworkUtils.numericToInetAddress(gateway);
+                    gatewayList.add(ia);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(RILJ_LOG_TAG, "Unknown gateway: " + gateway, e);
+                }
+            }
+        }
+
+        return new DataCallResponse(status, suggestedRetryTime, cid, active, type, ifname, laList,
+                dnsList, gatewayList, pcscfs, mtu);
+    }
+
+    /**
+     * Convert SetupDataCallResult defined in 1.0 or 1.4/types.hal into DataCallResponse
+     * @param dataCallResultList List of SetupDataCallResult defined in 1.0 or 1.4/types.hal
+     * @return List of converted DataCallResponse object
+     */
+    @VisibleForTesting
+    public static ArrayList<DataCallResponse> convertDataCallResultList(
+            List<? extends Object> dataCallResultList) {
+        ArrayList<DataCallResponse> response =
+                new ArrayList<DataCallResponse>(dataCallResultList.size());
+
+        for (Object obj : dataCallResultList) {
+            response.add(convertDataCallResult(obj));
+        }
+        return response;
+    }
+
+    /**
      * @return The {@link IwlanOperationMode IWLAN operation mode}
      */
     public @IwlanOperationMode int getIwlanOperationMode() {
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index b47b4f7..7314388 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -18,9 +18,11 @@
 
 import static com.android.internal.telephony.RILConstants.RADIO_NOT_AVAILABLE;
 import static com.android.internal.telephony.RILConstants.REQUEST_NOT_SUPPORTED;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PHONE_CAPABILITY;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLOT_STATUS;
 import static com.android.internal.telephony.RILConstants
         .RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_DATA_MODEM;
 
 import android.content.Context;
 import android.hardware.radio.V1_0.RadioResponseInfo;
@@ -298,8 +300,50 @@
                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
                 result.sendToTarget();
             }
+            return;
         }
-        // TODO: call radioConfigProxy.setPreferredDataModem when it's ready.
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_DATA_MODEM,
+                result, mDefaultWorkSource);
+
+        if (DBG) {
+            logd(rr.serialString() + "> " + requestToString(rr.mRequest));
+        }
+
+        try {
+            ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxy)
+                    .setPreferredDataModem(rr.mSerial, (byte) modemId);
+        } catch (RemoteException | RuntimeException e) {
+            resetProxyAndRequestList("setPreferredDataModem", e);
+        }
+    }
+
+    /**
+     * Wrapper function for IRadioConfig.getPhoneCapability().
+     */
+    public void getPhoneCapability(Message result) {
+        IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
+        if (radioConfigProxy == null || mRadioConfigVersion.less(RADIO_CONFIG_HAL_VERSION_1_1)) {
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_PHONE_CAPABILITY, result, mDefaultWorkSource);
+
+        if (DBG) {
+            logd(rr.serialString() + "> " + requestToString(rr.mRequest));
+        }
+
+        try {
+            ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxy)
+                    .getPhoneCapability(rr.mSerial);
+        } catch (RemoteException | RuntimeException e) {
+            resetProxyAndRequestList("getPhoneCapability", e);
+        }
     }
 
     /**
@@ -309,8 +353,9 @@
      * See PhoneSwitcher for more details.
      */
     public boolean isSetPreferredDataCommandSupported() {
-        // TODO: call radioConfigProxy.isSetPreferredDataCommandSupported when it's ready.
-        return false;
+        IRadioConfig radioConfigProxy = getRadioConfigProxy(null);
+        return radioConfigProxy != null && mRadioConfigVersion
+                .greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponse.java b/src/java/com/android/internal/telephony/RadioConfigResponse.java
index 3b333ae..d4b6778 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponse.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponse.java
@@ -18,13 +18,15 @@
 
 import android.hardware.radio.V1_0.RadioError;
 import android.hardware.radio.V1_0.RadioResponseInfo;
-import android.hardware.radio.config.V1_1.PhoneCapability;
 import android.hardware.radio.config.V1_2.IRadioConfigResponse;
+import android.telephony.ModemInfo;
+import android.telephony.PhoneCapability;
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This class is the implementation of IRadioConfigResponse interface.
@@ -111,16 +113,83 @@
         }
     }
 
+    private PhoneCapability convertHalPhoneCapability(
+            android.hardware.radio.config.V1_1.PhoneCapability phoneCapability) {
+        // TODO b/121394331: clean up V1_1.PhoneCapability fields.
+        int maxActiveVoiceCalls = 0;
+        int maxActiveData = phoneCapability.maxActiveData;
+        int max5G = 0;
+        List<ModemInfo> logicalModemList = new ArrayList();
+
+        for (android.hardware.radio.config.V1_1.ModemInfo
+                modemInfo : phoneCapability.logicalModemList) {
+            logicalModemList.add(new ModemInfo(modemInfo.modemId));
+        }
+
+        return new PhoneCapability(maxActiveVoiceCalls, maxActiveData, max5G, logicalModemList);
+    }
     /**
      * Response function for IRadioConfig.getPhoneCapability().
      */
-    public void getPhoneCapabilityResponse(RadioResponseInfo info,
-            PhoneCapability phoneCapability) {
+    public void getPhoneCapabilityResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.config.V1_1.PhoneCapability phoneCapability) {
+        RILRequest rr = mRadioConfig.processResponse(responseInfo);
+
+        if (rr != null) {
+            PhoneCapability ret = convertHalPhoneCapability(phoneCapability);
+            if (responseInfo.error == RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                Rlog.d(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " " + ret.toString());
+            } else {
+                rr.onError(responseInfo.error, ret);
+                Rlog.e(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " error "
+                        + responseInfo.error);
+            }
+        } else {
+            Rlog.e(TAG, "getPhoneCapabilityResponse: Error " + responseInfo.toString());
+        }
     }
 
     /**
      * Response function for IRadioConfig.setPreferredDataModem().
      */
-    public void setPreferredDataModemResponse(RadioResponseInfo info) {
+    public void setPreferredDataModemResponse(RadioResponseInfo responseInfo) {
+        RILRequest rr = mRadioConfig.processResponse(responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, null);
+                Rlog.d(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest));
+            } else {
+                rr.onError(responseInfo.error, null);
+                Rlog.e(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " error "
+                        + responseInfo.error);
+            }
+        } else {
+            Rlog.e(TAG, "setPreferredDataModemResponse: Error " + responseInfo.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.setModemsConfigResponse()
+     *
+     */
+    public void setModemsConfigResponse(RadioResponseInfo info) {
+
+    }
+
+    /**
+     * Response function for IRadioConfig.getModemsConfigResponse()
+     *
+     */
+    public void getModemsConfigResponse(RadioResponseInfo info,
+            android.hardware.radio.config.V1_1.ModemsConfig modemsConfig) {
+
     }
 }
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index 4719bae..7ad830e 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -78,21 +78,23 @@
 import android.hardware.radio.V1_0.CfData;
 import android.hardware.radio.V1_0.LceDataInfo;
 import android.hardware.radio.V1_0.PcoDataInfo;
-import android.hardware.radio.V1_0.SetupDataCallResult;
 import android.hardware.radio.V1_0.SimRefreshResult;
 import android.hardware.radio.V1_0.SsInfoData;
 import android.hardware.radio.V1_0.StkCcUnsolSsResult;
 import android.hardware.radio.V1_0.SuppSvcNotification;
 import android.hardware.radio.V1_2.CellConnectionStatus;
 import android.hardware.radio.V1_2.IRadioIndication;
+import android.hardware.radio.V1_4.RadioFrequencyInfo.hidl_discriminator;
 import android.os.AsyncResult;
 import android.os.SystemProperties;
 import android.telephony.CellInfo;
 import android.telephony.PcoData;
 import android.telephony.PhysicalChannelConfig;
+import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
+import android.telephony.data.DataCallResponse;
 import android.telephony.emergency.EmergencyNumber;
 
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
@@ -275,37 +277,19 @@
     /**
      * Indicates current physical channel configuration.
      */
+    public void currentPhysicalChannelConfigs_1_4(int indicationType,
+            ArrayList<android.hardware.radio.V1_4.PhysicalChannelConfig> configs) {
+        mRil.processIndication(indicationType);
+        physicalChannelConfigsIndication(configs);
+    }
+
+    /**
+     * Indicates current physical channel configuration.
+     */
     public void currentPhysicalChannelConfigs(int indicationType,
             ArrayList<android.hardware.radio.V1_2.PhysicalChannelConfig> configs) {
-        List<PhysicalChannelConfig> response = new ArrayList<>(configs.size());
-
-        for (android.hardware.radio.V1_2.PhysicalChannelConfig config : configs) {
-            int status;
-            switch (config.status) {
-                case CellConnectionStatus.PRIMARY_SERVING:
-                    status = PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
-                    break;
-                case CellConnectionStatus.SECONDARY_SERVING:
-                    status = PhysicalChannelConfig.CONNECTION_SECONDARY_SERVING;
-                    break;
-                default:
-                    // only PRIMARY_SERVING and SECONDARY_SERVING are supported.
-                    mRil.riljLoge("Unsupported CellConnectionStatus in PhysicalChannelConfig: "
-                            + config.status);
-                    status = PhysicalChannelConfig.CONNECTION_UNKNOWN;
-                    break;
-            }
-
-            response.add(new PhysicalChannelConfig.Builder()
-                    .setCellConnectionStatus(status)
-                    .setCellBandwidthDownlinkKhz(config.cellBandwidthDownlink)
-                    .build());
-        }
-
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
-
-        mRil.mPhysicalChannelConfigurationRegistrants.notifyRegistrants(
-                new AsyncResult(null, response, null));
+        mRil.processIndication(indicationType);
+        physicalChannelConfigsIndication(configs);
     }
 
     /**
@@ -330,13 +314,28 @@
                 new AsyncResult(null, response, null));
     }
 
-    public void dataCallListChanged(int indicationType, ArrayList<SetupDataCallResult> dcList) {
+    /** Indicates current data call list. */
+    public void dataCallListChanged(int indicationType,
+            ArrayList<android.hardware.radio.V1_0.SetupDataCallResult> dcList) {
         mRil.processIndication(indicationType);
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
 
+        ArrayList<DataCallResponse> response = RIL.convertDataCallResultList(dcList);
         mRil.mDataCallListChangedRegistrants.notifyRegistrants(
-                new AsyncResult(null, dcList, null));
+                new AsyncResult(null, response, null));
+    }
+
+    /** Indicates current data call list with radio HAL 1.4. */
+    public void dataCallListChanged_1_4(int indicationType,
+            ArrayList<android.hardware.radio.V1_4.SetupDataCallResult> dcList) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
+
+        ArrayList<DataCallResponse> response = RIL.convertDataCallResultList(dcList);
+        mRil.mDataCallListChangedRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
     }
 
     public void suppSvcNotify(int indicationType, SuppSvcNotification suppSvcNotification) {
@@ -752,13 +751,19 @@
     /** Incremental network scan results */
     public void networkScanResult(int indicationType,
                                   android.hardware.radio.V1_1.NetworkScanResult result) {
-        responseCellInfos(indicationType, result);
+        responseNetworkScan(indicationType, result);
     }
 
     /** Incremental network scan results with HAL V1_2 */
     public void networkScanResult_1_2(int indicationType,
                                       android.hardware.radio.V1_2.NetworkScanResult result) {
-        responseCellInfos_1_2(indicationType, result);
+        responseNetworkScan_1_2(indicationType, result);
+    }
+
+    /** Incremental network scan results with HAL V1_4 */
+    public void networkScanResult_1_4(int indicationType,
+                                      android.hardware.radio.V1_4.NetworkScanResult result) {
+        responseNetworkScan_1_4(indicationType, result);
     }
 
     public void imsNetworkStateChanged(int indicationType) {
@@ -966,8 +971,79 @@
         return state;
     }
 
-    private void responseCellInfos(int indicationType,
-                                   android.hardware.radio.V1_1.NetworkScanResult result) {
+
+    /**
+     * Set the frequency range or channel number from the physical channel config. Only one of them
+     * is valid, we should set the other to the unknown value.
+     * @param builder the builder of {@link PhysicalChannelConfig}.
+     * @param config physical channel config from ril.
+     */
+    private void setFrequencyRangeOrChannelNumber(PhysicalChannelConfig.Builder builder,
+            android.hardware.radio.V1_4.PhysicalChannelConfig config) {
+
+        switch (config.rfInfo.getDiscriminator()) {
+            case hidl_discriminator.range:
+                builder.setFrequencyRange(config.rfInfo.range());
+                break;
+            case hidl_discriminator.channelNumber:
+                builder.setChannelNumber(config.rfInfo.channelNumber());
+                break;
+            default:
+                mRil.riljLoge("Unsupported frequency type " + config.rfInfo.getDiscriminator());
+        }
+    }
+
+    private int convertConnectionStatusFromCellConnectionStatus(int status) {
+        switch (status) {
+            case CellConnectionStatus.PRIMARY_SERVING:
+                return PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
+            case CellConnectionStatus.SECONDARY_SERVING:
+                return PhysicalChannelConfig.CONNECTION_SECONDARY_SERVING;
+            default:
+                // only PRIMARY_SERVING and SECONDARY_SERVING are supported.
+                mRil.riljLoge("Unsupported CellConnectionStatus in PhysicalChannelConfig: "
+                        + status);
+                return PhysicalChannelConfig.CONNECTION_UNKNOWN;
+        }
+    }
+
+    private void physicalChannelConfigsIndication(List<? extends Object> configs) {
+        List<PhysicalChannelConfig> response = new ArrayList<>(configs.size());
+        for (Object obj : configs) {
+            if (obj instanceof android.hardware.radio.V1_2.PhysicalChannelConfig) {
+                android.hardware.radio.V1_2.PhysicalChannelConfig config =
+                        (android.hardware.radio.V1_2.PhysicalChannelConfig) obj;
+
+                response.add(new PhysicalChannelConfig.Builder()
+                        .setCellConnectionStatus(
+                                convertConnectionStatusFromCellConnectionStatus(config.status))
+                        .setCellBandwidthDownlinkKhz(config.cellBandwidthDownlink)
+                        .build());
+            } else if (obj instanceof android.hardware.radio.V1_4.PhysicalChannelConfig) {
+                android.hardware.radio.V1_4.PhysicalChannelConfig config =
+                        (android.hardware.radio.V1_4.PhysicalChannelConfig) obj;
+                PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder();
+                setFrequencyRangeOrChannelNumber(builder, config);
+                response.add(builder.setCellConnectionStatus(
+                        convertConnectionStatusFromCellConnectionStatus(config.base.status))
+                        .setCellBandwidthDownlinkKhz(config.base.cellBandwidthDownlink)
+                        .setRat(ServiceState.rilRadioTechnologyToNetworkType(config.rat))
+                        .setPhysicalCellId(config.physicalCellId)
+                        .setContextIds(config.contextIds.stream().mapToInt(x -> x).toArray())
+                        .build());
+            } else {
+                mRil.riljLoge("Unsupported PhysicalChannelConfig " + obj);
+            }
+        }
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
+
+        mRil.mPhysicalChannelConfigurationRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    private void responseNetworkScan(int indicationType,
+                                     android.hardware.radio.V1_1.NetworkScanResult result) {
         mRil.processIndication(indicationType);
 
         NetworkScanResult nsr = null;
@@ -977,8 +1053,8 @@
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
-    private void responseCellInfos_1_2(int indicationType,
-                                       android.hardware.radio.V1_2.NetworkScanResult result) {
+    private void responseNetworkScan_1_2(int indicationType,
+                                         android.hardware.radio.V1_2.NetworkScanResult result) {
         mRil.processIndication(indicationType);
 
         NetworkScanResult nsr = null;
@@ -987,4 +1063,14 @@
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
+
+    private void responseNetworkScan_1_4(int indicationType,
+                                         android.hardware.radio.V1_4.NetworkScanResult result) {
+        mRil.processIndication(indicationType);
+
+        ArrayList<CellInfo> cellInfos = RIL.convertHalCellInfoList_1_4(result.networkInfos);
+        NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index fedb45f..2d58e4d 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -20,6 +20,7 @@
 import android.hardware.radio.V1_0.ActivityStatsInfo;
 import android.hardware.radio.V1_0.AppStatus;
 import android.hardware.radio.V1_0.CardStatus;
+import android.hardware.radio.V1_0.Carrier;
 import android.hardware.radio.V1_0.CarrierRestrictions;
 import android.hardware.radio.V1_0.CdmaBroadcastSmsConfigInfo;
 import android.hardware.radio.V1_0.DataRegStateResult;
@@ -31,13 +32,15 @@
 import android.hardware.radio.V1_0.RadioError;
 import android.hardware.radio.V1_0.RadioResponseInfo;
 import android.hardware.radio.V1_0.SendSmsResult;
-import android.hardware.radio.V1_0.SetupDataCallResult;
 import android.hardware.radio.V1_0.VoiceRegStateResult;
 import android.hardware.radio.V1_2.IRadioResponse;
+import android.hardware.radio.V1_4.CarrierRestrictionsWithPriority;
+import android.hardware.radio.V1_4.SimLockMultiSimPolicy;
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemClock;
 import android.service.carrier.CarrierIdentifier;
+import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
@@ -46,6 +49,7 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.DataCallResponse;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.dataconnection.KeepaliveStatus;
@@ -417,7 +421,17 @@
      *                            types.hal
      */
     public void setupDataCallResponse(RadioResponseInfo responseInfo,
-                                      SetupDataCallResult setupDataCallResult) {
+            android.hardware.radio.V1_0.SetupDataCallResult setupDataCallResult) {
+        responseSetupDataCall(responseInfo, setupDataCallResult);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param setupDataCallResult Response to data call setup as defined by setupDataCallResult in
+     *                            1.4/types.hal
+     */
+    public void setupDataCallResponse_1_4(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_4.SetupDataCallResult setupDataCallResult) {
         responseSetupDataCall(responseInfo, setupDataCallResult);
     }
 
@@ -593,6 +607,16 @@
     }
 
     /**
+     * The same method as startNetworkScanResponse, except disallowing error codes
+     * OPERATION_NOT_ALLOWED and REQUEST_NOT_SUPPORTED.
+     *
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void startNetworkScanResponse_1_4(RadioResponseInfo responseInfo) {
+        responseScanStatus(responseInfo);
+    }
+
+    /**
      *
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
@@ -661,7 +685,17 @@
      *                           types.hal
      */
     public void getDataCallListResponse(RadioResponseInfo responseInfo,
-                                        ArrayList<SetupDataCallResult> dataCallResultList) {
+            ArrayList<android.hardware.radio.V1_0.SetupDataCallResult> dataCallResultList) {
+        responseDataCallList(responseInfo, dataCallResultList);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param dataCallResultList Response to get data call list as defined by setupDataCallResult in
+     *                           1.4/types.hal
+     */
+    public void getDataCallListResponse_1_4(RadioResponseInfo responseInfo,
+            ArrayList<android.hardware.radio.V1_4.SetupDataCallResult> dataCallResultList) {
         responseDataCallList(responseInfo, dataCallResultList);
     }
 
@@ -1299,19 +1333,74 @@
      *        if Length of allowed carriers list is 0, numAllowed = 0.
      */
     public void setAllowedCarriersResponse(RadioResponseInfo responseInfo, int numAllowed) {
-        responseInts(responseInfo, numAllowed);
+        // The number of allowed carriers set correctly is not really useful. Even if one is
+        // missing, the operation has failed, as the list should be treated as a single
+        // configuration item. So, ignoring the value of numAllowed and considering only the
+        // value of the responseInfo.error.
+        int ret = TelephonyManager.SET_CARRIER_RESTRICTION_ERROR;
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            mRil.riljLog("setAllowedCarriersResponse - error = " + responseInfo.error);
+
+            if (responseInfo.error == RadioError.NONE) {
+                ret = TelephonyManager.SET_CARRIER_RESTRICTION_SUCCESS;
+                sendMessageResponse(rr.mResult, ret);
+            } else if (responseInfo.error == RadioError.REQUEST_NOT_SUPPORTED) {
+                // Handle the case REQUEST_NOT_SUPPORTED as a valid response
+                responseInfo.error = RadioError.NONE;
+                ret = TelephonyManager.SET_CARRIER_RESTRICTION_NOT_SUPPORTED;
+                sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
     }
 
     /**
      *
      * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setAllowedCarriersResponse_1_4(RadioResponseInfo responseInfo) {
+        int ret = TelephonyManager.SET_CARRIER_RESTRICTION_ERROR;
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            mRil.riljLog("setAllowedCarriersResponse_1_4 - error = " + responseInfo.error);
+
+            if (responseInfo.error == RadioError.NONE) {
+                ret = TelephonyManager.SET_CARRIER_RESTRICTION_SUCCESS;
+                sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
      * @param allAllowed true only when all carriers are allowed. Ignore "carriers" struct.
      *                   If false, consider "carriers" struct
      * @param carriers Carrier restriction information.
      */
     public void getAllowedCarriersResponse(RadioResponseInfo responseInfo, boolean allAllowed,
-                                           CarrierRestrictions carriers) {
-        responseCarrierIdentifiers(responseInfo, allAllowed, carriers);
+            CarrierRestrictions carriers) {
+        CarrierRestrictionsWithPriority carrierRestrictions = new CarrierRestrictionsWithPriority();
+        carrierRestrictions.allowedCarriers = carriers.allowedCarriers;
+        carrierRestrictions.excludedCarriers = carriers.excludedCarriers;
+        carrierRestrictions.allowedCarriersPrioritized = true;
+
+        responseCarrierRestrictions(responseInfo, allAllowed, carrierRestrictions,
+                SimLockMultiSimPolicy.NO_MULTISIM_POLICY);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param carrierRestrictions Carrier restriction information.
+     * @param multiSimPolicy Policy for multi-sim devices.
+     */
+    public void getAllowedCarriersResponse_1_4(RadioResponseInfo responseInfo,
+            CarrierRestrictionsWithPriority carrierRestrictions,
+            int multiSimPolicy) {
+        // The API in IRadio 1.4 does not support the flag allAllowed, so setting it to false, so
+        // that values in carrierRestrictions are used.
+        responseCarrierRestrictions(responseInfo, false, carrierRestrictions, multiSimPolicy);
     }
 
     /**
@@ -1812,14 +1901,15 @@
     }
 
     private void responseSetupDataCall(RadioResponseInfo responseInfo,
-                                       SetupDataCallResult setupDataCallResult) {
+                                       Object setupDataCallResult) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
+            DataCallResponse response = RIL.convertDataCallResult(setupDataCallResult);
             if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, setupDataCallResult);
+                sendMessageResponse(rr.mResult, response);
             }
-            mRil.processResponseDone(rr, responseInfo, setupDataCallResult);
+            mRil.processResponseDone(rr, responseInfo, response);
         }
     }
 
@@ -1906,14 +1996,16 @@
     }
 
     private void responseDataCallList(RadioResponseInfo responseInfo,
-                                      ArrayList<SetupDataCallResult> dataCallResultList) {
+                                      List<? extends Object> dataCallResultList) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
+            ArrayList<DataCallResponse> response =
+                    RIL.convertDataCallResultList(dataCallResultList);
             if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, dataCallResultList);
+                sendMessageResponse(rr.mResult, response);
             }
-            mRil.processResponseDone(rr, responseInfo, dataCallResultList);
+            mRil.processResponseDone(rr, responseInfo, response);
         }
     }
 
@@ -2151,34 +2243,70 @@
         }
     }
 
-    private void responseCarrierIdentifiers(RadioResponseInfo responseInfo, boolean allAllowed,
-                                            CarrierRestrictions carriers) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            List<CarrierIdentifier> ret = new ArrayList<CarrierIdentifier>();
-            for (int i = 0; i < carriers.allowedCarriers.size(); i++) {
-                String mcc = carriers.allowedCarriers.get(i).mcc;
-                String mnc = carriers.allowedCarriers.get(i).mnc;
-                String spn = null, imsi = null, gid1 = null, gid2 = null;
-                int matchType = carriers.allowedCarriers.get(i).matchType;
-                String matchData = carriers.allowedCarriers.get(i).matchData;
-                if (matchType == CarrierIdentifier.MatchType.SPN) {
-                    spn = matchData;
-                } else if (matchType == CarrierIdentifier.MatchType.IMSI_PREFIX) {
-                    imsi = matchData;
-                } else if (matchType == CarrierIdentifier.MatchType.GID1) {
-                    gid1 = matchData;
-                } else if (matchType == CarrierIdentifier.MatchType.GID2) {
-                    gid2 = matchData;
-                }
-                ret.add(new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2));
+    private static List<CarrierIdentifier> convertCarrierList(List<Carrier> carrierList) {
+        List<CarrierIdentifier> ret = new ArrayList<>();
+        for (int i = 0; i < carrierList.size(); i++) {
+            String mcc = carrierList.get(i).mcc;
+            String mnc = carrierList.get(i).mnc;
+            String spn = null, imsi = null, gid1 = null, gid2 = null;
+            int matchType = carrierList.get(i).matchType;
+            String matchData = carrierList.get(i).matchData;
+            if (matchType == CarrierIdentifier.MatchType.SPN) {
+                spn = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.IMSI_PREFIX) {
+                imsi = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.GID1) {
+                gid1 = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.GID2) {
+                gid2 = matchData;
             }
-            if (responseInfo.error == RadioError.NONE) {
-                /* TODO: Handle excluded carriers */
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
+            ret.add(new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2));
         }
+        return ret;
+    }
+
+    private void responseCarrierRestrictions(RadioResponseInfo responseInfo, boolean allAllowed,
+                                            CarrierRestrictionsWithPriority carriers,
+                                            int multiSimPolicy) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr == null) {
+            return;
+        }
+        CarrierRestrictionRules ret;
+
+        if (allAllowed) {
+            ret = CarrierRestrictionRules.newBuilder().setAllCarriersAllowed().build();
+        } else {
+            int policy = CarrierRestrictionRules.MULTISIM_POLICY_NONE;
+            if (multiSimPolicy == SimLockMultiSimPolicy.ONE_VALID_SIM_MUST_BE_PRESENT) {
+                policy = CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT;
+            }
+
+            int carrierRestrictionDefault =
+                    CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
+            if (!carriers.allowedCarriersPrioritized) {
+                carrierRestrictionDefault =
+                        CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED;
+            }
+
+            ret = CarrierRestrictionRules.newBuilder()
+                    .setAllowedCarriers(convertCarrierList(carriers.allowedCarriers))
+                    .setExcludedCarriers(convertCarrierList(carriers.excludedCarriers))
+                    .setDefaultCarrierRestriction(carrierRestrictionDefault)
+                    .setMultiSimPolicy(policy)
+                    .build();
+        }
+
+        if (responseInfo.error == RadioError.NONE) {
+            sendMessageResponse(rr.mResult, ret);
+        }
+        mRil.processResponseDone(rr, responseInfo, ret);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void enableModemResponse(RadioResponseInfo responseInfo) {
+        responseVoid(responseInfo);
     }
 }
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index abad3cf..46ffec6e 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -19,7 +19,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -165,9 +164,8 @@
 
     private AppOpsManager mAppOps;
 
-    // FIXME: Does not allow for multiple subs in a slot and change to SparseArray
-    private static Map<Integer, Integer> sSlotIndexToSubId =
-            new ConcurrentHashMap<Integer, Integer>();
+    // Each slot can have multiple subs.
+    private static Map<Integer, ArrayList<Integer>> sSlotIndexToSubIds = new ConcurrentHashMap<>();
     private static int mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
 
@@ -226,7 +224,25 @@
     }
 
     private boolean isSubInfoReady() {
-        return sSlotIndexToSubId.size() > 0;
+        if (VDBG) {
+            // make sure sSlotIndexToSubIds is consistent with cached subinfo list
+            int count = 0;
+            for (Integer i : sSlotIndexToSubIds.keySet()) {
+                count += sSlotIndexToSubIds.get(i).size();
+            }
+            if (count != mCacheActiveSubInfoList.size()) {
+                logdl("mismatch between map and list. list size = " + mCacheActiveSubInfoList.size()
+                        + ", map size = " + count);
+                for (Integer i : sSlotIndexToSubIds.keySet()) {
+                    logdl("From the Map, subs in map at slot index: " + i + " are: "
+                            + sSlotIndexToSubIds.get(i));
+                }
+                for (SubscriptionInfo info : mCacheActiveSubInfoList) {
+                    logdl("From the Cached list, subinfo is: " + info);
+                }
+            }
+        }
+        return sSlotIndexToSubIds.size() > 0;
     }
 
     private SubscriptionController(Phone phone) {
@@ -334,6 +350,8 @@
                 SubscriptionManager.IS_METERED)) == 1;
         int profileClass = cursor.getInt(cursor.getColumnIndexOrThrow(
                 SubscriptionManager.PROFILE_CLASS));
+        int subType = cursor.getInt(cursor.getColumnIndexOrThrow(
+                SubscriptionManager.SUBSCRIPTION_TYPE));
 
         if (VDBG) {
             String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
@@ -346,7 +364,8 @@
                     + isEmbedded + " accessRules:" + Arrays.toString(accessRules)
                     + " cardId:" + cardIdToPrint + " publicCardId:" + publicCardId
                     + " isOpportunistic:" + isOpportunistic + " groupUUID:" + groupUUID
-                    + " isMetered:" + isMetered + " profileClass:" + profileClass);
+                    + " isMetered:" + isMetered + " profileClass:" + profileClass
+                    + " subscriptionType: " + subType);
         }
 
         // If line1number has been set to a different number, use it instead.
@@ -357,7 +376,7 @@
         return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
             nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
             isEmbedded, accessRules, cardId, publicCardId, isOpportunistic, groupUUID,
-            isMetered, false /* isGroupDisabled */, carrierId, profileClass);
+            isMetered, false /* isGroupDisabled */, carrierId, profileClass, subType);
     }
 
     /**
@@ -367,7 +386,7 @@
      * @return Array list of queried result from database
      */
      private List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) {
-        if (VDBG) logd("selection:" + selection + " " + queryKey);
+        if (VDBG) logd("selection:" + selection + ", querykey: " + queryKey);
         String[] selectionArgs = null;
         if (queryKey != null) {
             selectionArgs = new String[] {queryKey.toString()};
@@ -523,10 +542,11 @@
     }
 
     /**
-     * Get the active SubscriptionInfo associated with the slotIndex
+     * Get the active SubscriptionInfo associated with the slotIndex.
+     * This API does not return details on Remote-SIM subscriptions.
      * @param slotIndex the slot which the subscription is inserted
      * @param callingPackage The package making the IPC.
-     * @return SubscriptionInfo, maybe null if its not active
+     * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex.
      */
     @Override
     public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex,
@@ -610,7 +630,8 @@
     }
 
     /**
-     * Get the SubInfoRecord(s) of the currently inserted SIM(s)
+     * Get the SubInfoRecord(s) of the currently active SIM(s) - which include both local
+     * and remote SIMs.
      * @param callingPackage The package making the IPC.
      * @return Array list of currently inserted SubInfoRecord(s)
      */
@@ -620,7 +641,8 @@
     }
 
     /**
-     * Refresh the cache of SubInfoRecord(s) of the currently inserted SIM(s)
+     * Refresh the cache of SubInfoRecord(s) of the currently available SIM(s) - including
+     * local & remote SIMs.
      */
     @VisibleForTesting  // For mockito to mock this method
     public void refreshCachedActiveSubscriptionInfoList() {
@@ -637,7 +659,10 @@
         synchronized (mSubInfoListLock) {
             mCacheActiveSubInfoList.clear();
             List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo(
-                    SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);
+                    SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
+                    + SubscriptionManager.SUBSCRIPTION_TYPE + "="
+                    + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM,
+                    null);
             if (activeSubscriptionInfoList != null) {
                 activeSubscriptionInfoList.sort(SUBSCRIPTION_INFO_COMPARATOR);
                 mCacheActiveSubInfoList.addAll(activeSubscriptionInfoList);
@@ -723,7 +748,7 @@
     }
 
     /**
-     * @return the maximum number of subscriptions this device will support at any one time.
+     * @return the maximum number of local subscriptions this device will support at any one time.
      */
     @Override
     public int getActiveSubInfoCountMax() {
@@ -754,7 +779,10 @@
 
             List<SubscriptionInfo> subList = getSubInfo(
                     SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
-                            + SubscriptionManager.IS_EMBEDDED + "=1", null);
+                            + SubscriptionManager.IS_EMBEDDED + "=1 OR "
+                            + SubscriptionManager.SUBSCRIPTION_TYPE + "="
+                            + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM,
+                    null);
 
             if (subList != null) {
                 subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
@@ -907,71 +935,114 @@
      */
     @Override
     public int addSubInfoRecord(String iccId, int slotIndex) {
-        if (DBG) logdl("[addSubInfoRecord]+ iccId:" + SubscriptionInfo.givePrintableIccid(iccId) +
-                " slotIndex:" + slotIndex);
+        return addSubInfo(iccId, null, slotIndex, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+    }
 
-        enforceModifyPhoneState("addSubInfoRecord");
+    /**
+     * Add a new subscription info record, if needed.
+     * @param uniqueId This is the unique identifier for the subscription within the specific
+     *                 subscription type.
+     * @param displayName human-readable name of the device the subscription corresponds to.
+     * @param slotIndex value for {@link SubscriptionManager#SIM_SLOT_INDEX}
+     * @param subscriptionType the type of subscription to be added.
+     * @return 0 if success, < 0 on error.
+     */
+    @Override
+    public int addSubInfo(String uniqueId, String displayName, int slotIndex,
+            int subscriptionType) {
+        if (DBG) {
+            String iccIdStr = uniqueId;
+            if (!isSubscriptionForRemoteSim(subscriptionType)) {
+                iccIdStr = SubscriptionInfo.givePrintableIccid(uniqueId);
+            }
+            logdl("[addSubInfoRecord]+ iccid: " + iccIdStr
+                    + ", slotIndex: " + slotIndex
+                    + ", subscriptionType: " + subscriptionType);
+        }
+
+        enforceModifyPhoneState("addSubInfo");
 
         // Now that all security checks passes, perform the operation as ourselves.
         final long identity = Binder.clearCallingIdentity();
         try {
-            if (iccId == null) {
-                if (DBG) logdl("[addSubInfoRecord]- null iccId");
+            if (uniqueId == null) {
+                if (DBG) logdl("[addSubInfo]- null iccId");
                 return -1;
             }
 
             ContentResolver resolver = mContext.getContentResolver();
+            String selection = SubscriptionManager.ICC_ID + "=?";
+            String[] args;
+            if (isSubscriptionForRemoteSim(subscriptionType)) {
+                selection += " AND " + SubscriptionManager.SUBSCRIPTION_TYPE + "=?";
+                args = new String[]{uniqueId, Integer.toString(subscriptionType)};
+            } else {
+                selection += " OR " + SubscriptionManager.ICC_ID + "=?";
+                args = new String[]{uniqueId, IccUtils.getDecimalSubstring(uniqueId)};
+            }
             Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
                     new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
                             SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE,
                             SubscriptionManager.ICC_ID, SubscriptionManager.CARD_ID},
-                    SubscriptionManager.ICC_ID + "=?" + " OR " + SubscriptionManager.ICC_ID + "=?",
-                            new String[]{iccId, IccUtils.getDecimalSubstring(iccId)}, null);
+                    selection, args, null);
 
             boolean setDisplayName = false;
             try {
-                if (cursor == null || !cursor.moveToFirst()) {
-                    setDisplayName = true;
-                    Uri uri = insertEmptySubInfoRecord(iccId, slotIndex);
-                    if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
-                } else {
-                    int subId = cursor.getInt(0);
-                    int oldSimInfoId = cursor.getInt(1);
-                    int nameSource = cursor.getInt(2);
-                    String oldIccId = cursor.getString(3);
-                    String oldCardId = cursor.getString(4);
-                    ContentValues value = new ContentValues();
-
-                    if (slotIndex != oldSimInfoId) {
-                        value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
+                boolean recordsDoNotExist = (cursor == null || !cursor.moveToFirst());
+                if (isSubscriptionForRemoteSim(subscriptionType)) {
+                    if (recordsDoNotExist) {
+                        // create a Subscription record
+                        slotIndex = SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB;
+                        Uri uri = insertEmptySubInfoRecord(uniqueId, displayName,
+                                slotIndex, subscriptionType);
+                        if (DBG) logd("[addSubInfoRecord] New record created: " + uri);
+                    } else {
+                        if (DBG) logdl("[addSubInfoRecord] Record already exists");
                     }
-
-                    if (nameSource != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
+                } else {  // Handle Local SIM devices
+                    if (recordsDoNotExist) {
                         setDisplayName = true;
-                    }
+                        Uri uri = insertEmptySubInfoRecord(uniqueId, slotIndex);
+                        if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
+                    } else { // there are matching records in the database for the given ICC_ID
+                        int subId = cursor.getInt(0);
+                        int oldSimInfoId = cursor.getInt(1);
+                        int nameSource = cursor.getInt(2);
+                        String oldIccId = cursor.getString(3);
+                        String oldCardId = cursor.getString(4);
+                        ContentValues value = new ContentValues();
 
-                    if (oldIccId != null && oldIccId.length() < iccId.length()
-                            && (oldIccId.equals(IccUtils.getDecimalSubstring(iccId)))) {
-                        value.put(SubscriptionManager.ICC_ID, iccId);
-                    }
-
-                    UiccCard card = UiccController.getInstance().getUiccCardForPhone(slotIndex);
-                    if (card != null) {
-                        String cardId = card.getCardId();
-                        if (cardId != null && cardId != oldCardId) {
-                            value.put(SubscriptionManager.CARD_ID, cardId);
+                        if (slotIndex != oldSimInfoId) {
+                            value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
                         }
+
+                        if (nameSource != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
+                            setDisplayName = true;
+                        }
+
+                        if (oldIccId != null && oldIccId.length() < uniqueId.length()
+                                && (oldIccId.equals(IccUtils.getDecimalSubstring(uniqueId)))) {
+                            value.put(SubscriptionManager.ICC_ID, uniqueId);
+                        }
+
+                        UiccCard card = UiccController.getInstance().getUiccCardForPhone(slotIndex);
+                        if (card != null) {
+                            String cardId = card.getCardId();
+                            if (cardId != null && cardId != oldCardId) {
+                                value.put(SubscriptionManager.CARD_ID, cardId);
+                            }
+                        }
+
+                        if (value.size() > 0) {
+                            resolver.update(SubscriptionManager.getUriForSubscriptionId(subId),
+                                    value, null, null);
+
+                            // Refresh the Cache of Active Subscription Info List
+                            refreshCachedActiveSubscriptionInfoList();
+                        }
+
+                        if (DBG) logdl("[addSubInfoRecord] Record already exists");
                     }
-
-                    if (value.size() > 0) {
-                        resolver.update(SubscriptionManager.getUriForSubscriptionId(subId),
-                                value, null, null);
-
-                        // Refresh the Cache of Active Subscription Info List
-                        refreshCachedActiveSubscriptionInfoList();
-                    }
-
-                    if (DBG) logdl("[addSubInfoRecord] Record already exists");
                 }
             } finally {
                 if (cursor != null) {
@@ -979,57 +1050,69 @@
                 }
             }
 
+            selection = SubscriptionManager.SIM_SLOT_INDEX + "=?";
+            args = new String[] {String.valueOf(slotIndex)};
+            if (isSubscriptionForRemoteSim(subscriptionType)) {
+                selection = SubscriptionManager.ICC_ID + "=? AND "
+                        + SubscriptionManager.SUBSCRIPTION_TYPE + "=?";
+                args = new String[]{uniqueId, Integer.toString(subscriptionType)};
+            }
             cursor = resolver.query(SubscriptionManager.CONTENT_URI, null,
-                    SubscriptionManager.SIM_SLOT_INDEX + "=?",
-                    new String[] {String.valueOf(slotIndex)}, null);
+                    selection, args, null);
             try {
                 if (cursor != null && cursor.moveToFirst()) {
                     do {
                         int subId = cursor.getInt(cursor.getColumnIndexOrThrow(
                                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
-                        // If sSlotIndexToSubId already has the same subId for a slotIndex/phoneId,
+                        // If sSlotIndexToSubIds already has the same subId for a slotIndex/phoneId,
                         // do not add it.
-                        Integer currentSubId = sSlotIndexToSubId.get(slotIndex);
-                        if (currentSubId == null
-                                || currentSubId != subId
-                                || !SubscriptionManager.isValidSubscriptionId(currentSubId)) {
+                        if (addToSubIdList(slotIndex, subId, subscriptionType)) {
                             // TODO While two subs active, if user deactivats first
                             // one, need to update the default subId with second one.
 
                             // FIXME: Currently we assume phoneId == slotIndex which in the future
                             // may not be true, for instance with multiple subs per slot.
                             // But is true at the moment.
-                            sSlotIndexToSubId.put(slotIndex, subId);
                             int subIdCountMax = getActiveSubInfoCountMax();
                             int defaultSubId = getDefaultSubId();
                             if (DBG) {
                                 logdl("[addSubInfoRecord]"
-                                        + " sSlotIndexToSubId.size=" + sSlotIndexToSubId.size()
+                                        + " sSlotIndexToSubIds.size=" + sSlotIndexToSubIds.size()
                                         + " slotIndex=" + slotIndex + " subId=" + subId
-                                        + " defaultSubId=" + defaultSubId + " simCount=" + subIdCountMax);
+                                        + " defaultSubId=" + defaultSubId
+                                        + " simCount=" + subIdCountMax);
                             }
 
                             // Set the default sub if not set or if single sim device
-                            if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
-                                    || subIdCountMax == 1) {
-                                setDefaultFallbackSubId(subId);
-                            }
-                            // If single sim device, set this subscription as the default for everything
-                            if (subIdCountMax == 1) {
-                                if (DBG) {
-                                    logdl("[addSubInfoRecord] one sim set defaults to subId=" + subId);
+                            if (!isSubscriptionForRemoteSim(subscriptionType)) {
+                                if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
+                                        || subIdCountMax == 1) {
+                                    logdl("setting default fallback subid to " + subId);
+                                    setDefaultFallbackSubId(subId, subscriptionType);
                                 }
-                                setDefaultDataSubId(subId);
-                                setDefaultSmsSubId(subId);
-                                setDefaultVoiceSubId(subId);
+                                // If single sim device, set this subscription as the default for
+                                // everything
+                                if (subIdCountMax == 1) {
+                                    if (DBG) {
+                                        logdl("[addSubInfoRecord] one sim set defaults to subId="
+                                                + subId);
+                                    }
+                                    setDefaultDataSubId(subId);
+                                    setDefaultSmsSubId(subId);
+                                    setDefaultVoiceSubId(subId);
+                                }
+                            } else {
+                                updateDefaultSubIdsIfNeeded(subId, subscriptionType);
                             }
                         } else {
                             if (DBG) {
-                                logdl("[addSubInfoRecord] currentSubId != null"
-                                        + " && currentSubId is valid, IGNORE");
+                                logdl("[addSubInfoRecord] current SubId is already known, "
+                                        + "IGNORE");
                             }
                         }
-                        if (DBG) logdl("[addSubInfoRecord] hashmap(" + slotIndex + "," + subId + ")");
+                        if (DBG) {
+                            logdl("[addSubInfoRecord] hashmap(" + slotIndex + "," + subId + ")");
+                        }
                     } while (cursor.moveToNext());
                 }
             } finally {
@@ -1038,46 +1121,180 @@
                 }
             }
 
-            // Set Display name after sub id is set above so as to get valid simCarrierName
-            int subId = getSubIdUsingPhoneId(slotIndex);
-            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-                if (DBG) {
-                    logdl("[addSubInfoRecord]- getSubId failed invalid subId = " + subId);
-                }
-                return -1;
-            }
-            if (setDisplayName) {
-                String simCarrierName = mTelephonyManager.getSimOperatorName(subId);
-                String nameToSet;
-
-                if (!TextUtils.isEmpty(simCarrierName)) {
-                    nameToSet = simCarrierName;
-                } else {
-                    nameToSet = "CARD " + Integer.toString(slotIndex + 1);
-                }
-
-                ContentValues value = new ContentValues();
-                value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
-                resolver.update(SubscriptionManager.getUriForSubscriptionId(subId), value,
-                        null, null);
-
-                // Refresh the Cache of Active Subscription Info List
+            if (isSubscriptionForRemoteSim(subscriptionType)) {
                 refreshCachedActiveSubscriptionInfoList();
+                notifySubscriptionInfoChanged();
+            } else {  // Handle Local SIM devices
+                // Set Display name after sub id is set above so as to get valid simCarrierName
+                int subId = getSubIdUsingPhoneId(slotIndex);
+                if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                    if (DBG) {
+                        logdl("[addSubInfoRecord]- getSubId failed invalid subId = " + subId);
+                    }
+                    return -1;
+                }
+                if (setDisplayName) {
+                    String simCarrierName = mTelephonyManager.getSimOperatorName(subId);
+                    String nameToSet;
 
-                if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
+                    if (!TextUtils.isEmpty(simCarrierName)) {
+                        nameToSet = simCarrierName;
+                    } else {
+                        nameToSet = "CARD " + Integer.toString(slotIndex + 1);
+                    }
+
+                    ContentValues value = new ContentValues();
+                    value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
+                    resolver.update(SubscriptionManager.getUriForSubscriptionId(subId), value,
+                            null, null);
+
+                    // Refresh the Cache of Active Subscription Info List
+                    refreshCachedActiveSubscriptionInfoList();
+
+                    if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
+                }
+
+                // Once the records are loaded, notify DcTracker
+                sPhones[slotIndex].updateDataConnectionTracker();
+
+                if (DBG) logdl("[addSubInfoRecord]- info size=" + sSlotIndexToSubIds.size());
             }
 
-            // Once the records are loaded, notify DcTracker
-            sPhones[slotIndex].updateDataConnectionTracker();
-
-            if (DBG) logdl("[addSubInfoRecord]- info size=" + sSlotIndexToSubId.size());
-
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
         return 0;
     }
 
+    private void updateDefaultSubIdsIfNeeded(int newDefault, int subscriptionType) {
+        if (DBG) {
+            logdl("[updateDefaultSubIdsIfNeeded] newDefault=" + newDefault
+                    + ", subscriptionType=" + subscriptionType);
+        }
+        // Set the default ot new value only if the current default is invalid.
+        if (!isActiveSubscriptionId(getDefaultSubId())) {
+            // current default is not valid anylonger. set a new default
+            if (DBG) {
+                logdl("[updateDefaultSubIdsIfNeeded] set mDefaultFallbackSubId=" + newDefault);
+            }
+            setDefaultFallbackSubId(newDefault, subscriptionType);
+        }
+
+        int value = getDefaultSmsSubId();
+        if (!isActiveSubscriptionId(value)) {
+            // current default is not valid. set it to the given newDefault value
+            setDefaultSmsSubId(newDefault);
+        }
+        value = getDefaultDataSubId();
+        if (!isActiveSubscriptionId(value)) {
+            setDefaultDataSubId(newDefault);
+        }
+        value = getDefaultVoiceSubId();
+        if (!isActiveSubscriptionId(value)) {
+            setDefaultVoiceSubId(newDefault);
+        }
+    }
+
+    /**
+     * This method returns true if the given subId is among the list of currently active
+     * subscriptions.
+     */
+    private boolean isActiveSubscriptionId(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;
+        ArrayList<Integer> subIdList = getActiveSubIdArrayList();
+        if (subIdList.isEmpty()) return false;
+        return subIdList.contains(new Integer(subId));
+    }
+
+    /*
+     * Delete subscription info record for the given device.
+     * @param uniqueId This is the unique identifier for the subscription within the specific
+     *                 subscription type.
+     * @param subscriptionType the type of subscription to be removed
+     * @return 0 if success, < 0 on error.
+     */
+    @Override
+    public int removeSubInfo(String uniqueId, int subscriptionType) {
+        enforceModifyPhoneState("removeSubInfo");
+        if (DBG) {
+            logd("[removeSubInfo] uniqueId: " + uniqueId
+                    + ", subscriptionType: " + subscriptionType);
+        }
+
+        // validate the given info - does it exist in the active subscription list
+        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        int slotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+        for (SubscriptionInfo info : mCacheActiveSubInfoList) {
+            if ((info.getSubscriptionType() == subscriptionType)
+                    && info.getIccId().equalsIgnoreCase(uniqueId)) {
+                subId = info.getSubscriptionId();
+                slotIndex = info.getSimSlotIndex();
+                break;
+            }
+        }
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            if (DBG) {
+                logd("Invalid subscription details: subscriptionType = " + subscriptionType
+                        + ", uniqueId = " + uniqueId);
+            }
+            return -1;
+        }
+
+        if (DBG) logd("removing the subid : " + subId);
+
+        // Now that all security checks passes, perform the operation as ourselves.
+        int result = 0;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            ContentResolver resolver = mContext.getContentResolver();
+            result = resolver.delete(SubscriptionManager.CONTENT_URI,
+                    SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=? AND "
+                            + SubscriptionManager.SUBSCRIPTION_TYPE + "=?",
+                    new String[]{Integer.toString(subId), Integer.toString(subscriptionType)});
+            if (result != 1) {
+                if (DBG) {
+                    logd("found NO subscription to remove with subscriptionType = "
+                            + subscriptionType + ", uniqueId = " + uniqueId);
+                }
+                return -1;
+            }
+            refreshCachedActiveSubscriptionInfoList();
+
+            // update sSlotIndexToSubIds struct
+            ArrayList<Integer> subIdsList = sSlotIndexToSubIds.get(slotIndex);
+            if (subIdsList == null) {
+                loge("sSlotIndexToSubIds has no entry for slotIndex = " + slotIndex);
+            } else {
+                if (subIdsList.contains(subId)) {
+                    subIdsList.remove(new Integer(subId));
+                    if (subIdsList.isEmpty()) {
+                        sSlotIndexToSubIds.remove(slotIndex);
+                    }
+                } else {
+                    loge("sSlotIndexToSubIds has no subid: " + subId
+                            + ", in index: " + slotIndex);
+                }
+            }
+            // Since a subscription is removed, if this one is set as default for any setting,
+            // set some other subid as the default.
+            int newDefault = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+            SubscriptionInfo info = null;
+            final List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
+                    mContext.getOpPackageName());
+            if (!records.isEmpty()) {
+                // yes, we have more subscriptions. pick the first one.
+                // FIXME do we need a policy to figure out which one is to be next default
+                info = records.get(0);
+            }
+            updateDefaultSubIdsIfNeeded(info.getSubscriptionId(), info.getSubscriptionType());
+
+            notifySubscriptionInfoChanged();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return result;
+    }
+
     /**
      * Clear an subscriptionInfo to subinfo database if needed by updating slot index to invalid.
      * @param slotIndex the slot which the SIM is removed
@@ -1101,7 +1318,7 @@
         // Refresh the Cache of Active Subscription Info List
         refreshCachedActiveSubscriptionInfoList();
 
-        sSlotIndexToSubId.remove(slotIndex);
+        sSlotIndexToSubIds.remove(slotIndex);
 
         // update default subId
         clearDefaultsForInactiveSubIds();
@@ -1117,24 +1334,32 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public Uri insertEmptySubInfoRecord(String iccId, int slotIndex) {
+        return insertEmptySubInfoRecord(iccId, null, slotIndex,
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+    }
+
+    Uri insertEmptySubInfoRecord(String uniqueId, String displayName, int slotIndex,
+            int subscriptionType) {
         ContentResolver resolver = mContext.getContentResolver();
         ContentValues value = new ContentValues();
-        value.put(SubscriptionManager.ICC_ID, iccId);
+        value.put(SubscriptionManager.ICC_ID, uniqueId);
         int color = getUnusedColor(mContext.getOpPackageName());
         // default SIM color differs between slots
         value.put(SubscriptionManager.COLOR, color);
         value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
         value.put(SubscriptionManager.CARRIER_NAME, "");
-        UiccCard card = UiccController.getInstance().getUiccCardForPhone(slotIndex);
-        if (card != null) {
-            String cardId = card.getCardId();
-            if (cardId != null) {
-                value.put(SubscriptionManager.CARD_ID, cardId);
-            } else {
-                value.put(SubscriptionManager.CARD_ID, iccId);
-            }
+        value.put(SubscriptionManager.CARD_ID, uniqueId);
+        value.put(SubscriptionManager.SUBSCRIPTION_TYPE, subscriptionType);
+        if (isSubscriptionForRemoteSim(subscriptionType)) {
+            value.put(SubscriptionManager.DISPLAY_NAME, displayName);
         } else {
-            value.put(SubscriptionManager.CARD_ID, iccId);
+            UiccCard card = UiccController.getInstance().getUiccCardForPhone(slotIndex);
+            if (card != null) {
+                String cardId = card.getCardId();
+                if (cardId != null) {
+                    value.put(SubscriptionManager.CARD_ID, cardId);
+                }
+            }
         }
 
         Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);
@@ -1501,20 +1726,18 @@
             return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
         }
 
-        int size = sSlotIndexToSubId.size();
+        int size = sSlotIndexToSubIds.size();
 
-        if (size == 0)
-        {
+        if (size == 0) {
             if (DBG) logd("[getSlotIndex]- size == 0, return SIM_NOT_INSERTED instead");
             return SubscriptionManager.SIM_NOT_INSERTED;
         }
 
-        for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
+        for (Entry<Integer, ArrayList<Integer>> entry : sSlotIndexToSubIds.entrySet()) {
             int sim = entry.getKey();
-            int sub = entry.getValue();
+            ArrayList<Integer> subs = entry.getValue();
 
-            if (subId == sub)
-            {
+            if (subs != null && subs.contains(subId)) {
                 if (VDBG) logv("[getSlotIndex]- return = " + sim);
                 return sim;
             }
@@ -1544,36 +1767,27 @@
         }
 
         // Check that we have a valid slotIndex
+        // TODO b/123300875 This check should probably be removed once tests are fixed
         if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
             if (DBG) logd("[getSubId]- invalid slotIndex=" + slotIndex);
             return null;
         }
 
         // Check if we've got any SubscriptionInfo records using slotIndexToSubId as a surrogate.
-        int size = sSlotIndexToSubId.size();
+        int size = sSlotIndexToSubIds.size();
         if (size == 0) {
             if (VDBG) {
-                logd("[getSubId]- sSlotIndexToSubId.size == 0, return null slotIndex="
+                logd("[getSubId]- sSlotIndexToSubIds.size == 0, return null slotIndex="
                         + slotIndex);
             }
             return null;
         }
 
-        // Create an array of subIds that are in this slot?
-        ArrayList<Integer> subIds = new ArrayList<Integer>();
-        for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
-            int slot = entry.getKey();
-            int sub = entry.getValue();
-            if (slotIndex == slot) {
-                subIds.add(sub);
-            }
-        }
-
         // Convert ArrayList to array
-        int numSubIds = subIds.size();
-        if (numSubIds > 0) {
-            int[] subIdArr = new int[numSubIds];
-            for (int i = 0; i < numSubIds; i++) {
+        ArrayList<Integer> subIds = sSlotIndexToSubIds.get(slotIndex);
+        if (subIds != null && subIds.size() > 0) {
+            int[] subIdArr = new int[subIds.size()];
+            for (int i = 0; i < subIds.size(); i++) {
                 subIdArr[i] = subIds.get(i);
             }
             if (VDBG) logd("[getSubId]- subIdArr=" + subIdArr);
@@ -1602,7 +1816,7 @@
             return SubscriptionManager.INVALID_PHONE_INDEX;
         }
 
-        int size = sSlotIndexToSubId.size();
+        int size = sSlotIndexToSubIds.size();
         if (size == 0) {
             phoneId = mDefaultPhoneId;
             if (DBG) logdl("[getPhoneId]- no sims, returning default phoneId=" + phoneId);
@@ -1610,11 +1824,11 @@
         }
 
         // FIXME: Assumes phoneId == slotIndex
-        for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
+        for (Entry<Integer, ArrayList<Integer>> entry: sSlotIndexToSubIds.entrySet()) {
             int sim = entry.getKey();
-            int sub = entry.getValue();
+            ArrayList<Integer> subs = entry.getValue();
 
-            if (subId == sub) {
+            if (subs != null && subs.contains(subId)) {
                 if (VDBG) logdl("[getPhoneId]- found subId=" + subId + " phoneId=" + sim);
                 return sim;
             }
@@ -1638,14 +1852,14 @@
         // Now that all security checks passes, perform the operation as ourselves.
         final long identity = Binder.clearCallingIdentity();
         try {
-            int size = sSlotIndexToSubId.size();
+            int size = sSlotIndexToSubIds.size();
 
             if (size == 0) {
                 if (DBG) logdl("[clearSubInfo]- no simInfo size=" + size);
                 return 0;
             }
 
-            sSlotIndexToSubId.clear();
+            sSlotIndexToSubIds.clear();
             if (DBG) logdl("[clearSubInfo]- clear size=" + size);
             return size;
         } finally {
@@ -1855,11 +2069,18 @@
      * sub is set as default subId. If two or more  sub's are active
      * the first sub is set as default subscription
      */
-    private void setDefaultFallbackSubId(int subId) {
+    private void setDefaultFallbackSubId(int subId, int subscriptionType) {
         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
             throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID");
         }
-        if (DBG) logdl("[setDefaultFallbackSubId] subId=" + subId);
+        if (DBG) {
+            logdl("[setDefaultFallbackSubId] subId=" + subId + ", subscriptionType="
+                    + subscriptionType);
+        }
+        if (isSubscriptionForRemoteSim(subscriptionType)) {
+            mDefaultFallbackSubId = subId;
+            return;
+        }
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
             int phoneId = getPhoneId(subId);
             if (phoneId >= 0 && (phoneId < mTelephonyManager.getPhoneCount()
@@ -1869,17 +2090,7 @@
                 // Update MCC MNC device configuration information
                 String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
                 MccTable.updateMccMncConfiguration(mContext, defaultMccMnc);
-
-                // Broadcast an Intent for default sub change
-                Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
-                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
-                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
-                if (DBG) {
-                    logdl("[setDefaultFallbackSubId] broadcast default subId changed phoneId=" +
-                            phoneId + " subId=" + subId);
-                }
-                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                sendDefaultChangedBroadcast(phoneId, subId);
             } else {
                 if (DBG) {
                     logdl("[setDefaultFallbackSubId] not set invalid phoneId=" + phoneId
@@ -1889,6 +2100,19 @@
         }
     }
 
+    private void sendDefaultChangedBroadcast(int phoneId, int subId) {
+        // Broadcast an Intent for default sub change
+        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
+        if (DBG) {
+            logdl("[sendDefaultChangedBroadcast] broadcast default subId changed phoneId="
+                    + phoneId + " subId=" + subId);
+        }
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
     @Override
     public void clearDefaultsForInactiveSubIds() {
         enforceModifyPhoneState("clearDefaultsForInactiveSubIds");
@@ -2006,18 +2230,38 @@
         sPhones = phones;
     }
 
+    private synchronized ArrayList<Integer> getActiveSubIdArrayList() {
+        // Clone the sub id list so it can't change out from under us while iterating
+        List<Entry<Integer, ArrayList<Integer>>> simInfoList =
+                new ArrayList<>(sSlotIndexToSubIds.entrySet());
+
+        // Put the set of sub ids in slot index order
+        Collections.sort(simInfoList, (x, y) -> x.getKey().compareTo(y.getKey()));
+
+        // Collect the sub ids for each slot in turn
+        ArrayList<Integer> allSubs = new ArrayList<>();
+        for (Entry<Integer, ArrayList<Integer>> slot : simInfoList) {
+            allSubs.addAll(slot.getValue());
+        }
+        return allSubs;
+    }
+
     /**
      * @return the list of subId's that are active, is never null but the length maybe 0.
      */
     @Override
-    public @NonNull int[] getActiveSubIdList() {
-        int[] subIdArr = sSlotIndexToSubId.keySet().stream()
-                .sorted()
-                .mapToInt(slotId -> sSlotIndexToSubId.get(slotId))
-                .toArray();
+    public int[] getActiveSubIdList() {
+        ArrayList<Integer> allSubs = getActiveSubIdArrayList();
+        int[] subIdArr = new int[allSubs.size()];
+        int i = 0;
+        for (int sub : allSubs) {
+            subIdArr[i] = sub;
+            i++;
+        }
 
         if (VDBG) {
-            logdl("[getActiveSubIdList] subIdArr=" + Arrays.toString(subIdArr));
+            logdl("[getActiveSubIdList] allSubs=" + allSubs + " subIdArr.length="
+                    + subIdArr.length);
         }
         return subIdArr;
     }
@@ -2039,14 +2283,15 @@
     @Deprecated // This should be moved into isActiveSubId(int, String)
     public boolean isActiveSubId(int subId) {
         boolean retVal = SubscriptionManager.isValidSubscriptionId(subId)
-                && sSlotIndexToSubId.containsValue(subId);
+                && getActiveSubIdArrayList().contains(subId);
 
         if (VDBG) logdl("[isActiveSubId]- " + retVal);
         return retVal;
     }
 
     /**
-     * Get the SIM state for the slot index
+     * Get the SIM state for the slot index.
+     * For Remote-SIMs, this method returns {@link #IccCardConstants.State.UNKNOWN}
      * @return SIM state as the ordinal of {@See IccCardConstants.State}
      */
     @Override
@@ -2242,8 +2487,8 @@
                     .from(mContext).getDefaultSmsPhoneId());
             pw.flush();
 
-            for (Entry<Integer, Integer> entry : sSlotIndexToSubId.entrySet()) {
-                pw.println(" sSlotIndexToSubId[" + entry.getKey() + "]: subId=" + entry.getValue());
+            for (Entry<Integer, ArrayList<Integer>> entry : sSlotIndexToSubIds.entrySet()) {
+                pw.println(" sSlotIndexToSubId[" + entry.getKey() + "]: subIds=" + entry);
             }
             pw.flush();
             pw.println("++++++++++++++++++++++++++++++++");
@@ -2349,8 +2594,12 @@
 
         long token = Binder.clearCallingIdentity();
         try {
-            return setSubscriptionProperty(subId, SubscriptionManager.IS_OPPORTUNISTIC,
+            int ret = setSubscriptionProperty(subId, SubscriptionManager.IS_OPPORTUNISTIC,
                     String.valueOf(opportunistic ? 1 : 0));
+
+            if (ret != 0) notifySubscriptionInfoChanged();
+
+            return ret;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -2380,8 +2629,12 @@
 
         long token = Binder.clearCallingIdentity();
         try {
-            return setSubscriptionProperty(subId, SubscriptionManager.IS_METERED,
+            int ret = setSubscriptionProperty(subId, SubscriptionManager.IS_METERED,
                     String.valueOf(isMetered ? 1 : 0));
+
+            if (ret != 0) notifySubscriptionInfoChanged();
+
+            return ret;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -2721,6 +2974,53 @@
         }
     }
 
+    private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) {
+        ArrayList<Integer> subIdsList = sSlotIndexToSubIds.get(slotIndex);
+        if (subIdsList == null) {
+            subIdsList = new ArrayList<>();
+            sSlotIndexToSubIds.put(slotIndex, subIdsList);
+        }
+
+        // add the given subId unless it already exists
+        if (subIdsList.contains(subId)) {
+            logdl("slotIndex, subId combo already exists in the map. Not adding it again.");
+            return false;
+        }
+        if (isSubscriptionForRemoteSim(subscriptionType)) {
+            // For Remote SIM subscriptions, a slot can have multiple subscriptions.
+            subIdsList.add(subId);
+        } else {
+            // for all other types of subscriptions, a slot can have only one subscription at a time
+            subIdsList.clear();
+            subIdsList.add(subId);
+        }
+        if (DBG) logdl("slotIndex, subId combo is added to the map.");
+        return true;
+    }
+
+    private boolean isSubscriptionForRemoteSim(int subscriptionType) {
+        return subscriptionType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
+    }
+
+    /**
+     * This is only for testing
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public Map<Integer, ArrayList<Integer>> getSlotIndexToSubIdsMap() {
+        return sSlotIndexToSubIds;
+    }
+
+    /**
+     * This is only for testing
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void resetStaticMembers() {
+        mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
+    }
+
     private void notifyOpportunisticSubscriptionInfoChanged() {
         ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
                 "telephony.registry"));
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 98ac9f5..5fbbbd3 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -549,21 +549,28 @@
         List<SubscriptionInfo> subInfos = SubscriptionController.getInstance()
                 .getSubInfoUsingSlotIndexPrivileged(slotIndex, false);
         if (subInfos != null) {
+            boolean changed = false;
             for (int i = 0; i < subInfos.size(); i++) {
                 SubscriptionInfo temp = subInfos.get(i);
                 ContentValues value = new ContentValues(1);
 
                 String msisdn = TelephonyManager.getDefault().getLine1Number(
                         temp.getSubscriptionId());
-                if (msisdn != null) {
+
+                UiccSlot uiccSlot = UiccController.getInstance().getUiccSlotForPhone(slotIndex);
+                boolean isEuicc = (uiccSlot != null && uiccSlot.isEuicc());
+                if (isEuicc != temp.isEmbedded() || !TextUtils.equals(msisdn, temp.getNumber())) {
+                    value.put(SubscriptionManager.IS_EMBEDDED, isEuicc);
                     value.put(SubscriptionManager.NUMBER, msisdn);
                     mContext.getContentResolver().update(SubscriptionManager.getUriForSubscriptionId(
                             temp.getSubscriptionId()), value, null, null);
-
-                    // refresh Cached Active Subscription Info List
-                    SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
+                    changed = true;
                 }
             }
+            if (changed) {
+                // refresh Cached Active Subscription Info List
+                SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
+            }
         }
 
         // TODO investigate if we can update for each slot separately.
@@ -610,21 +617,25 @@
             return false;
         }
 
+        // If the returned result is not RESULT_OK or the profile list is null, don't update cache.
+        // Otherwise, update the cache.
         final EuiccProfileInfo[] embeddedProfiles;
-        if (result.getResult() == EuiccService.RESULT_OK) {
-            List<EuiccProfileInfo> list = result.getProfiles();
-            if (list == null || list.size() == 0) {
-                embeddedProfiles = new EuiccProfileInfo[0];
-            } else {
-                embeddedProfiles = list.toArray(new EuiccProfileInfo[list.size()]);
+        List<EuiccProfileInfo> list = result.getProfiles();
+        if (result.getResult() == EuiccService.RESULT_OK && list != null) {
+            embeddedProfiles = list.toArray(new EuiccProfileInfo[list.size()]);
+            if (DBG) {
+                logd("blockingGetEuiccProfileInfoList: got " + result.getProfiles().size()
+                        + " profiles");
             }
         } else {
-            logd("updatedEmbeddedSubscriptions: error " + result.getResult() + " listing profiles");
-            // If there's an error listing profiles, treat it equivalently to a successful
-            // listing which returned no profiles under the assumption that none are currently
-            // accessible.
-            embeddedProfiles = new EuiccProfileInfo[0];
+            if (DBG) {
+                logd("blockingGetEuiccProfileInfoList returns an error. "
+                        + "Result code=" + result.getResult()
+                        + ". Null profile list=" + (result.getProfiles() == null));
+            }
+            return false;
         }
+
         final boolean isRemovable = result.getIsRemovable();
 
         final String[] embeddedIccids = new String[embeddedProfiles.length];
diff --git a/src/java/com/android/internal/telephony/TransportManager.java b/src/java/com/android/internal/telephony/TransportManager.java
index a093b00..b161754 100644
--- a/src/java/com/android/internal/telephony/TransportManager.java
+++ b/src/java/com/android/internal/telephony/TransportManager.java
@@ -21,22 +21,27 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.data.ApnSetting.ApnType;
-import android.text.TextUtils;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.dataconnection.AccessNetworksManager.QualifiedNetworks;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 /**
  * This class represents the transport manager which manages available transports (i.e. WWAN or
@@ -46,7 +51,17 @@
 public class TransportManager extends Handler {
     private static final String TAG = TransportManager.class.getSimpleName();
 
-    private static final boolean DBG = true;
+    private static final Map<Integer, Integer> ACCESS_NETWORK_TRANSPORT_TYPE_MAP;
+
+    static {
+        ACCESS_NETWORK_TRANSPORT_TYPE_MAP = new HashMap<>();
+        ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.UNKNOWN, TransportType.WWAN);
+        ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.GERAN, TransportType.WWAN);
+        ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.UTRAN, TransportType.WWAN);
+        ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.EUTRAN, TransportType.WWAN);
+        ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.CDMA2000, TransportType.WWAN);
+        ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.IWLAN, TransportType.WLAN);
+    }
 
     private static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 1;
 
@@ -87,6 +102,12 @@
 
     private final AccessNetworksManager mAccessNetworksManager;
 
+    /**
+     * Available networks. The key is the APN type, and the value is the available network list in
+     * the preferred order.
+     */
+    private final Map<Integer, int[]> mCurrentAvailableNetworks;
+
     public TransportManager(Phone phone) {
         mPhone = phone;
         mAccessNetworksManager = new AccessNetworksManager(phone);
@@ -94,6 +115,8 @@
         mAccessNetworksManager.registerForQualifiedNetworksChanged(this,
                 EVENT_QUALIFIED_NETWORKS_CHANGED);
 
+        mCurrentAvailableNetworks = new ConcurrentHashMap<>();
+
         if (isInLegacyMode()) {
             // For legacy mode, WWAN is the only transport to handle all data connections, even
             // the IWLAN ones.
@@ -117,9 +140,11 @@
         }
     }
 
-    private synchronized void updateAvailableNetworks(List<QualifiedNetworks> networks) {
-        log("updateAvailableNetworks: " + networks);
-        //TODO: Update available networks and transports.
+    private synchronized void updateAvailableNetworks(List<QualifiedNetworks> networksList) {
+        log("updateAvailableNetworks: " + networksList);
+        for (QualifiedNetworks networks : networksList) {
+            mCurrentAvailableNetworks.put(networks.apnType, networks.qualifiedNetworks);
+        }
     }
 
     /**
@@ -150,14 +175,33 @@
     }
 
     /**
-     * Get the corresponding transport based on the APN type
+     * Get the transport based on the APN type
      *
      * @param apnType APN type
      * @return The transport type
      */
     public int getCurrentTransport(@ApnType int apnType) {
-        // TODO: Look up the transport from the transport type map
-        return TransportType.WWAN;
+        // In legacy mode, always route to cellular.
+        if (isInLegacyMode()) {
+            return TransportType.WWAN;
+        }
+
+        // If we can't find the available networks, always route to cellular.
+        if (!mCurrentAvailableNetworks.containsKey(apnType)) {
+            return TransportType.WWAN;
+        }
+
+        int[] availableNetworks = mCurrentAvailableNetworks.get(apnType);
+
+        // If the available networks list is empty, route to cellular.
+        if (ArrayUtils.isEmpty(availableNetworks)) {
+            return TransportType.WWAN;
+        }
+
+        // TODO: For now we choose the first network because it's the most preferred one. In the
+        // the future we should validate it to make sure this network does not violate carrier
+        // preference and user preference.
+        return ACCESS_NETWORK_TRANSPORT_TYPE_MAP.get(availableNetworks[0]);
     }
 
     /**
@@ -171,12 +215,11 @@
         IndentingPrintWriter pw = new IndentingPrintWriter(printwriter, "  ");
         pw.println("TransportManager:");
         pw.increaseIndent();
-        pw.print("mAvailableTransports=");
-        List<String> transportsStrings = new ArrayList<>();
-        for (int i = 0; i < mAvailableTransports.length; i++) {
-            transportsStrings.add(TransportType.toString(mAvailableTransports[i]));
-        }
-        pw.println("[" + TextUtils.join(",", transportsStrings) + "]");
+        pw.println("mAvailableTransports=[" + Arrays.stream(mAvailableTransports)
+                .mapToObj(type -> TransportType.toString(type))
+                .collect(Collectors.joining(",")) + "]");
+        pw.println("isInLegacy=" + isInLegacyMode());
+        pw.println("IWLAN operation mode=" + mPhone.mCi.getIwlanOperationMode());
         mAccessNetworksManager.dump(fd, pw, args);
         pw.decreaseIndent();
         pw.flush();
diff --git a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
index a38cc8b..6a2b763 100644
--- a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
@@ -47,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Access network manager manages the qualified/available networks for mobile data connection.
@@ -80,6 +81,7 @@
 
     private QualifiedNetworksServiceConnection mServiceConnection;
 
+    // Available networks. Key is the APN type.
     private final SparseArray<int[]> mAvailableNetworks = new SparseArray<>();
 
     private final RegistrantList mQualifiedNetworksChangedRegistrants = new RegistrantList();
@@ -119,7 +121,9 @@
             return "[QualifiedNetworks: apnType="
                     + ApnSetting.getApnTypeString(apnType)
                     + ", networks="
-                    + TextUtils.join(", ", accessNetworkStrings)
+                    + Arrays.stream(qualifiedNetworks)
+                    .mapToObj(type -> AccessNetworkType.toString(type))
+                    .collect(Collectors.joining(","))
                     + "]";
         }
     }
@@ -160,9 +164,11 @@
             IQualifiedNetworksServiceCallback.Stub {
         @Override
         public void onQualifiedNetworkTypesChanged(int apnTypes, int[] qualifiedNetworkTypes) {
-            log("onQualifiedNetworkTypesChanged. apnTypes = "
+            log("onQualifiedNetworkTypesChanged. apnTypes = ["
                     + ApnSetting.getApnTypesStringFromBitmask(apnTypes)
-                    + ", networks = " + Arrays.toString(qualifiedNetworkTypes));
+                    + "], networks = [" + Arrays.stream(qualifiedNetworkTypes)
+                    .mapToObj(i -> AccessNetworkType.toString(i)).collect(Collectors.joining(","))
+                    + "]");
             List<QualifiedNetworks> qualifiedNetworksList = new ArrayList<>();
             for (int supportedApnType : SUPPORTED_APN_TYPES) {
                 if ((apnTypes & supportedApnType) == supportedApnType) {
@@ -316,13 +322,10 @@
         pw.increaseIndent();
 
         for (int i = 0; i < mAvailableNetworks.size(); i++) {
-            pw.print("APN type "
-                    + ApnSetting.getApnTypeString(mAvailableNetworks.keyAt(i)) + ": ");
-            List<String> networksStrings = new ArrayList<>();
-            for (int network : mAvailableNetworks.valueAt(i)) {
-                networksStrings.add(AccessNetworkType.toString(network));
-            }
-            pw.println("[" + TextUtils.join(",", networksStrings) + "]");
+            pw.println("APN type " + ApnSetting.getApnTypeString(mAvailableNetworks.keyAt(i))
+                    + ": [" + Arrays.stream(mAvailableNetworks.valueAt(i))
+                    .mapToObj(type -> AccessNetworkType.toString(type))
+                    .collect(Collectors.joining(",")) + "]");
         }
         pw.decreaseIndent();
         pw.decreaseIndent();
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index 0b4da48..471c9a6 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -32,12 +32,13 @@
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RetryManager;
+import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType;
+import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -385,14 +386,7 @@
         return mDataEnabled.get();
     }
 
-    public void setDependencyMet(boolean met) {
-        if (DBG) {
-            log("set mDependencyMet as " + met + " current state is " + mDependencyMet.get());
-        }
-        mDependencyMet.set(met);
-    }
-
-    public boolean getDependencyMet() {
+    public boolean isDependencyMet() {
        return mDependencyMet.get();
     }
 
@@ -419,19 +413,21 @@
         }
     }
 
-    public void requestNetwork(NetworkRequest networkRequest, LocalLog log) {
+    public void requestNetwork(NetworkRequest networkRequest,
+                               @RequestNetworkType int type, LocalLog log) {
         synchronized (mRefCountLock) {
             if (mLocalLogs.contains(log) || mNetworkRequests.contains(networkRequest)) {
                 log.log("ApnContext.requestNetwork has duplicate add - " + mNetworkRequests.size());
             } else {
                 mLocalLogs.add(log);
                 mNetworkRequests.add(networkRequest);
-                mDcTracker.setEnabled(ApnSetting.getApnTypesBitmaskFromString(mApnType), true);
+                mDcTracker.enableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type);
             }
         }
     }
 
-    public void releaseNetwork(NetworkRequest networkRequest, LocalLog log) {
+    public void releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type,
+                               LocalLog log) {
         synchronized (mRefCountLock) {
             if (mLocalLogs.contains(log) == false) {
                 log.log("ApnContext.releaseNetwork can't find this log");
@@ -445,19 +441,15 @@
                 mNetworkRequests.remove(networkRequest);
                 log.log("ApnContext.releaseNetwork left with " + mNetworkRequests.size() +
                         " requests.");
-                if (mNetworkRequests.size() == 0) {
-                    mDcTracker.setEnabled(ApnSetting.getApnTypesBitmaskFromString(mApnType), false);
+                if (mNetworkRequests.size() == 0
+                        || type == DcTracker.RELEASE_TYPE_DETACH
+                        || type == DcTracker.RELEASE_TYPE_HANDOVER) {
+                    mDcTracker.disableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type);
                 }
             }
         }
     }
 
-    public List<NetworkRequest> getNetworkRequests() {
-        synchronized (mRefCountLock) {
-            return new ArrayList<NetworkRequest>(mNetworkRequests);
-        }
-    }
-
     /**
      * @param excludeDun True if excluding requests that have DUN capability
      * @return True if the attached network requests contain restricted capability.
diff --git a/src/java/com/android/internal/telephony/dataconnection/CellularDataService.java b/src/java/com/android/internal/telephony/dataconnection/CellularDataService.java
index 555b6e9..ae69631 100644
--- a/src/java/com/android/internal/telephony/dataconnection/CellularDataService.java
+++ b/src/java/com/android/internal/telephony/dataconnection/CellularDataService.java
@@ -16,10 +16,7 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import android.hardware.radio.V1_0.SetupDataCallResult;
-import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.NetworkUtils;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -31,16 +28,10 @@
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
 import android.telephony.data.DataServiceCallback;
-import android.text.TextUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -89,11 +80,11 @@
                     AsyncResult ar = (AsyncResult) message.obj;
                     switch (message.what) {
                         case SETUP_DATA_CALL_COMPLETE:
-                            SetupDataCallResult result = (SetupDataCallResult) ar.result;
+                            DataCallResponse response = (DataCallResponse) ar.result;
                             callback.onSetupDataCallComplete(ar.exception != null
                                     ? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
                                     : DataServiceCallback.RESULT_SUCCESS,
-                                    convertDataCallResult(result));
+                                    response);
                             break;
                         case DEACTIVATE_DATA_ALL_COMPLETE:
                             callback.onDeactivateDataCallComplete(ar.exception != null
@@ -116,13 +107,11 @@
                                             ? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
                                             : DataServiceCallback.RESULT_SUCCESS,
                                     ar.exception != null
-                                            ? null
-                                            : getDataCallList((List<SetupDataCallResult>) ar.result)
+                                            ? null : (List<DataCallResponse>) ar.result
                                     );
                             break;
                         case DATA_CALL_LIST_CHANGED:
-                            notifyDataCallListChanged(getDataCallList(
-                                    (List<SetupDataCallResult>) ar.result));
+                            notifyDataCallListChanged((List<DataCallResponse>) ar.result);
                             break;
                         default:
                             loge("Unexpected event: " + message.what);
@@ -135,14 +124,6 @@
             mPhone.mCi.registerForDataCallListChanged(mHandler, DATA_CALL_LIST_CHANGED, null);
         }
 
-        private List<DataCallResponse> getDataCallList(List<SetupDataCallResult> dcList) {
-            List<DataCallResponse> dcResponseList = new ArrayList<>();
-            for (SetupDataCallResult dcResult : dcList) {
-                dcResponseList.add(convertDataCallResult(dcResult));
-            }
-            return dcResponseList;
-        }
-
         @Override
         public void setupDataCall(int radioTechnology, DataProfile dataProfile, boolean isRoaming,
                                   boolean allowRoaming, int reason, LinkProperties linkProperties,
@@ -239,99 +220,6 @@
         return new CellularDataServiceProvider(slotId);
     }
 
-    /**
-     * Convert SetupDataCallResult defined in types.hal into DataCallResponse
-     * @param dcResult setup data call result
-     * @return converted DataCallResponse object
-     */
-    @VisibleForTesting
-    public DataCallResponse convertDataCallResult(SetupDataCallResult dcResult) {
-        if (dcResult == null) return null;
-
-        // Process address
-        String[] addresses = null;
-        if (!TextUtils.isEmpty(dcResult.addresses)) {
-            addresses = dcResult.addresses.split("\\s+");
-        }
-
-        List<LinkAddress> laList = new ArrayList<>();
-        if (addresses != null) {
-            for (String address : addresses) {
-                address = address.trim();
-                if (address.isEmpty()) continue;
-
-                try {
-                    LinkAddress la;
-                    // Check if the address contains prefix length. If yes, LinkAddress
-                    // can parse that.
-                    if (address.split("/").length == 2) {
-                        la = new LinkAddress(address);
-                    } else {
-                        InetAddress ia = NetworkUtils.numericToInetAddress(address);
-                        la = new LinkAddress(ia, (ia instanceof Inet4Address) ? 32 : 128);
-                    }
-
-                    laList.add(la);
-                } catch (IllegalArgumentException e) {
-                    loge("Unknown address: " + address + ", exception = " + e);
-                }
-            }
-        }
-
-        // Process dns
-        String[] dnses = null;
-        if (!TextUtils.isEmpty(dcResult.dnses)) {
-            dnses = dcResult.dnses.split("\\s+");
-        }
-
-        List<InetAddress> dnsList = new ArrayList<>();
-        if (dnses != null) {
-            for (String dns : dnses) {
-                dns = dns.trim();
-                InetAddress ia;
-                try {
-                    ia = NetworkUtils.numericToInetAddress(dns);
-                    dnsList.add(ia);
-                } catch (IllegalArgumentException e) {
-                    loge("Unknown dns: " + dns + ", exception = " + e);
-                }
-            }
-        }
-
-        // Process gateway
-        String[] gateways = null;
-        if (!TextUtils.isEmpty(dcResult.gateways)) {
-            gateways = dcResult.gateways.split("\\s+");
-        }
-
-        List<InetAddress> gatewayList = new ArrayList<>();
-        if (gateways != null) {
-            for (String gateway : gateways) {
-                gateway = gateway.trim();
-                InetAddress ia;
-                try {
-                    ia = NetworkUtils.numericToInetAddress(gateway);
-                    gatewayList.add(ia);
-                } catch (IllegalArgumentException e) {
-                    loge("Unknown gateway: " + gateway + ", exception = " + e);
-                }
-            }
-        }
-
-        return new DataCallResponse(dcResult.status,
-                dcResult.suggestedRetryTime,
-                dcResult.cid,
-                dcResult.active,
-                dcResult.type,
-                dcResult.ifname,
-                laList,
-                dnsList,
-                gatewayList,
-                new ArrayList<>(Arrays.asList(dcResult.pcscf.trim().split("\\s+"))),
-                dcResult.mtu
-        );
-    }
-
     private void log(String s) {
         Rlog.d(TAG, s);
     }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 39b443a..7dfe6af 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -129,14 +129,16 @@
         int mRilRat;
         Message mOnCompletedMsg;
         final int mConnectionGeneration;
+        final boolean mIsHandover;
 
         ConnectionParams(ApnContext apnContext, int profileId, int rilRadioTechnology,
-                         Message onCompletedMsg, int connectionGeneration) {
+                         Message onCompletedMsg, int connectionGeneration, boolean isHandover) {
             mApnContext = apnContext;
             mProfileId = profileId;
             mRilRat = rilRadioTechnology;
             mOnCompletedMsg = onCompletedMsg;
             mConnectionGeneration = connectionGeneration;
+            mIsHandover = isHandover;
         }
 
         @Override
@@ -144,7 +146,9 @@
             return "{mTag=" + mTag + " mApnContext=" + mApnContext
                     + " mProfileId=" + mProfileId
                     + " mRat=" + mRilRat
-                    + " mOnCompletedMsg=" + msgToString(mOnCompletedMsg) + "}";
+                    + " mOnCompletedMsg=" + msgToString(mOnCompletedMsg)
+                    + " mIsHandover=" + mIsHandover
+                    + "}";
         }
     }
 
@@ -155,11 +159,14 @@
         int mTag;
         public ApnContext mApnContext;
         String mReason;
+        final boolean mIsHandover;
         Message mOnCompletedMsg;
 
-        DisconnectParams(ApnContext apnContext, String reason, Message onCompletedMsg) {
+        DisconnectParams(ApnContext apnContext, String reason, boolean isHandover,
+                         Message onCompletedMsg) {
             mApnContext = apnContext;
             mReason = reason;
+            mIsHandover = isHandover;
             mOnCompletedMsg = onCompletedMsg;
         }
 
@@ -167,6 +174,7 @@
         public String toString() {
             return "{mTag=" + mTag + " mApnContext=" + mApnContext
                     + " mReason=" + mReason
+                    + " mIsHandover=" + mIsHandover
                     + " mOnCompletedMsg=" + msgToString(mOnCompletedMsg) + "}";
         }
     }
@@ -286,9 +294,12 @@
                                                     DataServiceManager dataServiceManager,
                                                     DcTesterFailBringUpAll failBringUpAll,
                                                     DcController dcc) {
+        String transportType = (dataServiceManager.getTransportType() == TransportType.WWAN)
+                ? "C"   // Cellular
+                : "I";  // IWLAN
         DataConnection dc = new DataConnection(phone,
-                "DC-" + mInstanceNumber.incrementAndGet(), id, dct, dataServiceManager,
-                failBringUpAll, dcc);
+                "DC-" + transportType + "-" + mInstanceNumber.incrementAndGet(), id, dct,
+                dataServiceManager, failBringUpAll, dcc);
         dc.start();
         if (DBG) dc.log("Made " + dc.getName());
         return dc;
@@ -499,20 +510,34 @@
     }
 
     /**
+     * Get the DcTracker for handover. There are multiple DcTrackers for different transports (e.g.
+     * WWAN, WLAN). For data handover, we need to handover the existing data connection from current
+     * DcTracker to the DcTracker on another transport.
+     */
+    private DcTracker getHandoverDcTracker() {
+        int transportType = mDataServiceManager.getTransportType();
+        // Get the DcTracker from the other transport.
+        return mPhone.getDcTracker(transportType == TransportType.WWAN
+                ? TransportType.WLAN : TransportType.WWAN);
+    }
+
+    /**
      * Begin setting up a data connection, calls setupDataCall
      * and the ConnectionParams will be returned with the
      * EVENT_SETUP_DATA_CONNECTION_DONE
      *
      * @param cp is the connection parameters
+     *
+     * @return Fail cause if failed to setup data connection. {@link DataFailCause#NONE} if success.
      */
-    private void onConnect(ConnectionParams cp) {
+    private @DataFailCause.FailCause int connect(ConnectionParams cp) {
         if (DBG) {
-            log("onConnect: carrier='" + mApnSetting.getEntryName()
+            log("connect: carrier='" + mApnSetting.getEntryName()
                     + "' APN='" + mApnSetting.getApnName()
                     + "' proxy='" + mApnSetting.getProxyAddressAsString()
                     + "' port='" + mApnSetting.getProxyPort() + "'");
         }
-        if (cp.mApnContext != null) cp.mApnContext.requestLog("DataConnection.onConnect");
+        if (cp.mApnContext != null) cp.mApnContext.requestLog("DataConnection.connect");
 
         // Check if we should fake an error.
         if (mDcTesterFailBringUpAll.getDcFailBringUp().mCounter  > 0) {
@@ -525,11 +550,11 @@
             AsyncResult.forMessage(msg, response, null);
             sendMessage(msg);
             if (DBG) {
-                log("onConnect: FailBringUpAll=" + mDcTesterFailBringUpAll.getDcFailBringUp()
+                log("connect: FailBringUpAll=" + mDcTesterFailBringUpAll.getDcFailBringUp()
                         + " send error response=" + response);
             }
             mDcTesterFailBringUpAll.getDcFailBringUp().mCounter -= 1;
-            return;
+            return DataFailCause.NONE;
         }
 
         mCreateTime = -1;
@@ -553,11 +578,46 @@
         boolean allowRoaming = mPhone.getDataRoamingEnabled()
                 || (isModemRoaming && !mPhone.getServiceState().getDataRoaming());
 
+        // Check if this data setup is a handover.
+        LinkProperties linkProperties = null;
+        int reason = DataService.REQUEST_REASON_NORMAL;
+        if (cp.mIsHandover) {
+            // If this is a data setup for handover, we need to pass the link properties
+            // of the existing data connection to the modem.
+            DcTracker dcTracker = getHandoverDcTracker();
+            if (dcTracker == null || cp.mApnContext == null) {
+                loge("connect: Handover failed. dcTracker=" + dcTracker + ", apnContext="
+                        + cp.mApnContext);
+                return DataFailCause.HANDOVER_FAILED;
+            }
+
+            DataConnection dc = dcTracker.getDataConnectionByApnType(cp.mApnContext.getApnType());
+            if (dc == null) {
+                loge("connect: Can't find data connection for handover.");
+                return DataFailCause.HANDOVER_FAILED;
+            }
+
+            linkProperties = dc.getLinkProperties();
+            if (linkProperties == null) {
+                loge("connect: Can't find link properties of handover data connection. dc="
+                        + dc);
+                return DataFailCause.HANDOVER_FAILED;
+            }
+
+            reason = DataService.REQUEST_REASON_HANDOVER;
+        }
+
         mDataServiceManager.setupDataCall(
-                ServiceState.rilRadioTechnologyToAccessNetworkType(cp.mRilRat), dp, isModemRoaming,
-                allowRoaming, DataService.REQUEST_REASON_NORMAL, null, msg);
+                ServiceState.rilRadioTechnologyToAccessNetworkType(cp.mRilRat),
+                dp,
+                isModemRoaming,
+                allowRoaming,
+                reason,
+                linkProperties,
+                msg);
         TelephonyMetrics.getInstance().writeSetupDataCall(mPhone.getPhoneId(), cp.mRilRat,
                 dp.getProfileId(), dp.getApn(), dp.getProtocol());
+        return DataFailCause.NONE;
     }
 
     public void onSubscriptionOverride(int overrideMask, int overrideValue) {
@@ -581,6 +641,8 @@
             if (TextUtils.equals(dp.mReason, Phone.REASON_RADIO_TURNED_OFF)
                     || TextUtils.equals(dp.mReason, Phone.REASON_PDP_RESET)) {
                 discReason = DataService.REQUEST_REASON_SHUTDOWN;
+            } else if (dp.mIsHandover) {
+                discReason = DataService.REQUEST_REASON_HANDOVER;
             }
         }
 
@@ -599,7 +661,7 @@
             if (apnContext == alreadySent) continue;
             if (reason != null) apnContext.setReason(reason);
             Pair<ApnContext, Integer> pair = new Pair<>(apnContext, cp.mConnectionGeneration);
-            Message msg = mDct.obtainMessage(event, pair);
+            Message msg = mDct.obtainMessage(event, mCid, cp.mIsHandover ? 1 : 0, pair);
             AsyncResult.forMessage(msg);
             msg.sendToTarget();
         }
@@ -624,6 +686,7 @@
 
             long timeStamp = System.currentTimeMillis();
             connectionCompletedMsg.arg1 = mCid;
+            connectionCompletedMsg.arg2 = cp.mIsHandover ? 1 : 0;
 
             if (cause == DataFailCause.NONE) {
                 mCreateTime = timeStamp;
@@ -746,6 +809,7 @@
                                                    ConnectionParams cp) {
         SetupResult result;
 
+        log("onSetupConnectionCompleted: resultCode=" + resultCode + ", response=" + response);
         if (cp.mTag != mTag) {
             if (DBG) {
                 log("onSetupConnectionCompleted stale cp.tag=" + cp.mTag + ", mtag=" + mTag);
@@ -1512,8 +1576,6 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            boolean retVal;
-
             switch (msg.what) {
                 case EVENT_RESET:
                 case EVENT_REEVALUATE_RESTRICTED_STATE:
@@ -1521,45 +1583,43 @@
                         log("DcInactiveState: msg.what=" + getWhatToString(msg.what)
                                 + ", ignore we're already done");
                     }
-                    retVal = HANDLED;
-                    break;
-
+                    return HANDLED;
                 case EVENT_CONNECT:
                     if (DBG) log("DcInactiveState: mag.what=EVENT_CONNECT");
                     ConnectionParams cp = (ConnectionParams) msg.obj;
-                    if (initConnection(cp)) {
-                        onConnect(mConnectionParams);
-                        transitionTo(mActivatingState);
-                    } else {
-                        if (DBG) {
-                            log("DcInactiveState: msg.what=EVENT_CONNECT initConnection failed");
-                        }
+
+                    if (!initConnection(cp)) {
+                        log("DcInactiveState: msg.what=EVENT_CONNECT initConnection failed");
                         notifyConnectCompleted(cp, DataFailCause.UNACCEPTABLE_NETWORK_PARAMETER,
                                 false);
+                        transitionTo(mInactiveState);
+                        return HANDLED;
                     }
-                    retVal = HANDLED;
-                    break;
 
+                    int cause = connect(cp);
+                    if (cause != DataFailCause.NONE) {
+                        log("DcInactiveState: msg.what=EVENT_CONNECT connect failed");
+                        notifyConnectCompleted(cp, cause, false);
+                        transitionTo(mInactiveState);
+                        return HANDLED;
+                    }
+
+                    transitionTo(mActivatingState);
+                    return HANDLED;
                 case EVENT_DISCONNECT:
                     if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT");
                     notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
-                    retVal = HANDLED;
-                    break;
-
+                    return HANDLED;
                 case EVENT_DISCONNECT_ALL:
                     if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT_ALL");
                     notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
-                    retVal = HANDLED;
-                    break;
-
+                    return HANDLED;
                 default:
                     if (VDBG) {
-                        log("DcInactiveState nothandled msg.what=" + getWhatToString(msg.what));
+                        log("DcInactiveState not handled msg.what=" + getWhatToString(msg.what));
                     }
-                    retVal = NOT_HANDLED;
-                    break;
+                    return NOT_HANDLED;
             }
-            return retVal;
         }
     }
     private DcInactiveState mInactiveState = new DcInactiveState();
@@ -1728,9 +1788,30 @@
                 log("mRestrictedNetworkOverride = " + mRestrictedNetworkOverride
                         + ", mUnmeteredUseOnly = " + mUnmeteredUseOnly);
             }
-            mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
-                    "DcNetworkAgent", mNetworkInfo, getNetworkCapabilities(), mLinkProperties,
-                    50, misc);
+
+            if (mConnectionParams != null && mConnectionParams.mIsHandover) {
+                // If this is a data setup for handover, we need to reuse the existing network agent
+                // instead of creating a new one. This should be transparent to connectivity
+                // service.
+                DcTracker dcTracker = getHandoverDcTracker();
+                DataConnection dc = dcTracker.getDataConnectionByApnType(
+                        mConnectionParams.mApnContext.getApnType());
+                if (dc != null) {
+                    mNetworkAgent = dc.getNetworkAgent();
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
+                        mNetworkAgent.sendLinkProperties(mLinkProperties);
+                    } else {
+                        loge("Failed to get network agent from original data connection " + dc);
+                    }
+                } else {
+                    loge("Cannot find the data connection for handover.");
+                }
+            } else {
+                mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
+                        "DcNetworkAgent", mNetworkInfo, getNetworkCapabilities(), mLinkProperties,
+                        50, misc);
+            }
             if (mDataServiceManager.getTransportType() == TransportType.WWAN) {
                 mPhone.mCi.registerForNattKeepaliveStatus(
                         getHandler(), DataConnection.EVENT_KEEPALIVE_STATUS, null);
@@ -1761,7 +1842,13 @@
                 mPhone.mCi.unregisterForLceInfo(getHandler());
             }
             if (mNetworkAgent != null) {
-                mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+                // We do not want to update the network info if this is a handover. For all other
+                // cases we need to update connectivity service with the latest network info.
+                //
+                // For handover, the network agent is transferred to the other data connection.
+                if (mDisconnectParams == null || !mDisconnectParams.mIsHandover) {
+                    mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+                }
                 mNetworkAgent = null;
             }
         }
@@ -2183,7 +2270,8 @@
                         new Pair<ApnContext, Integer>(apnContext, cp.mConnectionGeneration);
                 log("DcNetworkAgent: [unwanted]: disconnect apnContext=" + apnContext);
                 Message msg = mDct.obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, pair);
-                DisconnectParams dp = new DisconnectParams(apnContext, apnContext.getReason(), msg);
+                DisconnectParams dp = new DisconnectParams(apnContext, apnContext.getReason(),
+                        false, msg);
                 DataConnection.this.sendMessage(DataConnection.this.
                         obtainMessage(EVENT_DISCONNECT, dp));
             }
@@ -2200,15 +2288,10 @@
 
         @Override
         protected void networkStatus(int status, String redirectUrl) {
-            if(!TextUtils.isEmpty(redirectUrl)) {
-                log("validation status: " + status + " with redirection URL: " + redirectUrl);
-                /* its possible that we have multiple DataConnection with INTERNET_CAPABILITY
-                   all fail the validation with the same redirection url, send CMD back to DCTracker
-                   and let DcTracker to make the decision */
-                Message msg = mDct.obtainMessage(DctConstants.EVENT_REDIRECTION_DETECTED,
-                        redirectUrl);
-                msg.sendToTarget();
-            }
+            log("validation status: " + status + " with redirection URL: " + redirectUrl);
+            Message msg = mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
+                    status, 0, redirectUrl);
+            msg.sendToTarget();
         }
 
         @Override
@@ -2370,30 +2453,33 @@
      *                       AsyncResult.result = FailCause and AsyncResult.exception = Exception().
      * @param connectionGeneration used to track a single connection request so disconnects can get
      *                             ignored if obsolete.
+     * @param isHandover {@code true} if this request is for handover.
      */
     public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology,
-                        Message onCompletedMsg, int connectionGeneration) {
+                        Message onCompletedMsg, int connectionGeneration, boolean isHandover) {
         if (DBG) {
             log("bringUp: apnContext=" + apnContext + " onCompletedMsg=" + onCompletedMsg);
         }
         sendMessage(DataConnection.EVENT_CONNECT,
                 new ConnectionParams(apnContext, profileId, rilRadioTechnology, onCompletedMsg,
-                        connectionGeneration));
+                        connectionGeneration, isHandover));
     }
 
     /**
      * Tear down the connection through the apn on the network.
      *
+     * @param apnContext APN context
+     * @param reason reason to tear down
      * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
      *        With AsyncResult.userObj set to the original msg.obj.
      */
     public void tearDown(ApnContext apnContext, String reason, Message onCompletedMsg) {
         if (DBG) {
-            log("tearDown: apnContext=" + apnContext
-                    + " reason=" + reason + " onCompletedMsg=" + onCompletedMsg);
+            log("tearDown: apnContext=" + apnContext + " reason=" + reason + " onCompletedMsg="
+                    + onCompletedMsg);
         }
         sendMessage(DataConnection.EVENT_DISCONNECT,
-                new DisconnectParams(apnContext, reason, onCompletedMsg));
+                new DisconnectParams(apnContext, reason, false, onCompletedMsg));
     }
 
     // ******* "public" interface
@@ -2410,13 +2496,14 @@
      * Tear down the connection through the apn on the network.  Ignores reference count and
      * and always tears down.
      *
+     * @param isHandover {@code true} if this is for handover
      * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
      *        With AsyncResult.userObj set to the original msg.obj.
      */
-    public void tearDownAll(String reason, Message onCompletedMsg) {
+    public void tearDownAll(String reason, boolean isHandover, Message onCompletedMsg) {
         if (DBG) log("tearDownAll: reason=" + reason + " onCompletedMsg=" + onCompletedMsg);
         sendMessage(DataConnection.EVENT_DISCONNECT_ALL,
-                new DisconnectParams(null, reason, onCompletedMsg));
+                new DisconnectParams(null, reason, isHandover, onCompletedMsg));
     }
 
     /**
@@ -2484,6 +2571,10 @@
         return new ArrayList<>(mApnContexts.keySet());
     }
 
+    public DcNetworkAgent getNetworkAgent() {
+        return mNetworkAgent;
+    }
+
     /**
      * @return the string for msg.what as our info.
      */
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index d18d552..f1790a3 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.dataconnection;
 
 import android.content.Context;
+import android.hardware.radio.V1_4.DataConnActiveStatus;
 import android.net.INetworkPolicyListener;
 import android.net.LinkAddress;
 import android.net.LinkProperties.CompareResult;
@@ -65,17 +66,6 @@
     // @GuardedBy("mDcListAll")
     private final HashMap<Integer, DataConnection> mDcListActiveByCid = new HashMap<>();
 
-    /**
-     * Constants for the data connection activity:
-     * physical link down/up
-     *
-     * TODO: Move to RILConstants.java
-     */
-    static final int DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE = 0;
-    static final int DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT = 1;
-    static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
-    static final int DATA_CONNECTION_ACTIVE_UNKNOWN = Integer.MAX_VALUE;
-
     private DccDefaultState mDccDefaultState = new DccDefaultState();
 
     final TelephonyManager mTelephonyManager;
@@ -325,7 +315,7 @@
                         log("onDataStateChanged: Found ConnId=" + newState.getCallId()
                                 + " newState=" + newState.toString());
                     }
-                    if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
+                    if (newState.getActive() == DataConnActiveStatus.INACTIVE) {
                         if (mDct.isCleanupRequired.get()) {
                             apnsToCleanup.addAll(apnContexts);
                             mDct.isCleanupRequired.set(false);
@@ -413,10 +403,10 @@
                     }
                 }
 
-                if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_UP) {
+                if (newState.getActive() == DataConnActiveStatus.ACTIVE) {
                     isAnyDataCallActive = true;
                 }
-                if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT) {
+                if (newState.getActive() == DataConnActiveStatus.DORMANT) {
                     isAnyDataCallDormant = true;
                 }
             }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 6929ed4..daf2d7e 100755
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -16,12 +16,14 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 
 import static com.android.internal.telephony.RILConstants.DATA_PROFILE_DEFAULT;
 import static com.android.internal.telephony.RILConstants.DATA_PROFILE_INVALID;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.ProgressDialog;
@@ -38,6 +40,7 @@
 import android.database.Cursor;
 import android.net.ConnectivityManager;
 import android.net.LinkProperties;
+import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
 import android.net.NetworkRequest;
@@ -102,6 +105,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -122,6 +127,59 @@
     private static final boolean VDBG_STALL = false; // STOPSHIP if true
     private static final boolean RADIO_TESTS = false;
 
+    @IntDef(value = {
+            REQUEST_TYPE_NORMAL,
+            REQUEST_TYPE_HANDOVER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RequestNetworkType {}
+
+    /**
+     * Normal request for {@link #requestNetwork(NetworkRequest, int, LocalLog)}. 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
+     * 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).
+     */
+    public static final int REQUEST_TYPE_HANDOVER = 2;
+
+    @IntDef(value = {
+            RELEASE_TYPE_NORMAL,
+            RELEASE_TYPE_DETACH,
+            RELEASE_TYPE_HANDOVER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ReleaseNetworkType {}
+
+    /**
+     * For release network, this is just removing the network request from the {@link ApnContext}.
+     * Note this does not tear down the physical data connection. Normally the data connection is
+     * torn down by connectivity service directly calling {@link NetworkAgent#unwanted()}.
+     */
+    public static final int RELEASE_TYPE_NORMAL = 1;
+
+    /**
+     * Detach request for {@link #releaseNetwork(NetworkRequest, int, LocalLog)} 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.
+     */
+    public static final int RELEASE_TYPE_DETACH = 2;
+
+    /**
+     * Handover request for {@link #releaseNetwork(NetworkRequest, int, LocalLog)}. For release
+     * network, this performs a data connection softly clean up at the underlying layer (versus
+     * normal data release).
+     */
+    public static final int RELEASE_TYPE_HANDOVER = 3;
+
     private final String mLogTag;
 
     public AtomicBoolean isCleanupRequired = new AtomicBoolean(false);
@@ -136,7 +194,6 @@
     // All data enabling/disabling related settings
     private final DataEnabledSettings mDataEnabledSettings;
 
-
     /**
      * After detecting a potential connection problem, this is the max number
      * of subsequent polls before attempting recovery.
@@ -207,6 +264,9 @@
     /* The Url passed as object parameter in CMD_ENABLE_MOBILE_PROVISIONING */
     private String mProvisioningUrl = null;
 
+    /* Indicating data service is bound or not */
+    private boolean mDataServiceBound = false;
+
     /* Intent for the provisioning apn alarm */
     private static final String INTENT_PROVISIONING_APN_ALARM =
             "com.android.internal.telephony.provisioning_apn_alarm";
@@ -421,15 +481,7 @@
                 if (DBG) {
                     log("onDataReconnect: state is FAILED|IDLE, disassociate");
                 }
-                DataConnection dataConnection = apnContext.getDataConnection();
-                if (dataConnection != null) {
-                    if (DBG) {
-                        log("onDataReconnect: tearDown apnContext=" + apnContext);
-                    }
-                    dataConnection.tearDown(apnContext, "", null);
-                }
-                apnContext.setDataConnection(null);
-                apnContext.setState(DctConstants.State.IDLE);
+                apnContext.releaseDataConnection("");
             } else {
                 if (DBG) log("onDataReconnect: keep associated");
             }
@@ -487,7 +539,7 @@
     // Reference counter for enabling fail fast
     private static int sEnableFailFastRefCounter = 0;
     // True if data stall detection is enabled
-    private volatile boolean mDataStallDetectionEnabled = true;
+    private volatile boolean mDataStallNoRxEnabled = true;
 
     private volatile boolean mFailFast = false;
 
@@ -573,6 +625,8 @@
 
     private final int mTransportType;
 
+    private DataStallRecoveryHandler mDsRecoveryHandler;
+
     //***** Constructor
     public DcTracker(Phone phone, int transportType) {
         super();
@@ -597,6 +651,8 @@
         mAlarmManager =
                 (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
 
+        mDsRecoveryHandler = new DataStallRecoveryHandler();
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
@@ -795,21 +851,25 @@
         mPhone.notifyDataActivity();
     }
 
-    public void requestNetwork(NetworkRequest networkRequest, LocalLog log) {
+    public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type,
+                               LocalLog log) {
         final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
         final ApnContext apnContext = mApnContextsByType.get(apnType);
-        log.log("DcTracker.requestNetwork for " + networkRequest + " found " + apnContext);
-        if (apnContext != null) apnContext.requestNetwork(networkRequest, log);
+        log.log("DcTracker.requestNetwork for " + networkRequest + " found " + apnContext
+                + ", type=" + requestTypeToString(type));
+        if (apnContext != null) {
+            apnContext.requestNetwork(networkRequest, type, log);
+        }
     }
 
-    public void releaseNetwork(NetworkRequest networkRequest, LocalLog log,
-            boolean cleanUpConnection) {
+    public void releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type,
+                               LocalLog log) {
         final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
         final ApnContext apnContext = mApnContextsByType.get(apnType);
-        log.log("DcTracker.releaseNetwork for " + networkRequest + " found " + apnContext);
+        log.log("DcTracker.releaseNetwork for " + networkRequest + " found " + apnContext
+                + ", type=" + releaseTypeToString(type));
         if (apnContext != null) {
-            apnContext.releaseNetwork(networkRequest, log);
-            if (cleanUpConnection) cleanUpConnectionInternal(true, apnContext);
+            apnContext.releaseNetwork(networkRequest, type, log);
         }
     }
 
@@ -1319,7 +1379,7 @@
             if (apnContext.isConnectable()) {
                 log("isConnectable() call trySetupData");
                 apnContext.setReason(reason);
-                trySetupData(apnContext);
+                trySetupData(apnContext, false);
             }
         }
     }
@@ -1330,7 +1390,7 @@
         return result;
     }
 
-    private boolean trySetupData(ApnContext apnContext) {
+    private boolean trySetupData(ApnContext apnContext, boolean isHandover) {
 
         if (mPhone.getSimulatedRadioControl() != null) {
             // Assume data is connected on the simulator
@@ -1345,7 +1405,8 @@
         DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
         boolean isDataAllowed = isDataAllowed(apnContext, dataConnectionReasons);
         String logStr = "trySetupData for APN type " + apnContext.getApnType() + ", reason: "
-                + apnContext.getReason() + ". " + dataConnectionReasons.toString();
+                + apnContext.getReason() + ", isHandover=" + isHandover + ". "
+                + dataConnectionReasons.toString();
         if (DBG) log(logStr);
         apnContext.requestLog(logStr);
         if (isDataAllowed) {
@@ -1377,7 +1438,7 @@
                 }
             }
 
-            boolean retValue = setupData(apnContext, radioTech);
+            boolean retValue = setupData(apnContext, radioTech, isHandover);
             notifyOffApnsOfAvailability();
 
             if (DBG) log("trySetupData: X retValue=" + retValue);
@@ -1394,7 +1455,7 @@
             str.append("trySetupData failed. apnContext = [type=" + apnContext.getApnType()
                     + ", mState=" + apnContext.getState() + ", apnEnabled="
                     + apnContext.isEnabled() + ", mDependencyMet="
-                    + apnContext.getDependencyMet() + "] ");
+                    + apnContext.isDependencyMet() + "] ");
 
             if (!mDataEnabledSettings.isDataEnabled()) {
                 str.append("isDataEnabled() = false. " + mDataEnabledSettings);
@@ -1448,16 +1509,16 @@
      * eventually tearing down all data connections after all APN contexts are detached from the
      * data connections.
      *
-     * @param tearDown True if the underlying data connection should be disconnected when no
-     * APN context attached to the data connection. False if we only want to reset the data
-     * connection's state machine without requesting modem to tearing down the data connections.
+     * @param detach {@code true} if detaching APN context from the underlying data connection (when
+     * no other APN context is attached to the data connection, the data connection will be torn
+     * down.) {@code false} to only reset the data connection's state machine.
      *
      * @param reason reason for the clean up.
      * @return boolean - true if we did cleanup any connections, false if they
      *                   were already all disconnected.
      */
-    private boolean cleanUpAllConnectionsInternal(boolean tearDown, String reason) {
-        if (DBG) log("cleanUpAllConnections: tearDown=" + tearDown + " reason=" + reason);
+    private boolean cleanUpAllConnectionsInternal(boolean detach, String reason) {
+        if (DBG) log("cleanUpAllConnections: detach=" + detach + " reason=" + reason);
         boolean didDisconnect = false;
         boolean disableMeteredOnly = false;
 
@@ -1478,10 +1539,10 @@
                     if (apnContext.isDisconnected() == false) didDisconnect = true;
                     if (DBG) log("clean up metered ApnContext Type: " + apnContext.getApnType());
                     apnContext.setReason(reason);
-                    cleanUpConnectionInternal(tearDown, apnContext);
+                    cleanUpConnectionInternal(detach, false, apnContext);
                 }
             } else {
-                // Exclude the IMS APN from single DataConenction case.
+                // Exclude the IMS APN from single data connection case.
                 if (reason.equals(Phone.REASON_SINGLE_PDN_ARBITRATION)
                         && apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS)) {
                     continue;
@@ -1489,7 +1550,7 @@
                 // TODO - only do cleanup if not disconnected
                 if (apnContext.isDisconnected() == false) didDisconnect = true;
                 apnContext.setReason(reason);
-                cleanUpConnectionInternal(tearDown, apnContext);
+                cleanUpConnectionInternal(detach, false, apnContext);
             }
         }
 
@@ -1500,7 +1561,7 @@
         mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
         log("cleanUpConnectionInternal: mDisconnectPendingCount = " + mDisconnectPendingCount);
-        if (tearDown && mDisconnectPendingCount == 0) {
+        if (detach && mDisconnectPendingCount == 0) {
             notifyAllDataDisconnected();
         }
 
@@ -1522,38 +1583,33 @@
     }
 
     /**
-     * Detach the APN context from the associated data connection. This data connection might be
+     * Detach the APN context from the associated data connection. This data connection will be
      * torn down if no other APN context is attached to it.
      *
-     * @param tearDown True if tearing down data connection when no other APN context attached.
-     * False to only reset the data connection's state machine.
-     * @param apnContext The APN context to be detached
+     * @param detach {@code true} if detaching APN context from the underlying data connection (when
+     * no other APN context is attached to the data connection, the data connection will be torn
+     * down.) {@code false} to only reset the data connection's state machine.
+     * @param isHandover {@code true} if this is a handover request. When this is set to
+     * {@code true}, {@code detach} also needs to be set to {@code true}.
+     * @param apnContext The APN context to be detached.
      */
-    private void cleanUpConnectionInternal(boolean tearDown, ApnContext apnContext) {
+    private void cleanUpConnectionInternal(boolean detach, boolean isHandover,
+                                           ApnContext apnContext) {
         if (apnContext == null) {
             if (DBG) log("cleanUpConnectionInternal: apn context is null");
             return;
         }
 
         DataConnection dataConnection = apnContext.getDataConnection();
-        String str = "cleanUpConnectionInternal: tearDown=" + tearDown + " reason="
+        String str = "cleanUpConnectionInternal: detach=" + detach + " reason="
                 + apnContext.getReason();
         if (VDBG) log(str + " apnContext=" + apnContext);
         apnContext.requestLog(str);
-        if (tearDown) {
+        if (detach) {
             if (apnContext.isDisconnected()) {
-                // The request is tearDown and but ApnContext is not connected.
+                // The request is detach and but ApnContext is not connected.
                 // If apnContext is not enabled anymore, break the linkage to the data connection.
-                apnContext.setState(DctConstants.State.IDLE);
-                if (!apnContext.isReady()) {
-                    if (dataConnection != null) {
-                        str = "cleanUpConnectionInternal: teardown, disconnected, !ready";
-                        if (DBG) log(str + " apnContext=" + apnContext);
-                        apnContext.requestLog(str);
-                        dataConnection.tearDown(apnContext, "", null);
-                    }
-                    apnContext.setDataConnection(null);
-                }
+                apnContext.releaseDataConnection("");
             } else {
                 // Connection is still there. Try to clean up.
                 if (dataConnection != null) {
@@ -1581,8 +1637,8 @@
                         Pair<ApnContext, Integer> pair = new Pair<>(apnContext, generation);
                         Message msg = obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, pair);
 
-                        if (disconnectAll) {
-                            dataConnection.tearDownAll(apnContext.getReason(), msg);
+                        if (disconnectAll || isHandover) {
+                            dataConnection.tearDownAll(apnContext.getReason(), isHandover, msg);
                         } else {
                             dataConnection.tearDown(apnContext, apnContext.getReason(), msg);
                         }
@@ -1611,7 +1667,7 @@
         if (dataConnection != null) {
             cancelReconnectAlarm(apnContext);
         }
-        str = "cleanUpConnectionInternal: X tearDown=" + tearDown + " reason="
+        str = "cleanUpConnectionInternal: X detach=" + detach + " reason="
                 + apnContext.getReason();
         if (DBG) log(str + " apnContext=" + apnContext + " dc=" + apnContext.getDataConnection());
         apnContext.requestLog(str);
@@ -1701,6 +1757,19 @@
     }
 
     /**
+     * @return the {@link DataConnection} with the given APN context. Null if no data connection
+     * is found.
+     */
+    public @Nullable DataConnection getDataConnectionByApnType(String apnType) {
+        // TODO: Clean up all APN type in string usage
+        ApnContext apnContext = mApnContexts.get(apnType);
+        if (apnContext != null) {
+            return apnContext.getDataConnection();
+        }
+        return null;
+    }
+
+    /**
      * Determine if DUN connection is special and we need to teardown on start/stop
      */
     private boolean teardownForDun() {
@@ -1761,11 +1830,12 @@
      *
      * @param apnContext APN context
      * @param radioTech RAT of the data connection
+     * @param isHandover {@code true} if this is for data handover
      * @return True if successful, otherwise false.
      */
-    private boolean setupData(ApnContext apnContext, int radioTech) {
-        if (DBG) log("setupData: apnContext=" + apnContext);
-        apnContext.requestLog("setupData");
+    private boolean setupData(ApnContext apnContext, int radioTech, boolean isHandover) {
+        if (DBG) log("setupData: apnContext=" + apnContext + ", isHandover=" + isHandover);
+        apnContext.requestLog("setupData. handover=" + isHandover);
         ApnSetting apnSetting;
         DataConnection dataConnection = null;
 
@@ -1853,7 +1923,7 @@
         Message msg = obtainMessage();
         msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
         msg.obj = new Pair<ApnContext, Integer>(apnContext, generation);
-        dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation);
+        dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation, isHandover);
 
         if (DBG) log("setupData: initing!");
         return true;
@@ -2024,7 +2094,11 @@
          */
 
         int reset = Integer.parseInt(SystemProperties.get("net.ppp.reset-by-timeout", "0"));
-        SystemProperties.set("net.ppp.reset-by-timeout", String.valueOf(reset + 1));
+        try {
+            SystemProperties.set("net.ppp.reset-by-timeout", String.valueOf(reset + 1));
+        } catch (RuntimeException ex) {
+            log("Failed to set net.ppp.reset-by-timeout");
+        }
     }
 
     /**
@@ -2116,83 +2190,6 @@
         setDataProfilesAsNeeded();
     }
 
-    private void applyNewState(ApnContext apnContext, boolean enabled, boolean met) {
-        boolean cleanup = false;
-        boolean trySetup = false;
-        String str ="applyNewState(" + apnContext.getApnType() + ", " + enabled +
-                "(" + apnContext.isEnabled() + "), " + met + "(" +
-                apnContext.getDependencyMet() +"))";
-        if (DBG) log(str);
-        apnContext.requestLog(str);
-
-        if (apnContext.isReady()) {
-            cleanup = true;
-            if (enabled && met) {
-                DctConstants.State state = apnContext.getState();
-                switch(state) {
-                    case CONNECTING:
-                    case CONNECTED:
-                    case DISCONNECTING:
-                        // We're "READY" and active so just return
-                        if (DBG) log("applyNewState: 'ready' so return");
-                        apnContext.requestLog("applyNewState state=" + state + ", so return");
-                        return;
-                    case IDLE:
-                        // fall through: this is unexpected but if it happens cleanup and try setup
-                    case FAILED:
-                    case RETRYING:
-                        // We're "READY" but not active so disconnect (cleanup = true) and
-                        // connect (trySetup = true) to be sure we retry the connection.
-                        trySetup = true;
-                        apnContext.setReason(Phone.REASON_DATA_ENABLED);
-                        break;
-                }
-            } else if (met) {
-                apnContext.setReason(Phone.REASON_DATA_DISABLED_INTERNAL);
-                // If ConnectivityService has disabled this network, stop trying to bring
-                // it up, but do not tear it down - ConnectivityService will do that
-                // directly by talking with the DataConnection.
-                //
-                // This doesn't apply to DUN, however.  Those connections have special
-                // requirements from carriers and we need stop using them when the dun
-                // request goes away.  This applies to both CDMA and GSM because they both
-                // can declare the DUN APN sharable by default traffic, thus still satisfying
-                // those requests and not torn down organically.
-                if ((apnContext.getApnType() == PhoneConstants.APN_TYPE_DUN && teardownForDun())
-                        || apnContext.getState() != DctConstants.State.CONNECTED) {
-                    str = "Clean up the connection. Apn type = " + apnContext.getApnType()
-                            + ", state = " + apnContext.getState();
-                    if (DBG) log(str);
-                    apnContext.requestLog(str);
-                    cleanup = true;
-                } else {
-                    cleanup = false;
-                }
-            } else {
-                apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_UNMET);
-            }
-        } else {
-            if (enabled && met) {
-                if (apnContext.isEnabled()) {
-                    apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_MET);
-                } else {
-                    apnContext.setReason(Phone.REASON_DATA_ENABLED);
-                }
-                if (apnContext.getState() == DctConstants.State.FAILED) {
-                    apnContext.setState(DctConstants.State.IDLE);
-                }
-                trySetup = true;
-            }
-        }
-        apnContext.setEnabled(enabled);
-        apnContext.setDependencyMet(met);
-        if (cleanup) cleanUpConnectionInternal(true, apnContext);
-        if (trySetup) {
-            apnContext.resetErrorCodeRetries();
-            trySetupData(apnContext);
-        }
-    }
-
     private DataConnection checkForCompatibleConnectedApnContext(ApnContext apnContext) {
         int apnType = apnContext.getApnTypeBitmask();
         ArrayList<ApnSetting> dunSettings = null;
@@ -2268,28 +2265,124 @@
         return null;
     }
 
-    public void setEnabled(int apnType, boolean enable) {
-        Message msg = obtainMessage(DctConstants.EVENT_ENABLE_NEW_APN);
-        msg.arg1 = apnType;
-        msg.arg2 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
-        sendMessage(msg);
+    public void enableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType) {
+        sendMessage(obtainMessage(DctConstants.EVENT_ENABLE_APN, apnType, requestType));
     }
 
-    private void onEnableApn(int apnType, int enabled) {
+    private void onEnableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType) {
         ApnContext apnContext = mApnContextsByType.get(apnType);
         if (apnContext == null) {
-            loge("onEnableApn(" + apnType + ", " + enabled + "): NO ApnContext");
+            loge("onEnableApn(" + apnType + "): NO ApnContext");
             return;
         }
-        // TODO change our retry manager to use the appropriate numbers for the new APN
-        if (DBG) log("onEnableApn: apnContext=" + apnContext + " call applyNewState");
-        applyNewState(apnContext, enabled == DctConstants.ENABLED, apnContext.getDependencyMet());
 
-        if ((enabled == DctConstants.DISABLED) &&
-            isOnlySingleDcAllowed(mPhone.getServiceState().getRilDataRadioTechnology()) &&
-            !isHigherPriorityApnContextActive(apnContext)) {
+        boolean trySetup = false;
+        String str = "onEnableApn: apnType=" + ApnSetting.getApnTypeString(apnType)
+                + ", request type=" + requestType;
+        if (DBG) log(str);
+        apnContext.requestLog(str);
 
-            if(DBG) log("onEnableApn: isOnlySingleDcAllowed true & higher priority APN disabled");
+        if (!apnContext.isDependencyMet()) {
+            apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_UNMET);
+            apnContext.setEnabled(true);
+            str = "onEnableApn: dependency is not met.";
+            if (DBG) log(str);
+            apnContext.requestLog(str);
+            return;
+        }
+
+        if (apnContext.isReady()) {
+            DctConstants.State state = apnContext.getState();
+            switch(state) {
+                case CONNECTING:
+                case CONNECTED:
+                case DISCONNECTING:
+                    // We're "READY" and active so just return
+                    if (DBG) log("onEnableApn: 'ready' so return");
+                    apnContext.requestLog("onEnableApn state=" + state + ", so return");
+                    return;
+                case IDLE:
+                    // fall through: this is unexpected but if it happens cleanup and try setup
+                case FAILED:
+                case RETRYING:
+                    // We're "READY" but not active so disconnect (cleanup = true) and
+                    // connect (trySetup = true) to be sure we retry the connection.
+                    trySetup = true;
+                    apnContext.setReason(Phone.REASON_DATA_ENABLED);
+                    break;
+            }
+        } else {
+            if (apnContext.isEnabled()) {
+                apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_MET);
+            } else {
+                apnContext.setReason(Phone.REASON_DATA_ENABLED);
+            }
+            if (apnContext.getState() == DctConstants.State.FAILED) {
+                apnContext.setState(DctConstants.State.IDLE);
+            }
+            trySetup = true;
+        }
+        apnContext.setEnabled(true);
+        if (trySetup) {
+            apnContext.resetErrorCodeRetries();
+            trySetupData(apnContext, requestType == REQUEST_TYPE_HANDOVER);
+        }
+    }
+
+    public void disableApn(@ApnSetting.ApnType int apnType, @ReleaseNetworkType int releaseType) {
+        sendMessage(obtainMessage(DctConstants.EVENT_DISABLE_APN, apnType, releaseType));
+    }
+
+    private void onDisableApn(@ApnSetting.ApnType int apnType,
+                              @ReleaseNetworkType int releaseType) {
+        ApnContext apnContext = mApnContextsByType.get(apnType);
+        if (apnContext == null) {
+            loge("disableApn(" + apnType + "): NO ApnContext");
+            return;
+        }
+
+        boolean cleanup = false;
+        String str = "onDisableApn: apnType=" + ApnSetting.getApnTypeString(apnType)
+                + ", release type=" + releaseType;
+        if (DBG) log(str);
+        apnContext.requestLog(str);
+
+        if (apnContext.isReady()) {
+            cleanup = (releaseType == RELEASE_TYPE_DETACH
+                    || releaseType == RELEASE_TYPE_HANDOVER);
+            if (apnContext.isDependencyMet()) {
+                apnContext.setReason(Phone.REASON_DATA_DISABLED_INTERNAL);
+                // If ConnectivityService has disabled this network, stop trying to bring
+                // it up, but do not tear it down - ConnectivityService will do that
+                // directly by talking with the DataConnection.
+                //
+                // This doesn't apply to DUN, however.  Those connections have special
+                // requirements from carriers and we need stop using them when the dun
+                // request goes away.  This applies to both CDMA and GSM because they both
+                // can declare the DUN APN sharable by default traffic, thus still satisfying
+                // those requests and not torn down organically.
+                if ((PhoneConstants.APN_TYPE_DUN.equals(apnContext.getApnType())
+                        && teardownForDun())
+                        || apnContext.getState() != DctConstants.State.CONNECTED) {
+                    str = "Clean up the connection. Apn type = " + apnContext.getApnType()
+                            + ", state = " + apnContext.getState();
+                    if (DBG) log(str);
+                    apnContext.requestLog(str);
+                    cleanup = true;
+                }
+            } else {
+                apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_UNMET);
+            }
+        }
+
+        apnContext.setEnabled(false);
+        if (cleanup) {
+            cleanUpConnectionInternal(true, releaseType == RELEASE_TYPE_HANDOVER, apnContext);
+        }
+
+        if (isOnlySingleDcAllowed(mPhone.getServiceState().getRilDataRadioTechnology())
+                && !isHigherPriorityApnContextActive(apnContext)) {
+            if (DBG) log("disableApn:isOnlySingleDcAllowed true & higher priority APN disabled");
             // If the highest priority APN is disabled and only single
             // data call is allowed, try to setup data call on other connectable APN.
             setupDataOnConnectableApns(Phone.REASON_SINGLE_PDN_ARBITRATION, RetryFailures.ALWAYS);
@@ -2511,7 +2604,7 @@
         }
 
         if (getOverallState() != DctConstants.State.IDLE) {
-            cleanUpConnectionInternal(true, null);
+            cleanUpConnectionInternal(true, false, null);
         }
     }
 
@@ -2571,7 +2664,8 @@
      * A SETUP (aka bringUp) has completed, possibly with an error. If
      * there is an error this method will call {@link #onDataSetupCompleteError}.
      */
-    private void onDataSetupComplete(ApnContext apnContext, boolean success, int cause) {
+    private void onDataSetupComplete(ApnContext apnContext, boolean success, int cause,
+                                     boolean isHandover) {
         if (success) {
             DataConnection dataConnection = apnContext.getDataConnection();
 
@@ -2595,7 +2689,7 @@
             }
             if (dataConnection == null) {
                 log("onDataSetupComplete: no connection to DC, handle as error");
-                onDataSetupCompleteError(apnContext);
+                onDataSetupCompleteError(apnContext, isHandover);
             } else {
                 ApnSetting apn = apnContext.getApnSetting();
                 if (DBG) {
@@ -2708,8 +2802,8 @@
         } else {
             if (DBG) {
                 ApnSetting apn = apnContext.getApnSetting();
-                log(String.format("onDataSetupComplete: error apn=%s cause=%s",
-                        (apn == null ? "unknown" : apn.getApnName()), cause));
+                log("onDataSetupComplete: error apn=" + apn.getApnName() + ", cause=" + cause
+                        + ", isHandover=" + isHandover);
             }
             if (DataFailCause.isEventLoggable(cause)) {
                 // Log this failure to the Event Logs.
@@ -2741,7 +2835,7 @@
                 log("cause = " + cause + ", mark apn as permanent failed. apn = " + apn);
                 apnContext.markApnPermanentFailed(apn);
             }
-            onDataSetupCompleteError(apnContext);
+            onDataSetupCompleteError(apnContext, isHandover);
         }
     }
 
@@ -2751,15 +2845,17 @@
      * beginning if the list is empty. Between each SETUP request there will
      * be a delay defined by {@link #getApnDelay()}.
      */
-    private void onDataSetupCompleteError(ApnContext apnContext) {
+    private void onDataSetupCompleteError(ApnContext apnContext, boolean isHandover) {
         long delay = apnContext.getDelayForNextApn(mFailFast);
 
         // Check if we need to retry or not.
-        if (delay >= 0) {
+        // TODO: We should support handover retry in the future.
+        if (delay >= 0 && !isHandover) {
             if (DBG) log("onDataSetupCompleteError: Try next APN. delay = " + delay);
             apnContext.setState(DctConstants.State.RETRYING);
             // Wait a bit before trying the next APN, so that
             // we're not tying up the RIL command channel
+
             startAlarmForReconnect(delay, apnContext);
         } else {
             // If we are not going to retry any APN, set this APN context to failed state.
@@ -2767,19 +2863,36 @@
             apnContext.setState(DctConstants.State.FAILED);
             mPhone.notifyDataConnection(apnContext.getApnType());
             apnContext.setDataConnection(null);
-            log("onDataSetupCompleteError: Stop retrying APNs.");
+            log("onDataSetupCompleteError: Stop retrying APNs. delay=" + delay
+                    + ", isHandover=" + isHandover);
         }
     }
 
     /**
-     * Called when EVENT_REDIRECTION_DETECTED is received.
+     * Called when EVENT_NETWORK_STATUS_CHANGED is received.
+     *
+     * @param status One of {@code NetworkAgent.VALID_NETWORK} or
+     * {@code NetworkAgent.INVALID_NETWORK}.
+     * @param redirectUrl If the Internet probe was redirected, this
+     * is the destination it was redirected to, otherwise {@code null}
      */
-    private void onDataConnectionRedirected(String redirectUrl) {
+    private void onNetworkStatusChanged(int status, String redirectUrl) {
         if (!TextUtils.isEmpty(redirectUrl)) {
             Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
             intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, redirectUrl);
             mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
             log("Notify carrier signal receivers with redirectUrl: " + redirectUrl);
+        } else {
+            final boolean isValid = status == NetworkAgent.VALID_NETWORK;
+            if (!mDsRecoveryHandler.isRecoveryOnBadNetworkEnabled()) {
+                if (DBG) log("Skip data stall recovery on network status change with in threshold");
+                return;
+            }
+            if (mTransportType != TransportType.WWAN) {
+                if (DBG) log("Skip data stall recovery on non WWAN");
+                return;
+            }
+            mDsRecoveryHandler.processNetworkStatusChanged(isValid);
         }
     }
 
@@ -3282,6 +3395,7 @@
         Pair<ApnContext, Integer> pair;
         ApnContext apnContext;
         int generation;
+        boolean isHandover;
         switch (msg.what) {
             case DctConstants.EVENT_RECORDS_LOADED:
                 // If onRecordsLoadedOrSubIdChanged() is not called here, it should be called on
@@ -3303,7 +3417,7 @@
                 break;
 
             case DctConstants.EVENT_DO_RECOVERY:
-                doRecovery();
+                mDsRecoveryHandler.doRecovery();
                 break;
 
             case DctConstants.EVENT_APN_CHANGED:
@@ -3342,7 +3456,7 @@
                     apnContext = mApnContextsByType.get(ApnSetting.TYPE_DEFAULT);
                     if (apnContext != null) {
                         apnContext.setReason(Phone.REASON_PS_RESTRICT_ENABLED);
-                        trySetupData(apnContext);
+                        trySetupData(apnContext, false);
                     } else {
                         loge("**** Default ApnContext not found ****");
                         if (Build.IS_DEBUGGABLE) {
@@ -3353,16 +3467,12 @@
                 break;
 
             case DctConstants.EVENT_TRY_SETUP_DATA:
-                if (msg.obj instanceof ApnContext) {
-                    trySetupData((ApnContext) msg.obj);
-                } else {
-                    loge("EVENT_TRY_SETUP request w/o apnContext or String");
-                }
+                trySetupData((ApnContext) msg.obj, false);
                 break;
 
             case DctConstants.EVENT_CLEAN_UP_CONNECTION:
                 if (DBG) log("EVENT_CLEAN_UP_CONNECTION");
-                cleanUpConnectionInternal(true, (ApnContext) msg.obj);
+                cleanUpConnectionInternal(true, false, (ApnContext) msg.obj);
                 break;
             case DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS:
                 if ((msg.obj != null) && (msg.obj instanceof String == false)) {
@@ -3393,10 +3503,15 @@
                     mProvisioningSpinner = null;
                 }
                 break;
-            case DctConstants.EVENT_ENABLE_NEW_APN:
+
+            case DctConstants.EVENT_ENABLE_APN:
                 onEnableApn(msg.arg1, msg.arg2);
                 break;
 
+            case DctConstants.EVENT_DISABLE_APN:
+                onDisableApn(msg.arg1, msg.arg2);
+                break;
+
             case DctConstants.EVENT_DATA_STALL_ALARM:
                 onDataStallAlarm(msg.arg1);
                 break;
@@ -3409,10 +3524,22 @@
             case DctConstants.EVENT_ROAMING_SETTING_CHANGE:
                 onDataRoamingOnOrSettingsChanged(msg.what);
                 break;
-            case DctConstants.EVENT_REDIRECTION_DETECTED:
+
+            case DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE:
+                // Update sharedPreference to false when exits new device provisioning, indicating
+                // no users modifications on the settings for new devices. Thus carrier specific
+                // default roaming settings can be applied for new devices till user modification.
+                final SharedPreferences sp = PreferenceManager
+                        .getDefaultSharedPreferences(mPhone.getContext());
+                if (!sp.contains(Phone.DATA_ROAMING_IS_USER_SETTING_KEY)) {
+                    sp.edit().putBoolean(Phone.DATA_ROAMING_IS_USER_SETTING_KEY, false).commit();
+                }
+                break;
+
+            case DctConstants.EVENT_NETWORK_STATUS_CHANGED:
+                int status = msg.arg1;
                 String url = (String) msg.obj;
-                log("dataConnectionTracker.handleMessage: EVENT_REDIRECTION_DETECTED=" + url);
-                onDataConnectionRedirected(url);
+                onNetworkStatusChanged(status, url);
                 break;
 
             case DctConstants.EVENT_RADIO_AVAILABLE:
@@ -3428,6 +3555,7 @@
                 pair = (Pair<ApnContext, Integer>) ar.userObj;
                 apnContext = pair.first;
                 generation = pair.second;
+                isHandover = msg.arg2 != 0;
                 if (apnContext.getConnectionGeneration() == generation) {
                     boolean success = true;
                     int cause = DataFailCause.UNKNOWN;
@@ -3435,7 +3563,7 @@
                         success = false;
                         cause = (int) ar.result;
                     }
-                    onDataSetupComplete(apnContext, success, cause);
+                    onDataSetupComplete(apnContext, success, cause, isHandover);
                 } else {
                     loge("EVENT_DATA_SETUP_COMPLETE: Dropped the event because generation "
                             + "did not match.");
@@ -3447,8 +3575,9 @@
                 pair = (Pair<ApnContext, Integer>) ar.userObj;
                 apnContext = pair.first;
                 generation = pair.second;
+                isHandover = msg.arg1 != 0;
                 if (apnContext.getConnectionGeneration() == generation) {
-                    onDataSetupCompleteError(apnContext);
+                    onDataSetupCompleteError(apnContext, isHandover);
                 } else {
                     loge("EVENT_DATA_SETUP_COMPLETE_ERROR: Dropped the event because generation "
                             + "did not match.");
@@ -3496,8 +3625,8 @@
                 if (mFailFast != enabled) {
                     mFailFast = enabled;
 
-                    mDataStallDetectionEnabled = !enabled;
-                    if (mDataStallDetectionEnabled
+                    mDataStallNoRxEnabled = !enabled;
+                    if (mDsRecoveryHandler.isNoRxDataStallDetectionEnabled()
                             && (getOverallState() == DctConstants.State.CONNECTED)
                             && (!mInVoiceCall ||
                                     mPhone.getServiceStateTracker()
@@ -3543,7 +3672,7 @@
                         mIsProvisioning = false;
                         mProvisioningUrl = null;
                         stopProvisioningApnAlarm();
-                        cleanUpConnectionInternal(true, apnCtx);
+                        cleanUpConnectionInternal(true, false, apnCtx);
                     } else {
                         if (DBG) {
                             log("EVENT_PROVISIONING_APN_ALARM: ignore stale tag,"
@@ -3774,7 +3903,7 @@
         pw.println(" mNetStatPollEnabled=" + mNetStatPollEnabled);
         pw.println(" mDataStallTxRxSum=" + mDataStallTxRxSum);
         pw.println(" mDataStallAlarmTag=" + mDataStallAlarmTag);
-        pw.println(" mDataStallDetectionEnabled=" + mDataStallDetectionEnabled);
+        pw.println(" mDataStallNoRxEnabled=" + mDataStallNoRxEnabled);
         pw.println(" mSentSinceLastRecv=" + mSentSinceLastRecv);
         pw.println(" mNoRecvPollCount=" + mNoRecvPollCount);
         pw.println(" mResolver=" + mResolver);
@@ -3782,6 +3911,7 @@
         pw.println(" mAutoAttachOnCreation=" + mAutoAttachOnCreation.get());
         pw.println(" mIsScreenOn=" + mIsScreenOn);
         pw.println(" mUniqueIdGenerator=" + mUniqueIdGenerator);
+        pw.println(" mDataServiceBound=" + mDataServiceBound);
         pw.println(" mDataRoamingLeakageLog= ");
         mDataRoamingLeakageLog.dump(fd, pw, args);
         pw.println(" mApnSettingsInitializationLog= ");
@@ -3790,7 +3920,11 @@
         pw.println(" ***************************************");
         DcController dcc = mDcc;
         if (dcc != null) {
-            dcc.dump(fd, pw, args);
+            if (mDataServiceBound) {
+                dcc.dump(fd, pw, args);
+            } else {
+                pw.println(" Can't dump mDcc because data service is not bound.");
+            }
         } else {
             pw.println(" mDcc=null");
         }
@@ -3954,10 +4088,10 @@
         return true;
     }
 
-    private void cleanUpConnectionsOnUpdatedApns(boolean tearDown, String reason) {
-        if (DBG) log("cleanUpConnectionsOnUpdatedApns: tearDown=" + tearDown);
+    private void cleanUpConnectionsOnUpdatedApns(boolean detach, String reason) {
+        if (DBG) log("cleanUpConnectionsOnUpdatedApns: detach=" + detach);
         if (mAllApnSettings.isEmpty()) {
-            cleanUpAllConnectionsInternal(tearDown, Phone.REASON_APN_CHANGED);
+            cleanUpAllConnectionsInternal(detach, Phone.REASON_APN_CHANGED);
         } else {
             int radioTech = mPhone.getServiceState().getRilDataRadioTechnology();
             if (radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
@@ -3983,7 +4117,7 @@
                     if (!apnContext.isDisconnected()) {
                         if (VDBG) log("cleanUpConnectionsOnUpdatedApns for " + apnContext);
                         apnContext.setReason(reason);
-                        cleanUpConnectionInternal(true, apnContext);
+                        cleanUpConnectionInternal(true, false, apnContext);
                     }
                 }
             }
@@ -3997,7 +4131,7 @@
         mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
         if (DBG) log("mDisconnectPendingCount = " + mDisconnectPendingCount);
-        if (tearDown && mDisconnectPendingCount == 0) {
+        if (detach && mDisconnectPendingCount == 0) {
             notifyAllDataDisconnected();
         }
     }
@@ -4165,80 +4299,172 @@
     /**
      * Data-Stall
      */
+
     // Recovery action taken in case of data stall
-    private static class RecoveryAction {
-        public static final int GET_DATA_CALL_LIST      = 0;
-        public static final int CLEANUP                 = 1;
-        public static final int REREGISTER              = 2;
-        public static final int RADIO_RESTART           = 3;
+    @IntDef(
+        value = {
+            RECOVERY_ACTION_GET_DATA_CALL_LIST,
+            RECOVERY_ACTION_CLEANUP,
+            RECOVERY_ACTION_REREGISTER,
+            RECOVERY_ACTION_RADIO_RESTART
+        })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface RecoveryAction {};
+    private static final int RECOVERY_ACTION_GET_DATA_CALL_LIST      = 0;
+    private static final int RECOVERY_ACTION_CLEANUP                 = 1;
+    private static final int RECOVERY_ACTION_REREGISTER              = 2;
+    private static final int RECOVERY_ACTION_RADIO_RESTART           = 3;
 
-        private static boolean isAggressiveRecovery(int value) {
-            return ((value == RecoveryAction.CLEANUP) ||
-                    (value == RecoveryAction.REREGISTER) ||
-                    (value == RecoveryAction.RADIO_RESTART));
+    // Recovery handler class for cellular data stall
+    private class DataStallRecoveryHandler {
+        // Default minimum duration between each recovery steps
+        private static final int
+                DEFAULT_MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS = (3 * 60 * 1000); // 3 mins
+
+        // The elapsed real time of last recovery attempted
+        private long mTimeLastRecoveryStartMs;
+        // Whether current network good or not
+        private boolean mIsValidNetwork;
+
+        public DataStallRecoveryHandler() {
+            reset();
         }
-    }
 
-    private int getRecoveryAction() {
-        int action = Settings.System.getInt(mResolver,
-                "radio.data.stall.recovery.action", RecoveryAction.GET_DATA_CALL_LIST);
-        if (VDBG_STALL) log("getRecoveryAction: " + action);
-        return action;
-    }
+        public void reset() {
+            mTimeLastRecoveryStartMs = 0;
+            putRecoveryAction(RECOVERY_ACTION_GET_DATA_CALL_LIST);
+        }
 
-    private void putRecoveryAction(int action) {
-        Settings.System.putInt(mResolver, "radio.data.stall.recovery.action", action);
-        if (VDBG_STALL) log("putRecoveryAction: " + action);
-    }
+        public boolean isAggressiveRecovery() {
+            @RecoveryAction int action = getRecoveryAction();
 
-    private void broadcastDataStallDetected(int recoveryAction) {
-        Intent intent = new Intent(TelephonyManager.ACTION_DATA_STALL_DETECTED);
-        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-        intent.putExtra(TelephonyManager.EXTRA_RECOVERY_ACTION, recoveryAction);
-        mPhone.getContext().sendBroadcast(intent, READ_PHONE_STATE);
-    }
+            return ((action == RECOVERY_ACTION_CLEANUP)
+                    || (action == RECOVERY_ACTION_REREGISTER)
+                    || (action == RECOVERY_ACTION_RADIO_RESTART));
+        }
 
-    private void doRecovery() {
-        if (getOverallState() == DctConstants.State.CONNECTED) {
-            // Go through a series of recovery steps, each action transitions to the next action
-            final int recoveryAction = getRecoveryAction();
-            TelephonyMetrics.getInstance().writeDataStallEvent(mPhone.getPhoneId(), recoveryAction);
-            broadcastDataStallDetected(recoveryAction);
+        private long getMinDurationBetweenRecovery() {
+            return Settings.Global.getLong(mResolver,
+                Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS,
+                DEFAULT_MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS);
+        }
 
-            switch (recoveryAction) {
-                case RecoveryAction.GET_DATA_CALL_LIST:
-                    EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_GET_DATA_CALL_LIST,
-                            mSentSinceLastRecv);
-                    if (DBG) log("doRecovery() get data call list");
-                    mDataServiceManager.getDataCallList(obtainMessage());
-                    putRecoveryAction(RecoveryAction.CLEANUP);
-                    break;
-                case RecoveryAction.CLEANUP:
-                    EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP,
-                            mSentSinceLastRecv);
-                    if (DBG) log("doRecovery() cleanup all connections");
-                    cleanUpAllConnectionsInternal(true, Phone.REASON_PDP_RESET);
-                    putRecoveryAction(RecoveryAction.REREGISTER);
-                    break;
-                case RecoveryAction.REREGISTER:
-                    EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_REREGISTER,
-                            mSentSinceLastRecv);
-                    if (DBG) log("doRecovery() re-register");
-                    mPhone.getServiceStateTracker().reRegisterNetwork(null);
-                    putRecoveryAction(RecoveryAction.RADIO_RESTART);
-                    break;
-                case RecoveryAction.RADIO_RESTART:
-                    EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
-                            mSentSinceLastRecv);
-                    if (DBG) log("restarting radio");
-                    restartRadio();
-                    putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
-                    break;
-                default:
-                    throw new RuntimeException("doRecovery: Invalid recoveryAction="
-                            + recoveryAction);
+        private long getElapsedTimeSinceRecoveryMs() {
+            return (SystemClock.elapsedRealtime() - mTimeLastRecoveryStartMs);
+        }
+
+        @RecoveryAction
+        private int getRecoveryAction() {
+            @RecoveryAction int action = Settings.System.getInt(mResolver,
+                    "radio.data.stall.recovery.action", RECOVERY_ACTION_GET_DATA_CALL_LIST);
+            if (VDBG_STALL) log("getRecoveryAction: " + action);
+            return action;
+        }
+
+        private void putRecoveryAction(@RecoveryAction int action) {
+            Settings.System.putInt(mResolver, "radio.data.stall.recovery.action", action);
+            if (VDBG_STALL) log("putRecoveryAction: " + action);
+        }
+
+        private void broadcastDataStallDetected(@RecoveryAction int recoveryAction) {
+            Intent intent = new Intent(TelephonyManager.ACTION_DATA_STALL_DETECTED);
+            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
+            intent.putExtra(TelephonyManager.EXTRA_RECOVERY_ACTION, recoveryAction);
+            mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE);
+        }
+
+        private boolean isRecoveryAlreadyStarted() {
+            return getRecoveryAction() != RECOVERY_ACTION_GET_DATA_CALL_LIST;
+        }
+
+        private boolean checkRecovery() {
+            // To avoid back to back recovery wait for a grace period
+            if (getElapsedTimeSinceRecoveryMs() < getMinDurationBetweenRecovery()) {
+                if (VDBG_STALL) log("skip back to back data stall recovery");
+                return false;
             }
-            mSentSinceLastRecv = 0;
+            // Data is not allowed in current environment
+            if (!isDataAllowed(null, null)) {
+                log("skipped data stall recovery due to data is not allowd");
+                return false;
+            }
+            return true;
+        }
+
+        private void triggerRecovery() {
+            sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
+        }
+
+        public void doRecovery() {
+            if (getOverallState() == DctConstants.State.CONNECTED) {
+                // Go through a series of recovery steps, each action transitions to the next action
+                @RecoveryAction final int recoveryAction = getRecoveryAction();
+                TelephonyMetrics.getInstance().writeDataStallEvent(
+                        mPhone.getPhoneId(), recoveryAction);
+                broadcastDataStallDetected(recoveryAction);
+
+                switch (recoveryAction) {
+                    case RECOVERY_ACTION_GET_DATA_CALL_LIST:
+                        EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_GET_DATA_CALL_LIST,
+                            mSentSinceLastRecv);
+                        if (DBG) log("doRecovery() get data call list");
+                        mDataServiceManager.getDataCallList(obtainMessage());
+                        putRecoveryAction(RECOVERY_ACTION_CLEANUP);
+                        break;
+                    case RECOVERY_ACTION_CLEANUP:
+                        EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP,
+                            mSentSinceLastRecv);
+                        if (DBG) log("doRecovery() cleanup all connections");
+                        cleanUpAllConnections(Phone.REASON_PDP_RESET);
+                        putRecoveryAction(RECOVERY_ACTION_REREGISTER);
+                        break;
+                    case RECOVERY_ACTION_REREGISTER:
+                        EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_REREGISTER,
+                            mSentSinceLastRecv);
+                        if (DBG) log("doRecovery() re-register");
+                        mPhone.getServiceStateTracker().reRegisterNetwork(null);
+                        putRecoveryAction(RECOVERY_ACTION_RADIO_RESTART);
+                        break;
+                    case RECOVERY_ACTION_RADIO_RESTART:
+                        EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
+                            mSentSinceLastRecv);
+                        if (DBG) log("restarting radio");
+                        restartRadio();
+                        reset();
+                        break;
+                    default:
+                        throw new RuntimeException("doRecovery: Invalid recoveryAction="
+                            + recoveryAction);
+                }
+                mSentSinceLastRecv = 0;
+                mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
+            }
+        }
+
+        public void processNetworkStatusChanged(boolean isValid) {
+            if (isValid) {
+                mIsValidNetwork = true;
+                reset();
+            } else {
+                if (mIsValidNetwork || isRecoveryAlreadyStarted()) {
+                    mIsValidNetwork = false;
+                    // Check and trigger a recovery if network switched from good
+                    // to bad or recovery is already started before.
+                    if (checkRecovery()) {
+                        if (DBG) log("trigger data stall recovery");
+                        triggerRecovery();
+                    }
+                }
+            }
+        }
+
+        public boolean isRecoveryOnBadNetworkEnabled() {
+            return Settings.Global.getInt(mResolver,
+                    Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 1) == 1;
+        }
+
+        public boolean isNoRxDataStallDetectionEnabled() {
+            return mDataStallNoRxEnabled && !isRecoveryOnBadNetworkEnabled();
         }
     }
 
@@ -4265,7 +4491,7 @@
         if ( sent > 0 && received > 0 ) {
             if (VDBG_STALL) log("updateDataStallInfo: IN/OUT");
             mSentSinceLastRecv = 0;
-            putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
+            mDsRecoveryHandler.reset();
         } else if (sent > 0 && received == 0) {
             if (isPhoneStateIdle()) {
                 mSentSinceLastRecv += sent;
@@ -4279,7 +4505,7 @@
         } else if (sent == 0 && received > 0) {
             if (VDBG_STALL) log("updateDataStallInfo: IN");
             mSentSinceLastRecv = 0;
-            putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
+            mDsRecoveryHandler.reset();
         } else {
             if (VDBG_STALL) log("updateDataStallInfo: NONE");
         }
@@ -4314,7 +4540,8 @@
         boolean suspectedStall = DATA_STALL_NOT_SUSPECTED;
         if (mSentSinceLastRecv >= hangWatchdogTrigger) {
             if (DBG) {
-                log("onDataStallAlarm: tag=" + tag + " do recovery action=" + getRecoveryAction());
+                log("onDataStallAlarm: tag=" + tag + " do recovery action="
+                        + mDsRecoveryHandler.getRecoveryAction());
             }
             suspectedStall = DATA_STALL_SUSPECTED;
             sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
@@ -4328,13 +4555,13 @@
     }
 
     private void startDataStallAlarm(boolean suspectedStall) {
-        int nextAction = getRecoveryAction();
         int delayInMs;
 
-        if (mDataStallDetectionEnabled && getOverallState() == DctConstants.State.CONNECTED) {
+        if (mDsRecoveryHandler.isNoRxDataStallDetectionEnabled()
+                && getOverallState() == DctConstants.State.CONNECTED) {
             // If screen is on or data stall is currently suspected, set the alarm
             // with an aggressive timeout.
-            if (mIsScreenOn || suspectedStall || RecoveryAction.isAggressiveRecovery(nextAction)) {
+            if (mIsScreenOn || suspectedStall || mDsRecoveryHandler.isAggressiveRecovery()) {
                 delayInMs = Settings.Global.getInt(mResolver,
                         Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
                         DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
@@ -4380,9 +4607,7 @@
         if (isConnected() == false) return;
         // To be called on screen status change.
         // Do not cancel the alarm if it is set with aggressive timeout.
-        int nextAction = getRecoveryAction();
-
-        if (RecoveryAction.isAggressiveRecovery(nextAction)) {
+        if (mDsRecoveryHandler.isAggressiveRecovery()) {
             if (DBG) log("restartDataStallAlarm: action is pending. not resetting the alarm.");
             return;
         }
@@ -4475,5 +4700,23 @@
         } else {
             mDcc.dispose();
         }
+        mDataServiceBound = bound;
+    }
+
+    public static String requestTypeToString(@RequestNetworkType int type) {
+        switch (type) {
+            case REQUEST_TYPE_NORMAL: return "NORMAL";
+            case REQUEST_TYPE_HANDOVER: return "HANDOVER";
+        }
+        return "UNKNOWN";
+    }
+
+    public static String releaseTypeToString(@ReleaseNetworkType int type) {
+        switch (type) {
+            case RELEASE_TYPE_NORMAL: return "NORMAL";
+            case RELEASE_TYPE_DETACH: return "DETACH";
+            case RELEASE_TYPE_HANDOVER: return "HANDOVER";
+        }
+        return "UNKNOWN";
     }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
index a606ad6..da584f5 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
@@ -25,7 +25,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Rlog;
 import android.util.LocalLog;
 
@@ -38,6 +37,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
+import java.util.Map;
 
 public class TelephonyNetworkFactory extends NetworkFactory {
     public final String LOG_TAG;
@@ -52,13 +52,15 @@
     private final PhoneSwitcher mPhoneSwitcher;
     private final SubscriptionController mSubscriptionController;
     private final SubscriptionMonitor mSubscriptionMonitor;
-    private final DcTracker mDcTracker;
     private final LocalLog mLocalLog = new LocalLog(REQUEST_LOG_SIZE);
 
     // Key: network request. Value: whether it's applied to DcTracker.
-    private final HashMap<NetworkRequest, Boolean> mNetworkRequests = new HashMap();
+    private final Map<NetworkRequest, Boolean> mNetworkRequests = new HashMap<>();
 
     private final Phone mPhone;
+
+    private final TransportManager mTransportManager;
+
     private int mSubscriptionId;
 
     private final static int TELEPHONY_NETWORK_SCORE = 50;
@@ -74,6 +76,7 @@
         super(looper, phone.getContext(), "TelephonyNetworkFactory[" + phone.getPhoneId()
                 + "]", null);
         mPhone = phone;
+        mTransportManager = mPhone.getTransportManager();
         mInternalHandler = new InternalHandler(looper);
 
         mSubscriptionController = SubscriptionController.getInstance();
@@ -84,9 +87,6 @@
         mPhoneSwitcher = PhoneSwitcher.getInstance();
         mSubscriptionMonitor = subscriptionMonitor;
         LOG_TAG = "TelephonyNetworkFactory[" + mPhone.getPhoneId() + "]";
-        // TODO: Will need to dynamically route network requests to the corresponding DcTracker in
-        // the future. For now we route everything to WWAN.
-        mDcTracker = mPhone.getDcTracker(TransportType.WWAN);
 
         mPhoneSwitcher.registerForActivePhoneSwitch(mInternalHandler, EVENT_ACTIVE_PHONE_SWITCH,
                 null);
@@ -151,6 +151,30 @@
         }
     }
 
+    private int getTransportTypeFromNetworkRequest(NetworkRequest networkRequest) {
+        int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
+        return mTransportManager.getCurrentTransport(apnType);
+    }
+
+    private void requestNetworkInternal(NetworkRequest networkRequest) {
+        int transportType = getTransportTypeFromNetworkRequest(networkRequest);
+        if (mPhone.getDcTracker(transportType) != null) {
+            // TODO: Handover logic will be added later. For now always normal request.
+            mPhone.getDcTracker(transportType).requestNetwork(networkRequest,
+                    DcTracker.REQUEST_TYPE_NORMAL, mLocalLog);
+        }
+    }
+
+    private void releaseNetworkInternal(NetworkRequest networkRequest, boolean cleanUpOnRelease) {
+        int transportType = getTransportTypeFromNetworkRequest(networkRequest);
+        if (mPhone.getDcTracker(transportType) != null) {
+            // TODO: Handover logic will be added later. For now always normal or detach request.
+            mPhone.getDcTracker(transportType).releaseNetwork(networkRequest,
+                    cleanUpOnRelease ? DcTracker.RELEASE_TYPE_DETACH
+                            : DcTracker.RELEASE_TYPE_NORMAL, mLocalLog);
+        }
+    }
+
     private void applyRequestsOnActivePhoneSwitch(NetworkRequest networkRequest,
             boolean cleanUpOnRelease, int action) {
         if (action == ACTION_NO_OP) return;
@@ -159,9 +183,9 @@
                 ? "Requesting" : "Releasing") + " network request " + networkRequest;
         mLocalLog.log(logStr);
         if (action == ACTION_REQUEST) {
-            mDcTracker.requestNetwork(networkRequest, mLocalLog);
+            requestNetworkInternal(networkRequest);
         } else if (action == ACTION_RELEASE) {
-            mDcTracker.releaseNetwork(networkRequest, mLocalLog, cleanUpOnRelease);
+            releaseNetworkInternal(networkRequest, cleanUpOnRelease);
         }
     }
 
@@ -221,7 +245,7 @@
         mLocalLog.log(s);
 
         if (shouldApply) {
-            mDcTracker.requestNetwork(networkRequest, mLocalLog);
+            requestNetworkInternal(networkRequest);
         }
     }
 
@@ -242,9 +266,8 @@
         log(s);
         mLocalLog.log(s);
 
-
         if (applied) {
-            mDcTracker.releaseNetwork(networkRequest, mLocalLog, false);
+            releaseNetworkInternal(networkRequest, false);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
index c54643b..9b7dd173 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
@@ -23,6 +23,7 @@
 import android.os.Message;
 import android.os.RegistrantList;
 import android.os.SystemProperties;
+import android.telephony.CallQuality;
 import android.telephony.NetworkScanRequest;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
@@ -133,6 +134,10 @@
         mTtyModeReceivedRegistrants.notifyRegistrants(result);
     }
 
+    public void onCallQualityChanged(CallQuality callQuality) {
+        mNotifier.notifyCallQualityChanged(this, callQuality);
+    }
+
     @Override
     public ServiceState getServiceState() {
         // FIXME: we may need to provide this when data connectivity is lost
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 376c732..603f821 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -46,6 +46,7 @@
 import android.telecom.ConferenceParticipant;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.CallQuality;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
@@ -2752,6 +2753,15 @@
                 conn.updateMultipartyState(isMultiParty);
             }
         }
+
+        /**
+         * Handles a change to the call quality for an {@code ImsCall}.
+         * Notifies apps through the System API {@link PhoneStateListener#onCallAttributesChanged}.
+         */
+        @Override
+        public void onCallQualityChanged(ImsCall imsCall, CallQuality callQuality) {
+            mPhone.onCallQualityChanged(callQuality);
+        }
     };
 
     /**
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
index ac219ea..5dd9975 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -32,10 +32,11 @@
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_IP;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_IPV4V6;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_IPV6;
+import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_NON_IP;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_PPP;
+import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_UNSTRUCTURED;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_UNKNOWN;
 
-import android.hardware.radio.V1_0.SetupDataCallResult;
 import android.os.Build;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -1104,6 +1105,10 @@
                 return PDP_TYPE_IPV4V6;
             case "PPP":
                 return PDP_TYPE_PPP;
+            case "NON-IP":
+                return PDP_TYPE_NON_IP;
+            case "UNSTRUCTURED":
+                return PDP_TYPE_UNSTRUCTURED;
         }
         Rlog.e(TAG, "Unknown type: " + type);
         return PDP_UNKNOWN;
@@ -1419,23 +1424,23 @@
      * @param result Data call result
      */
     private void writeOnSetupDataCallResponse(int phoneId, int rilSerial, int rilError,
-                                              int rilRequest, SetupDataCallResult result) {
+                                              int rilRequest, DataCallResponse response) {
 
         RilSetupDataCallResponse setupDataCallResponse = new RilSetupDataCallResponse();
         RilDataCall dataCall = new RilDataCall();
 
-        if (result != null) {
-            setupDataCallResponse.status =
-                    (result.status == 0 ? RilDataCallFailCause.PDP_FAIL_NONE : result.status);
-            setupDataCallResponse.suggestedRetryTimeMillis = result.suggestedRetryTime;
+        if (response != null) {
+            setupDataCallResponse.status = (response.getStatus() == 0
+                    ? RilDataCallFailCause.PDP_FAIL_NONE : response.getStatus());
+            setupDataCallResponse.suggestedRetryTimeMillis = response.getSuggestedRetryTime();
 
-            dataCall.cid = result.cid;
-            if (!TextUtils.isEmpty(result.type)) {
-                dataCall.type = toPdpType(result.type);
+            dataCall.cid = response.getCallId();
+            if (!TextUtils.isEmpty(response.getType())) {
+                dataCall.type = toPdpType(response.getType());
             }
 
-            if (!TextUtils.isEmpty(result.ifname)) {
-                dataCall.iframe = result.ifname;
+            if (!TextUtils.isEmpty(response.getIfname())) {
+                dataCall.iframe = response.getIfname();
             }
         }
         setupDataCallResponse.call = dataCall;
@@ -1548,8 +1553,8 @@
                                             int rilRequest, Object ret) {
         switch (rilRequest) {
             case RIL_REQUEST_SETUP_DATA_CALL:
-                SetupDataCallResult result = (SetupDataCallResult) ret;
-                writeOnSetupDataCallResponse(phoneId, rilSerial, rilError, rilRequest, result);
+                DataCallResponse response = (DataCallResponse) ret;
+                writeOnSetupDataCallResponse(phoneId, rilSerial, rilError, rilRequest, response);
                 break;
             case RIL_REQUEST_DEACTIVATE_DATA_CALL:
                 writeOnDeactivateDataCallResponse(phoneId, rilError);
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
index cf2154c..0b1b349 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -29,7 +29,7 @@
 import android.os.Parcel;
 import android.os.SystemClock;
 import android.os.WorkSource;
-import android.service.carrier.CarrierIdentifier;
+import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoGsm;
 import android.telephony.CellSignalStrengthCdma;
@@ -57,6 +57,7 @@
 import com.android.internal.telephony.LastCallFailCause;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.RadioCapability;
 import com.android.internal.telephony.SmsResponse;
 import com.android.internal.telephony.UUSInfo;
@@ -540,7 +541,12 @@
      */
     @Override
     public void getDataCallList(Message result) {
-        resultSuccess(result, new ArrayList<DataCallResponse>(0));
+        ArrayList<SetupDataCallResult> dcCallList = new ArrayList<SetupDataCallResult>(0);
+        SimulatedCommandsVerifier.getInstance().getDataCallList(result);
+        if (mSetupDataCallResult != null) {
+            dcCallList.add(mSetupDataCallResult);
+        }
+        resultSuccess(result, dcCallList);
     }
 
     /**
@@ -1164,11 +1170,11 @@
             }
         }
 
+        DataCallResponse response = RIL.convertDataCallResult(mSetupDataCallResult);
         if (mDcSuccess) {
-            resultSuccess(result, mSetupDataCallResult);
+            resultSuccess(result, response);
         } else {
-            resultFail(result, mSetupDataCallResult,
-                    new RuntimeException("Setup data call failed!"));
+            resultFail(result, response, new RuntimeException("Setup data call failed!"));
         }
     }
 
@@ -2059,8 +2065,8 @@
     }
 
     @Override
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result,
-            WorkSource workSource) {
+    public void setAllowedCarriers(CarrierRestrictionRules carrierRestrictionRules,
+            Message result, WorkSource workSource) {
         unimplemented(result);
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index 97260af..a8db6c8 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -25,6 +25,7 @@
 import android.os.Message;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.TelephonyComponentFactory;
@@ -489,7 +490,7 @@
      * card or the EID of the card for an eUICC card.
      */
     public String getCardId() {
-        if (mCardId != null) {
+        if (!TextUtils.isEmpty(mCardId)) {
             return mCardId;
         } else if (mUiccProfile != null) {
             return mUiccProfile.getIccId();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierRestrictionRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierRestrictionRulesTest.java
new file mode 100644
index 0000000..e493577
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierRestrictionRulesTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.os.Parcel;
+import android.service.carrier.CarrierIdentifier;
+import android.telephony.CarrierRestrictionRules;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+
+/** Unit tests for {@link CarrierRestrictionRules}. */
+
+public class CarrierRestrictionRulesTest extends AndroidTestCase {
+
+    private static final String MCC1 = "110";
+    private static final String MNC1 = "210";
+    private static final String MCC2 = "120";
+    private static final String MNC2 = "220";
+    private static final String MCC1_WILDCHAR = "3??";
+    private static final String MNC1_WILDCHAR = "???";
+    private static final String MCC2_WILDCHAR = "31?";
+    private static final String MNC2_WILDCHAR = "?0";
+    private static final String GID1 = "80";
+
+    @SmallTest
+    public void testBuilderAllowedAndExcludedCarriers() {
+        ArrayList<CarrierIdentifier> allowedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierIdentifier(MCC1, MNC1, null, null, null, null));
+        allowedCarriers.add(new CarrierIdentifier(MCC2, MNC2, null, null, null, null));
+
+        ArrayList<CarrierIdentifier> excludedCarriers = new ArrayList<>();
+        excludedCarriers.add(new CarrierIdentifier(MCC2, MNC2, null, null, GID1, null));
+
+        CarrierRestrictionRules rules = CarrierRestrictionRules.newBuilder()
+                .setAllowedCarriers(allowedCarriers)
+                .setExcludedCarriers(excludedCarriers)
+                .build();
+
+        assertEquals(false, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriers().equals(allowedCarriers));
+        assertTrue(rules.getExcludedCarriers().equals(excludedCarriers));
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
+
+    @SmallTest
+    public void testBuilderEmptyLists() {
+        ArrayList<CarrierIdentifier> emptyCarriers = new ArrayList<>();
+
+        CarrierRestrictionRules rules = CarrierRestrictionRules.newBuilder().build();
+
+        assertEquals(false, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriers().equals(emptyCarriers));
+        assertTrue(rules.getExcludedCarriers().equals(emptyCarriers));
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
+
+    @SmallTest
+    public void testBuilderWildCharacter() {
+        ArrayList<CarrierIdentifier> allowedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierIdentifier(MCC1_WILDCHAR, MNC1_WILDCHAR, null, null,
+                null, null));
+
+        ArrayList<CarrierIdentifier> excludedCarriers = new ArrayList<>();
+        excludedCarriers.add(new CarrierIdentifier(MCC2_WILDCHAR, MNC2_WILDCHAR, null, null,
+                GID1, null));
+
+        CarrierRestrictionRules rules = CarrierRestrictionRules.newBuilder()
+                .setAllowedCarriers(allowedCarriers)
+                .setExcludedCarriers(excludedCarriers)
+                .build();
+
+        assertEquals(false, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriers().equals(allowedCarriers));
+        assertTrue(rules.getExcludedCarriers().equals(excludedCarriers));
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
+
+    @SmallTest
+    public void testBuilderDefaultAllowed() {
+        ArrayList<CarrierIdentifier> allowedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierIdentifier(MCC1, MNC1, null, null, null, null));
+        allowedCarriers.add(new CarrierIdentifier(MCC2, MNC2, null, null, null, null));
+
+        ArrayList<CarrierIdentifier> excludedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierIdentifier(MCC2, MNC2, null, null, GID1, null));
+
+        CarrierRestrictionRules rules = CarrierRestrictionRules.newBuilder()
+                .setAllowedCarriers(allowedCarriers)
+                .setExcludedCarriers(excludedCarriers)
+                .setDefaultCarrierRestriction(
+                    CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED)
+                .build();
+
+        assertEquals(false, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriers().equals(allowedCarriers));
+        assertTrue(rules.getExcludedCarriers().equals(excludedCarriers));
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
+
+    @SmallTest
+    public void testBuilderAllCarriersAllowed() {
+        ArrayList<CarrierIdentifier> allowedCarriers = new ArrayList<>();
+        ArrayList<CarrierIdentifier> excludedCarriers = new ArrayList<>();
+
+        CarrierRestrictionRules rules = CarrierRestrictionRules.newBuilder()
+                .setAllCarriersAllowed()
+                .build();
+
+        assertEquals(true, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriers().equals(allowedCarriers));
+        assertTrue(rules.getExcludedCarriers().equals(excludedCarriers));
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
+
+    @SmallTest
+    public void testParcel() {
+        ArrayList<CarrierIdentifier> allowedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierIdentifier(MCC1, MNC1, null, null, null, null));
+        allowedCarriers.add(new CarrierIdentifier(MCC2, MNC2, null, null, null, null));
+
+        ArrayList<CarrierIdentifier> excludedCarriers = new ArrayList<>();
+        excludedCarriers.add(new CarrierIdentifier(MCC2, MNC2, null, null, GID1, null));
+
+        CarrierRestrictionRules rules = CarrierRestrictionRules.newBuilder()
+                .setAllowedCarriers(allowedCarriers)
+                .setExcludedCarriers(excludedCarriers)
+                .setDefaultCarrierRestriction(
+                    CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED)
+                .setMultiSimPolicy(
+                    CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT)
+                .build();
+
+        Parcel p = Parcel.obtain();
+        rules.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        CarrierRestrictionRules newRules = CarrierRestrictionRules.CREATOR.createFromParcel(p);
+
+        assertEquals(false, rules.isAllCarriersAllowed());
+        assertTrue(allowedCarriers.equals(newRules.getAllowedCarriers()));
+        assertTrue(excludedCarriers.equals(newRules.getExcludedCarriers()));
+        assertEquals(rules.getDefaultCarrierRestriction(),
+                newRules.getDefaultCarrierRestriction());
+        assertEquals(rules.getMultiSimPolicy(),
+                CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT);
+    }
+
+    @SmallTest
+    public void testDefaultMultiSimPolicy() {
+        CarrierRestrictionRules rules = CarrierRestrictionRules.newBuilder().build();
+
+        assertEquals(CarrierRestrictionRules.MULTISIM_POLICY_NONE, rules.getMultiSimPolicy());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index 2758d7c..c9fa7b7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -189,45 +189,49 @@
 
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
         verify(mTelephonyRegisteryMock, times(0)).notifyPreciseCallState(anyInt(), anyInt(),
-                anyInt());
+                anyInt(), anyInt());
 
         doReturn(mForeGroundCall).when(mPhone).getForegroundCall();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
         verify(mTelephonyRegisteryMock, times(0)).notifyPreciseCallState(anyInt(), anyInt(),
-                anyInt());
+                anyInt(), anyInt());
 
         doReturn(mBackGroundCall).when(mPhone).getBackgroundCall();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
         verify(mTelephonyRegisteryMock, times(0)).notifyPreciseCallState(anyInt(), anyInt(),
-                anyInt());
+                anyInt(), anyInt());
 
         doReturn(mRingingCall).when(mPhone).getRingingCall();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
         verify(mTelephonyRegisteryMock, times(1)).notifyPreciseCallState(
                 PreciseCallState.PRECISE_CALL_STATE_IDLE,
                 PreciseCallState.PRECISE_CALL_STATE_IDLE,
-                PreciseCallState.PRECISE_CALL_STATE_IDLE);
+                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                mPhone.getPhoneId());
 
         doReturn(Call.State.ACTIVE).when(mForeGroundCall).getState();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
         verify(mTelephonyRegisteryMock, times(1)).notifyPreciseCallState(
                 PreciseCallState.PRECISE_CALL_STATE_IDLE,
                 PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
-                PreciseCallState.PRECISE_CALL_STATE_IDLE);
+                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                mPhone.getPhoneId());
 
         doReturn(Call.State.HOLDING).when(mBackGroundCall).getState();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
         verify(mTelephonyRegisteryMock, times(1)).notifyPreciseCallState(
                 PreciseCallState.PRECISE_CALL_STATE_IDLE,
                 PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
-                PreciseCallState.PRECISE_CALL_STATE_HOLDING);
+                PreciseCallState.PRECISE_CALL_STATE_HOLDING,
+                mPhone.getPhoneId());
 
         doReturn(Call.State.ALERTING).when(mRingingCall).getState();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
         verify(mTelephonyRegisteryMock, times(1)).notifyPreciseCallState(
                 PreciseCallState.PRECISE_CALL_STATE_ALERTING,
                 PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
-                PreciseCallState.PRECISE_CALL_STATE_HOLDING);
+                PreciseCallState.PRECISE_CALL_STATE_HOLDING,
+                mPhone.getPhoneId());
     }
 
     @Test @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index e3e5d96..d074155 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -103,7 +103,8 @@
                     + SubscriptionManager.ISO_COUNTRY_CODE + " TEXT,"
                     + SubscriptionManager.CARRIER_ID + " INTEGER DEFAULT -1,"
                     + SubscriptionManager.PROFILE_CLASS
-                    + " INTEGER DEFAULT " + SubscriptionManager.PROFILE_CLASS_DEFAULT
+                    + " INTEGER DEFAULT " + SubscriptionManager.PROFILE_CLASS_DEFAULT + ","
+                    + SubscriptionManager.SUBSCRIPTION_TYPE + " INTEGER DEFAULT 0"
                     + ");";
         }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
index 04b36b8..8f97571 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
@@ -24,6 +24,7 @@
 import android.text.SpannableStringBuilder;
 import android.text.TextWatcher;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class PhoneNumberWatcherTest {
@@ -267,6 +268,7 @@
      * case the replacement text should be formatted by the PhoneNumberFormattingTextWatcher.
      */
     @Test @SmallTest
+    @Ignore("b/122886015")
     public void testAutoCompleteUnformattedWithUnformattedNumber() {
         String init = "650";
         String replacement = "6501234567";
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index b87b416..c23c274 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -92,6 +92,7 @@
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.hardware.radio.V1_0.Carrier;
 import android.hardware.radio.V1_0.CdmaSmsMessage;
 import android.hardware.radio.V1_0.DataProfileInfo;
 import android.hardware.radio.V1_0.GsmSmsMessage;
@@ -104,12 +105,15 @@
 import android.hardware.radio.V1_0.SmsWriteArgs;
 import android.hardware.radio.deprecated.V1_0.IOemHook;
 import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IPowerManager;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.WorkSource;
+import android.service.carrier.CarrierIdentifier;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
@@ -130,6 +134,7 @@
 import android.telephony.SmsManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 
 import androidx.test.filters.FlakyTest;
@@ -716,7 +721,7 @@
                 mSerialNumberCaptor.capture(),
                 eq((DataProfileInfo) invokeMethod(
                         mRILInstance,
-                        "convertToHalDataProfile",
+                        "convertToHalDataProfile10",
                         new Class<?>[] {DataProfile.class},
                         new Object[] {dataProfile})),
                 eq(dataProfile.isPersistent()),
@@ -1027,7 +1032,7 @@
                 mRILInstance,
                 "obtainRequest",
                 new Class<?>[] {Integer.TYPE, Message.class, WorkSource.class},
-                new Object[] {RIL_REQUEST_GET_SIM_STATUS, obtainMessage(), null});
+                new Object[] {RIL_REQUEST_GET_SIM_STATUS, obtainMessage(), new WorkSource()});
 
         // The wake lock should be held when obtain a RIL request.
         assertTrue(mRILInstance.getWakeLock(RIL.FOR_WAKELOCK).isHeld());
@@ -1526,6 +1531,57 @@
     }
 
     @Test
+    public void testConvertDataCallResult() throws Exception {
+        // Test V1.0 SetupDataCallResult
+        android.hardware.radio.V1_0.SetupDataCallResult result10 =
+                new android.hardware.radio.V1_0.SetupDataCallResult();
+        result10.status = android.hardware.radio.V1_0.DataCallFailCause.NONE;
+        result10.suggestedRetryTime = -1;
+        result10.cid = 0;
+        result10.active = 2;
+        result10.type = "IPV4V6";
+        result10.ifname = "ifname";
+        result10.addresses = "10.0.2.15 2607:fb90:a620:651d:eabe:f8da:c107:44be/64";
+        result10.dnses = "10.0.2.3 fd00:976a::9";
+        result10.gateways = "10.0.2.15 fe80::2";
+        result10.pcscf = "fd00:976a:c206:20::6   fd00:976a:c206:20::9    fd00:976a:c202:1d::9";
+        result10.mtu = 1500;
+
+        DataCallResponse response = new DataCallResponse(0, -1, 0, 2, "IPV4V6",
+                "ifname",
+                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress("10.0.2.15"), 32),
+                        new LinkAddress("2607:fb90:a620:651d:eabe:f8da:c107:44be/64")),
+                Arrays.asList(NetworkUtils.numericToInetAddress("10.0.2.3"),
+                        NetworkUtils.numericToInetAddress("fd00:976a::9")),
+                Arrays.asList(NetworkUtils.numericToInetAddress("10.0.2.15"),
+                        NetworkUtils.numericToInetAddress("fe80::2")),
+                Arrays.asList("fd00:976a:c206:20::6", "fd00:976a:c206:20::9",
+                        "fd00:976a:c202:1d::9"),
+                1500);
+
+        assertEquals(response, RIL.convertDataCallResult(result10));
+
+        // Test V1.4 SetupDataCallResult
+        android.hardware.radio.V1_4.SetupDataCallResult result14 =
+                new android.hardware.radio.V1_4.SetupDataCallResult();
+        result14.cause = android.hardware.radio.V1_4.DataCallFailCause.NONE;
+        result14.suggestedRetryTime = -1;
+        result14.cid = 0;
+        result14.active = android.hardware.radio.V1_4.DataConnActiveStatus.ACTIVE;
+        result14.type = android.hardware.radio.V1_4.PdpProtocolType.IPV4V6;
+        result14.ifname = "ifname";
+        result14.addresses = new ArrayList<>(
+                Arrays.asList("10.0.2.15", "2607:fb90:a620:651d:eabe:f8da:c107:44be/64"));
+        result14.dnses = new ArrayList<>(Arrays.asList("10.0.2.3", "fd00:976a::9"));
+        result14.gateways = new ArrayList<>(Arrays.asList("10.0.2.15", "fe80::2"));
+        result14.pcscf = new ArrayList<>(Arrays.asList(
+                "fd00:976a:c206:20::6", "fd00:976a:c206:20::9", "fd00:976a:c202:1d::9"));
+        result14.mtu = 1500;
+
+        assertEquals(response, RIL.convertDataCallResult(result14));
+    }
+
+    @Test
     public void testGetWorksourceClientId() {
         RILRequest request = RILRequest.obtain(0, null, null);
         assertEquals(null, request.getWorkSourceClientId());
@@ -1700,4 +1756,50 @@
         assertEquals(BEARER_BITMAP, dpi.bearerBitmap);
         assertEquals(MTU, dpi.mtu);
     }
+
+    @Test
+    public void testCreateCarrierRestrictionList() {
+        ArrayList<CarrierIdentifier> carriers = new ArrayList<>();
+        carriers.add(new CarrierIdentifier("110", "120", null, null, null, null));
+        carriers.add(new CarrierIdentifier("210", "220", "SPN", null, null, null));
+        carriers.add(new CarrierIdentifier("310", "320", null, "012345", null, null));
+        carriers.add(new CarrierIdentifier("410", "420", null, null, "GID1", null));
+        carriers.add(new CarrierIdentifier("510", "520", null, null, null, "GID2"));
+
+        Carrier c1 = new Carrier();
+        c1.mcc = "110";
+        c1.mnc = "120";
+        c1.matchType = CarrierIdentifier.MatchType.ALL;
+        Carrier c2 = new Carrier();
+        c2.mcc = "210";
+        c2.mnc = "220";
+        c2.matchType = CarrierIdentifier.MatchType.SPN;
+        c2.matchData = "SPN";
+        Carrier c3 = new Carrier();
+        c3.mcc = "310";
+        c3.mnc = "320";
+        c3.matchType = CarrierIdentifier.MatchType.IMSI_PREFIX;
+        c3.matchData = "012345";
+        Carrier c4 = new Carrier();
+        c4.mcc = "410";
+        c4.mnc = "420";
+        c4.matchType = CarrierIdentifier.MatchType.GID1;
+        c4.matchData = "GID1";
+        Carrier c5 = new Carrier();
+        c5.mcc = "510";
+        c5.mnc = "520";
+        c5.matchType = CarrierIdentifier.MatchType.GID2;
+        c5.matchData = "GID2";
+
+        ArrayList<Carrier> expected = new ArrayList<>();
+        expected.add(c1);
+        expected.add(c2);
+        expected.add(c3);
+        expected.add(c4);
+        expected.add(c5);
+
+        ArrayList<Carrier> result = RIL.createCarrierRestrictionList(carriers);
+
+        assertTrue(result.equals(expected));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index 1b7fdd8..29cfd27 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -46,8 +46,11 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 
 public class SubscriptionControllerTest extends TelephonyTest {
     private static final int SINGLE_SIM = 1;
@@ -58,13 +61,19 @@
     @Mock
     private ITelephonyRegistry.Stub mTelephonyRegisteryMock;
 
+    private static final String MAC_ADDRESS_PREFIX = "mac_";
+    private static final String DISPLAY_NAME_PREFIX = "my_phone_";
+
     @Before
     public void setUp() throws Exception {
         super.setUp("SubscriptionControllerTest");
 
         doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount();
         doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount();
-
+        mMockContentResolver = (MockContentResolver) mContext.getContentResolver();
+        mFakeTelephonyProvider = new FakeTelephonyProvider();
+        mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(),
+                mFakeTelephonyProvider);
         replaceInstance(SubscriptionController.class, "sInstance", null, null);
 
         SubscriptionController.init(mContext, null);
@@ -75,11 +84,6 @@
         mContextFixture.putIntArrayResource(com.android.internal.R.array.sim_colors, new int[]{5});
 
         mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone});
-        mMockContentResolver = (MockContentResolver) mContext.getContentResolver();
-        mFakeTelephonyProvider = new FakeTelephonyProvider();
-        mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(),
-                mFakeTelephonyProvider);
-
     }
 
     @After
@@ -91,6 +95,7 @@
         /*clear sub info in mSubscriptionControllerUT since they will otherwise be persistent
          * between each test case. */
         mSubscriptionControllerUT.clearSubInfo();
+        mSubscriptionControllerUT.resetStaticMembers();
 
         /* clear settings for default voice/data/sms sub ID */
         Settings.Global.putInt(mContext.getContentResolver(),
@@ -215,7 +220,7 @@
     }
 
     @Test @SmallTest
-    public void testDefaultSubID() {
+    public void testDefaultSubIdOnSingleSimDevice() {
         assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
                 mSubscriptionControllerUT.getDefaultDataSubId());
         assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
@@ -225,12 +230,10 @@
         /* insert one sim */
         testInsertSim();
         // if support single sim, sms/data/voice default sub should be the same
-        assertNotSame(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mSubscriptionControllerUT.getDefaultSubId());
-        assertEquals(mSubscriptionControllerUT.getDefaultDataSubId(),
-                mSubscriptionControllerUT.getDefaultSmsSubId());
-        assertEquals(mSubscriptionControllerUT.getDefaultDataSubId(),
-                mSubscriptionControllerUT.getDefaultVoiceSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId());
     }
 
     @Test @SmallTest
@@ -409,6 +412,168 @@
                 .notifyOpportunisticSubscriptionInfoChanged();
     }
 
+    @Test @SmallTest
+    public void testInsertRemoteSim() {
+        makeThisDeviceMultiSimCapable();
+
+        // verify there are no sim's in the system.
+        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
+
+        addAndVerifyRemoteSimAddition(1, 0);
+    }
+
+    private void addAndVerifyRemoteSimAddition(int num, int numOfCurrentSubs) {
+        // Verify the number of current subs in the system
+        assertEquals(numOfCurrentSubs,
+                mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
+
+        // if there are current subs in the system, get that info
+        List<SubscriptionInfo> mSubList;
+        ArrayList<String> macAddresses = new ArrayList<>();
+        ArrayList<String> displayNames = new ArrayList<>();
+        if (numOfCurrentSubs > 0) {
+            mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
+            assertNotNull(mSubList);
+            assertEquals(numOfCurrentSubs, mSubList.size());
+            for (SubscriptionInfo info : mSubList) {
+                assertNotNull(info.getIccId());
+                assertNotNull(info.getDisplayName());
+                macAddresses.add(info.getIccId());
+                displayNames.add(info.getDisplayName().toString());
+            }
+        }
+
+        // To add more subs, we need to create macAddresses + displaynames.
+        for (int i = 0; i < num; i++) {
+            macAddresses.add(MAC_ADDRESS_PREFIX + (numOfCurrentSubs + i));
+            displayNames.add(DISPLAY_NAME_PREFIX + (numOfCurrentSubs + i));
+        }
+
+        // Add subs - one at a time and verify the contents in subscription info data structs
+        for (int i = 0; i < num; i++) {
+            int index = numOfCurrentSubs + i;
+            mSubscriptionControllerUT.addSubInfo(macAddresses.get(index), displayNames.get(index),
+                    SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB,
+                    SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+
+            // make sure the subscription is added in SubscriptionController data structs
+            Map<Integer, ArrayList<Integer>> slotIndexToSubsMap =
+                    mSubscriptionControllerUT.getSlotIndexToSubIdsMap();
+            assertNotNull(slotIndexToSubsMap);
+            // Since All remote sim's go to the same slot index, there should only be one entry
+            assertEquals(1, slotIndexToSubsMap.size());
+
+            // get all the subscriptions available. should be what is just added in this method
+            // PLUS the number of subs that already existed before
+            int expectedNumOfSubs = numOfCurrentSubs + i + 1;
+            ArrayList<Integer> subIdsList =
+                    slotIndexToSubsMap.get(SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB);
+            assertNotNull(subIdsList);
+            assertEquals(expectedNumOfSubs, subIdsList.size());
+
+            // validate slot index, sub id etc
+            mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
+            assertNotNull(mSubList);
+            assertEquals(expectedNumOfSubs, mSubList.size());
+
+            // sort on subscription-id which will make sure the previously existing subscriptions
+            // are in earlier slots in the array
+            mSubList.sort(SUBSCRIPTION_INFO_COMPARATOR);
+
+            // Verify the subscription data. Skip the verification for the existing subs.
+            for (int j = numOfCurrentSubs; j < mSubList.size(); j++) {
+                SubscriptionInfo info = mSubList.get(j);
+                assertTrue(SubscriptionManager.isValidSubscriptionId(info.getSubscriptionId()));
+                assertEquals(SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB,
+                        info.getSimSlotIndex());
+                assertEquals(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM,
+                        info.getSubscriptionType());
+                assertEquals(macAddresses.get(j), info.getIccId());
+                assertEquals(displayNames.get(j), info.getDisplayName());
+            }
+        }
+    }
+
+    private static final Comparator<SubscriptionInfo> SUBSCRIPTION_INFO_COMPARATOR =
+            Comparator.comparingInt(o -> o.getSubscriptionId());
+
+    @Test @SmallTest
+    public void testInsertMultipleRemoteSims() {
+        makeThisDeviceMultiSimCapable();
+
+        // verify that there are no subscription info records
+        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
+        Map<Integer, ArrayList<Integer>> slotIndexToSubsMap =
+                mSubscriptionControllerUT.getSlotIndexToSubIdsMap();
+        assertNotNull(slotIndexToSubsMap);
+        assertTrue(slotIndexToSubsMap.isEmpty());
+
+        // Add a few subscriptions
+        addAndVerifyRemoteSimAddition(4, 0);
+    }
+
+
+    @Test @SmallTest
+    public void testDefaultSubIdOnMultiSimDevice() {
+        makeThisDeviceMultiSimCapable();
+
+        // Initially, defaults should be -1
+        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                mSubscriptionControllerUT.getDefaultDataSubId());
+        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                mSubscriptionControllerUT.getDefaultSmsSubId());
+        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                mSubscriptionControllerUT.getDefaultSmsSubId());
+
+        // Insert one Remote-Sim.
+        testInsertRemoteSim();
+
+        // defaults should be set to this newly-inserted subscription
+        assertEquals(1, mSubscriptionControllerUT.getDefaultSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId());
+
+        // Add a few subscriptions
+        addAndVerifyRemoteSimAddition(4, 1);
+
+        // defaults should be still be set to the first sub - and unchanged by the addition of
+        // the above multiple sims.
+        assertEquals(1, mSubscriptionControllerUT.getDefaultSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId());
+        assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId());
+    }
+
+    @Test @SmallTest
+    public void testRemoveSubscription() {
+        makeThisDeviceMultiSimCapable();
+
+        /* insert some sims */
+        testInsertMultipleRemoteSims();
+        assertEquals(1, mSubscriptionControllerUT.getDefaultSubId());
+        int[] subIdsArray = mSubscriptionControllerUT.getActiveSubIdList();
+        assertTrue(subIdsArray.length > 0);
+        int len = subIdsArray.length;
+
+        // remove the first sim - which also is the default sim.
+        int result = mSubscriptionControllerUT.removeSubInfo(MAC_ADDRESS_PREFIX + 0,
+                SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+
+        assertTrue(result > 0);
+        // now check the number of subs left. should be one less than earlier
+        int[] newSubIdsArray = mSubscriptionControllerUT.getActiveSubIdList();
+        assertTrue(newSubIdsArray.length > 0);
+        assertEquals(len - 1, newSubIdsArray.length);
+
+        // now check that there is a new default
+        assertNotSame(1, mSubscriptionControllerUT.getDefaultSubId());
+    }
+
+    private void makeThisDeviceMultiSimCapable() {
+        doReturn(10).when(mTelephonyManager).getSimCount();
+    }
+
     @Test
     @SmallTest
     public void testSetSubscriptionGroupWithModifyPermission() throws Exception {
@@ -444,6 +609,7 @@
     public void testSetSubscriptionGroupWithCarrierPrivilegePermission() throws Exception {
         testInsertSim();
         // Adding a second profile and mark as embedded.
+        // TODO b/123300875 slot index 1 is not expected to be valid
         mSubscriptionControllerUT.addSubInfoRecord("test2", 1);
         ContentValues values = new ContentValues();
         values.put(SubscriptionManager.IS_EMBEDDED, 1);
@@ -484,6 +650,7 @@
         // Put sub3 into slot 1 to make sub2 inactive.
         mContextFixture.addCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_PHONE_STATE);
+        // TODO b/123300875 slot index 1 is not expected to be valid
         mSubscriptionControllerUT.addSubInfoRecord("test3", 1);
         mContextFixture.removeCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_PHONE_STATE);
@@ -590,6 +757,7 @@
     @Test
     @SmallTest
     public void testGetActiveSubIdList() throws Exception {
+        // TODO b/123300875 slot index 1 is not expected to be valid
         mSubscriptionControllerUT.addSubInfoRecord("123", 1);   // sub 1
         mSubscriptionControllerUT.addSubInfoRecord("456", 0);   // sub 2
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
index a935b38..c804cd0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
@@ -511,25 +511,15 @@
         when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
                 new String[0], false /* removable */)).thenReturn(subInfoList);
 
-        assertTrue(mUpdater.updateEmbeddedSubscriptions(FAKE_CARD_ID));
+        assertFalse(mUpdater.updateEmbeddedSubscriptions(FAKE_CARD_ID));
 
         // No new entries should be created.
         verify(mSubscriptionController, times(0)).clearSubInfo();
         verify(mSubscriptionController, never()).insertEmptySubInfoRecord(anyString(), anyInt());
 
-        // 1 should not have been touched.
+        // No existing entries should have been updated.
         verify(mContentProvider, never()).update(eq(SubscriptionManager.CONTENT_URI), any(),
-                eq(SubscriptionManager.ICC_ID + "=\"1\""), isNull());
-        verify(mContentProvider, never()).update(eq(SubscriptionManager.CONTENT_URI), any(),
-                eq(SubscriptionManager.ICC_ID + "IN (\"1\")"), isNull());
-
-        // 2 should have been removed since it was returned from the cache but the LPA had an
-        // error when listing.
-        ArgumentCaptor<ContentValues> iccid2Values = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), iccid2Values.capture(),
-                eq(SubscriptionManager.ICC_ID + " IN (\"2\")"), isNull());
-        assertEquals(0,
-                iccid2Values.getValue().getAsInteger(SubscriptionManager.IS_EMBEDDED).intValue());
+                any(), isNull());
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
index 2d2d7ba..736ea5a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
@@ -21,9 +21,11 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
 import android.net.NetworkRequest;
 import android.telephony.data.ApnSetting;
@@ -87,14 +89,6 @@
 
     @Test
     @SmallTest
-    public void testDependencyMet() throws Exception {
-        assertTrue(mApnContext.getDependencyMet());
-        mApnContext.setDependencyMet(false);
-        assertFalse(mApnContext.getDependencyMet());
-    }
-
-    @Test
-    @SmallTest
     public void testReason() throws Exception {
         mApnContext.setReason("dataEnabled");
         assertEquals("dataEnabled", mApnContext.getReason());
@@ -108,7 +102,6 @@
         mApnContext.setState(DctConstants.State.DISCONNECTING);
         assertEquals(DctConstants.State.DISCONNECTING, mApnContext.getState());
         mApnContext.setEnabled(true);
-        mApnContext.setDependencyMet(true);
         assertFalse(mApnContext.isConnectable());
 
         mApnContext.setState(DctConstants.State.RETRYING);
@@ -123,19 +116,92 @@
 
     @Test
     @SmallTest
-    public void testNetworkRequest() throws Exception {
+    public void testNetworkRequestNormal() throws Exception {
         LocalLog log = new LocalLog(3);
-        NetworkRequest nr = new NetworkRequest.Builder().build();
-        mApnContext.requestNetwork(nr, log);
+        NetworkRequest nr1 = new NetworkRequest.Builder().build();
+        mApnContext.requestNetwork(nr1, DcTracker.REQUEST_TYPE_NORMAL, log);
 
-        verify(mDcTracker, times(1)).setEnabled(eq(ApnSetting.TYPE_DEFAULT), eq(true));
-        mApnContext.requestNetwork(nr, log);
-        verify(mDcTracker, times(1)).setEnabled(eq(ApnSetting.TYPE_DEFAULT), eq(true));
+        verify(mDcTracker, times(1)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_NORMAL));
+        mApnContext.requestNetwork(nr1, DcTracker.REQUEST_TYPE_NORMAL, log);
+        verify(mDcTracker, times(1)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_NORMAL));
 
-        mApnContext.releaseNetwork(nr, log);
-        verify(mDcTracker, times(1)).setEnabled(eq(ApnSetting.TYPE_DEFAULT), eq(false));
-        mApnContext.releaseNetwork(nr, log);
-        verify(mDcTracker, times(1)).setEnabled(eq(ApnSetting.TYPE_DEFAULT), eq(false));
+        mApnContext.requestNetwork(nr1, DcTracker.REQUEST_TYPE_NORMAL, log);
+        // The same request should be ignore.
+        verify(mDcTracker, times(1)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_NORMAL));
+
+        NetworkRequest nr2 = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+        LocalLog log2 = new LocalLog(3);
+
+        mApnContext.requestNetwork(nr2, DcTracker.REQUEST_TYPE_NORMAL, log2);
+        verify(mDcTracker, times(2)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_NORMAL));
+
+        mApnContext.releaseNetwork(nr1, DcTracker.RELEASE_TYPE_NORMAL, log);
+        verify(mDcTracker, never()).disableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.RELEASE_TYPE_NORMAL));
+
+        mApnContext.releaseNetwork(nr2, DcTracker.RELEASE_TYPE_NORMAL, log2);
+        verify(mDcTracker, times(1)).disableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.RELEASE_TYPE_NORMAL));
+    }
+
+    @Test
+    @SmallTest
+    public void testNetworkRequestDetach() throws Exception {
+        LocalLog log = new LocalLog(3);
+        NetworkRequest nr1 = new NetworkRequest.Builder().build();
+        mApnContext.requestNetwork(nr1, DcTracker.REQUEST_TYPE_NORMAL, log);
+
+        verify(mDcTracker, times(1)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_NORMAL));
+        mApnContext.requestNetwork(nr1, DcTracker.REQUEST_TYPE_NORMAL, log);
+        verify(mDcTracker, times(1)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_NORMAL));
+
+        mApnContext.requestNetwork(nr1, DcTracker.REQUEST_TYPE_NORMAL, log);
+        // The same request should be ignore.
+        verify(mDcTracker, times(1)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_NORMAL));
+
+        NetworkRequest nr2 = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+        LocalLog log2 = new LocalLog(3);
+
+        mApnContext.requestNetwork(nr2, DcTracker.REQUEST_TYPE_NORMAL, log2);
+        verify(mDcTracker, times(2)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_NORMAL));
+
+        mApnContext.releaseNetwork(nr1, DcTracker.RELEASE_TYPE_DETACH, log);
+        verify(mDcTracker, times(1)).disableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.RELEASE_TYPE_DETACH));
+
+        mApnContext.releaseNetwork(nr2, DcTracker.RELEASE_TYPE_NORMAL, log2);
+        verify(mDcTracker, times(1)).disableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.RELEASE_TYPE_NORMAL));
+    }
+
+    @Test
+    @SmallTest
+    public void testNetworkRequestHandover() throws Exception {
+        LocalLog log = new LocalLog(3);
+        NetworkRequest nr1 = new NetworkRequest.Builder().build();
+        mApnContext.requestNetwork(nr1, DcTracker.REQUEST_TYPE_HANDOVER, log);
+        verify(mDcTracker, times(1)).enableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.REQUEST_TYPE_HANDOVER));
+
+        mApnContext.releaseNetwork(nr1, DcTracker.RELEASE_TYPE_HANDOVER, log);
+        verify(mDcTracker, times(1)).disableApn(eq(ApnSetting.TYPE_DEFAULT),
+                eq(DcTracker.RELEASE_TYPE_HANDOVER));
     }
 
     @Test
@@ -198,17 +264,10 @@
     @SmallTest
     public void testIsReady() throws Exception {
         mApnContext.setEnabled(true);
-        mApnContext.setDependencyMet(true);
         assertTrue(mApnContext.isReady());
 
         mApnContext.setEnabled(false);
         assertFalse(mApnContext.isReady());
-
-        mApnContext.setDependencyMet(false);
-        assertFalse(mApnContext.isReady());
-
-        mApnContext.setEnabled(true);
-        assertFalse(mApnContext.isReady());
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/CellularDataServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/CellularDataServiceTest.java
deleted file mode 100644
index a34febc..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/CellularDataServiceTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-
-import static junit.framework.Assert.assertEquals;
-
-import android.hardware.radio.V1_0.SetupDataCallResult;
-import android.net.LinkAddress;
-import android.net.NetworkUtils;
-import android.telephony.data.DataCallResponse;
-
-import com.android.internal.telephony.dataconnection.CellularDataService;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-
-public class CellularDataServiceTest extends TelephonyTest {
-
-    private CellularDataService mCellularDataService;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp(getClass().getSimpleName());
-        mCellularDataService = new CellularDataService();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    @Test
-    public void testConvertDataCallResult() throws Exception {
-
-        SetupDataCallResult result = new SetupDataCallResult();
-        result.status = 0;
-        result.suggestedRetryTime = -1;
-        result.cid = 1;
-        result.active = 1;
-        result.type = "IP";
-        result.ifname = "eth0";
-        result.addresses = "10.0.2.15";
-        result.dnses = "10.0.2.3";
-        result.gateways = "10.0.2.15 fe80::2";
-        result.pcscf = "";
-        result.mtu = 1500;
-
-        DataCallResponse response = new DataCallResponse(0, -1, 1, 1, "IP",
-                "eth0",
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress("10.0.2.15"), 32)),
-                Arrays.asList(NetworkUtils.numericToInetAddress("10.0.2.3")),
-                Arrays.asList(NetworkUtils.numericToInetAddress("10.0.2.15"),
-                        NetworkUtils.numericToInetAddress("fe80::2")),
-                Arrays.asList(""),
-                1500);
-
-        assertEquals(response, mCellularDataService.convertDataCallResult(result));
-
-        result.status = 0;
-        result.suggestedRetryTime = -1;
-        result.cid = 0;
-        result.active = 2;
-        result.type = "IPV4V6";
-        result.ifname = "ifname";
-        result.addresses = "2607:fb90:a620:651d:eabe:f8da:c107:44be/64";
-        result.dnses = "fd00:976a::9      fd00:976a::10";
-        result.gateways = "fe80::4c61:1832:7b28:d36c    1.2.3.4";
-        result.pcscf = "fd00:976a:c206:20::6   fd00:976a:c206:20::9    fd00:976a:c202:1d::9";
-        result.mtu = 1500;
-
-        response = new DataCallResponse(0, -1, 0, 2, "IPV4V6",
-                "ifname",
-                Arrays.asList(new LinkAddress("2607:fb90:a620:651d:eabe:f8da:c107:44be/64")),
-                Arrays.asList(NetworkUtils.numericToInetAddress("fd00:976a::9"),
-                        NetworkUtils.numericToInetAddress("fd00:976a::10")),
-                Arrays.asList(NetworkUtils.numericToInetAddress("fe80::4c61:1832:7b28:d36c"),
-                        NetworkUtils.numericToInetAddress("1.2.3.4")),
-                Arrays.asList("fd00:976a:c206:20::6", "fd00:976a:c206:20::9",
-                        "fd00:976a:c202:1d::9"),
-                1500);
-
-        assertEquals(response, mCellularDataService.convertDataCallResult(result));
-    }
-}
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 6e9fa7b..08b1ab1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -48,6 +48,7 @@
 import android.database.MatrixCursor;
 import android.hardware.radio.V1_0.SetupDataCallResult;
 import android.net.LinkProperties;
+import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.Uri;
@@ -580,9 +581,9 @@
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
         waitForMs(200);
 
-        logd("Sending EVENT_ENABLE_NEW_APN");
+        logd("Sending EVENT_ENABLE_APN");
         // APN id 0 is APN_TYPE_DEFAULT
-        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
         waitForMs(200);
 
         dataConnectionReasons = new DataConnectionReasons();
@@ -629,9 +630,9 @@
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
         waitForMs(200);
 
-        logd("Sending EVENT_ENABLE_NEW_APN");
+        logd("Sending EVENT_ENABLE_APN");
         // APN id 0 is APN_TYPE_DEFAULT
-        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
         waitForMs(200);
 
         dataConnectionReasons = new DataConnectionReasons();
@@ -685,8 +686,8 @@
         //set Default and MMS to be metered in the CarrierConfigManager
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
-        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
-        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
 
         logd("Sending EVENT_RECORDS_LOADED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
@@ -734,8 +735,8 @@
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
 
-        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
-        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
 
         logd("Sending EVENT_RECORDS_LOADED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
@@ -786,8 +787,8 @@
         //set Default and MMS to be metered in the CarrierConfigManager
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
-        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
-        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
 
         logd("Sending DISABLE_ROAMING_CMD");
         mDct.setDataRoamingEnabledByUser(false);
@@ -863,7 +864,7 @@
         NetworkRequest nr = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
         LocalLog l = new LocalLog(100);
-        mDct.requestNetwork(nr, l);
+        mDct.requestNetwork(nr, DcTracker.REQUEST_TYPE_NORMAL, l);
         waitForMs(200);
 
         verifyDataConnected(FAKE_APN1);
@@ -898,8 +899,8 @@
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
 
-        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
-        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
 
         logd("Sending EVENT_RECORDS_LOADED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
@@ -940,7 +941,7 @@
         doReturn(apnSetting).when(mApnContext).getApnSetting();
         doReturn(mDataConnection).when(mApnContext).getDataConnection();
         doReturn(true).when(mApnContext).isEnabled();
-        doReturn(true).when(mApnContext).getDependencyMet();
+        doReturn(true).when(mApnContext).isDependencyMet();
         doReturn(true).when(mApnContext).isReady();
         doReturn(false).when(mApnContext).hasRestrictedRequests(eq(true));
     }
@@ -1187,7 +1188,7 @@
                 .getRilDataRadioTechnology();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
-        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
 
         logd("Sending EVENT_RECORDS_LOADED");
@@ -1307,7 +1308,7 @@
                 .getRilDataRadioTechnology();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
-        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
 
         logd("Sending EVENT_RECORDS_LOADED");
@@ -1426,6 +1427,220 @@
                 true, DataEnabledSettings.REASON_PROVISIONING_DATA_ENABLED_CHANGED);
     }*/
 
+    @Test
+    @SmallTest
+    public void testNetworkStatusChangedRecoveryOFF() throws Exception {
+        ContentResolver resolver = mContext.getContentResolver();
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 0);
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
+
+        logd("Sending EVENT_RECORDS_LOADED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        waitForMs(200);
+
+        ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
+        verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
+                eq(ServiceState.rilRadioTechnologyToAccessNetworkType(
+                mServiceState.getRilDataRadioTechnology())), dpCaptor.capture(),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, LTE_BEARER_BITMASK);
+
+        logd("Sending EVENT_NETWORK_STATUS_CHANGED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
+                NetworkAgent.VALID_NETWORK, 0, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_NETWORK_STATUS_CHANGED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
+                NetworkAgent.INVALID_NETWORK, 0, null));
+        waitForMs(200);
+
+        // Verify that its no-op when the new data stall detection feature is disabled
+        verify(mSimulatedCommandsVerifier, times(0)).getDataCallList(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testNetworkStatusChangedRecoveryON() throws Exception {
+        ContentResolver resolver = mContext.getContentResolver();
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 1);
+        Settings.System.putInt(resolver, "radio.data.stall.recovery.action", 0);
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
+
+        logd("Sending EVENT_RECORDS_LOADED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        waitForMs(200);
+
+        ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
+        verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
+                eq(ServiceState.rilRadioTechnologyToAccessNetworkType(
+                mServiceState.getRilDataRadioTechnology())), dpCaptor.capture(),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, LTE_BEARER_BITMASK);
+
+        logd("Sending EVENT_NETWORK_STATUS_CHANGED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
+                NetworkAgent.VALID_NETWORK, 0, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_NETWORK_STATUS_CHANGED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
+                NetworkAgent.INVALID_NETWORK, 0, null));
+        waitForMs(200);
+
+        verify(mSimulatedCommandsVerifier, times(1)).getDataCallList(any(Message.class));
+
+        // reset the setting at the end of this test
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 0);
+        waitForMs(200);
+    }
+
+    @Test
+    @SmallTest
+    public void testRecoveryStepPDPReset() throws Exception {
+        ContentResolver resolver = mContext.getContentResolver();
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 1);
+        Settings.Global.putLong(resolver,
+                Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, 100);
+        Settings.System.putInt(resolver, "radio.data.stall.recovery.action", 1);
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
+
+        logd("Sending EVENT_RECORDS_LOADED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        waitForMs(200);
+
+        ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
+        verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
+                eq(ServiceState.rilRadioTechnologyToAccessNetworkType(
+                mServiceState.getRilDataRadioTechnology())), dpCaptor.capture(),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, LTE_BEARER_BITMASK);
+
+        logd("Sending EVENT_NETWORK_STATUS_CHANGED false");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
+                NetworkAgent.INVALID_NETWORK, 0, null));
+        waitForMs(200);
+
+        // expected tear down all DataConnections
+        verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(
+                eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
+                any(Message.class));
+
+        // reset the setting at the end of this test
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 0);
+        waitForMs(200);
+    }
+
+
+    @Test
+    @SmallTest
+    public void testRecoveryStepReRegister() throws Exception {
+        ContentResolver resolver = mContext.getContentResolver();
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 1);
+        Settings.Global.putLong(resolver,
+                Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, 100);
+        Settings.System.putInt(resolver, "radio.data.stall.recovery.action", 2);
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
+
+        logd("Sending EVENT_RECORDS_LOADED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        waitForMs(200);
+
+        ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
+        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
+                eq(ServiceState.rilRadioTechnologyToAccessNetworkType(
+                mServiceState.getRilDataRadioTechnology())), dpCaptor.capture(),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, LTE_BEARER_BITMASK);
+
+        logd("Sending EVENT_NETWORK_STATUS_CHANGED false");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
+                NetworkAgent.INVALID_NETWORK, 0, null));
+        waitForMs(200);
+
+        // expected to get preferred network type
+        verify(mSST, times(1)).reRegisterNetwork(eq(null));
+
+        // reset the setting at the end of this test
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 0);
+    }
+
+    @Test
+    @SmallTest
+    public void testRecoveryStepRestartRadio() throws Exception {
+        ContentResolver resolver = mContext.getContentResolver();
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 1);
+        Settings.Global.putLong(resolver,
+                Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, 100);
+        Settings.System.putInt(resolver, "radio.data.stall.recovery.action", 3);
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL);
+
+        logd("Sending EVENT_RECORDS_LOADED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        waitForMs(200);
+
+        ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
+        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
+                eq(ServiceState.rilRadioTechnologyToAccessNetworkType(
+                mServiceState.getRilDataRadioTechnology())), dpCaptor.capture(),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, LTE_BEARER_BITMASK);
+
+        logd("Sending EVENT_NETWORK_STATUS_CHANGED false");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
+                NetworkAgent.INVALID_NETWORK, 0, null));
+        waitForMs(200);
+
+        // expected to get preferred network type
+        verify(mSST, times(1)).powerOffRadioSafely();
+
+        // reset the setting at the end of this test
+        Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 0);
+    }
+
     private void verifyDataEnabledChangedMessage(boolean enabled, int reason) {
         verify(mHandler, times(1)).sendMessageDelayed(any(), anyLong());
         Pair<Boolean, Integer> result = (Pair) ((AsyncResult) mMessage.obj).result;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
index 8521369..49fe5bc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
@@ -20,7 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
 
 import android.content.Context;
@@ -113,12 +113,12 @@
         doAnswer(invocation -> {
             mNetworkRequestList.add((NetworkRequest) invocation.getArguments()[0]);
             return null;
-        }).when(mDcTracker).requestNetwork(any(), any());
+        }).when(mDcTracker).requestNetwork(any(), anyInt(), any());
 
         doAnswer(invocation -> {
             mNetworkRequestList.remove((NetworkRequest) invocation.getArguments()[0]);
             return null;
-        }).when(mDcTracker).releaseNetwork(any(), any(), anyBoolean());
+        }).when(mDcTracker).releaseNetwork(any(), anyInt(), any());
     }
 
     @After
@@ -149,9 +149,6 @@
 
         mTelephonyNetworkFactoryUT = new TelephonyNetworkFactory(mSubscriptionMonitorMock, mLooper,
                 mPhone);
-
-        replaceInstance(TelephonyNetworkFactory.class, "mDcTracker",
-                mTelephonyNetworkFactoryUT, mDcTracker);
     }
 
     /**
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 95f1b3a..6b987b7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
@@ -35,9 +35,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
 
-import android.hardware.radio.V1_0.SetupDataCallResult;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.data.DataCallResponse;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.feature.MmTelFeature;
@@ -70,6 +72,7 @@
 import org.mockito.Mock;
 
 import java.lang.reflect.Method;
+import java.util.Arrays;
 
 public class TelephonyMetricsTest extends TelephonyTest {
 
@@ -390,21 +393,22 @@
     @Test
     @SmallTest
     public void testWriteOnSetupDataCallResponse() throws Exception {
-        SetupDataCallResult result = new SetupDataCallResult();
-        result.status = 5;
-        result.suggestedRetryTime = 6;
-        result.cid = 7;
-        result.active = 8;
-        result.type = "IPV4V6";
-        result.ifname = FAKE_IFNAME;
-        result.addresses = FAKE_ADDRESS;
-        result.dnses = FAKE_DNS;
-        result.gateways = FAKE_GATEWAY;
-        result.pcscf = FAKE_PCSCF_ADDRESS;
-        result.mtu = 1440;
+        DataCallResponse response = new DataCallResponse(
+                5, /* status */
+                6, /* suggestedRetryTime */
+                7, /* cid */
+                8, /* active */
+                "IPV4V6", /* type */
+                FAKE_IFNAME, /* ifname */
+                Arrays.asList(new LinkAddress(
+                       NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)), /* addresses */
+                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)), /* dnses */
+                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)), /* gateways */
+                Arrays.asList(FAKE_PCSCF_ADDRESS), /* pcscfs */
+                1440 /* mtu */);
 
         mMetrics.writeOnRilSolicitedResponse(mPhone.getPhoneId(), 1, 2,
-                RIL_REQUEST_SETUP_DATA_CALL, result);
+                RIL_REQUEST_SETUP_DATA_CALL, response);
         TelephonyLog log = buildProto();
 
         assertEquals(1, log.events.length);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
index a77588b..9ef18ee 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
@@ -59,6 +59,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * @hide
@@ -656,7 +657,8 @@
     }
 
     @Override
-    public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdownEnabled) {
+    public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdownEnabled,
+            List<String> lockdownWhitelist) {
         throw new RuntimeException("not implemented");
     }
 
@@ -666,6 +668,16 @@
     }
 
     @Override
+    public boolean isVpnLockdownEnabled(int userId) {
+        throw new RuntimeException("not implemented");
+    }
+
+    @Override
+    public List<String> getVpnLockdownWhitelist(int userId) {
+        throw new RuntimeException("not implemented");
+    }
+
+    @Override
     public int checkMobileProvisioning(int suggestedTimeOutMs) {
         throw new RuntimeException("not implemented");
     }
@@ -946,6 +958,13 @@
     }
 
     @Override
+    public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
+            int intervalSeconds, Messenger messenger,
+            IBinder binder, String srcAddr, String dstAddr) {
+        throw new RuntimeException("not implemented");
+    }
+
+    @Override
     public void stopKeepalive(Network network, int slot) {
         throw new RuntimeException("not implemented");
     }
@@ -988,4 +1007,19 @@
     public NetworkRequest getDefaultRequest() {
         throw new RuntimeException("not implemented");
     }
+
+    @Override
+    public boolean isCallerCurrentAlwaysOnVpnApp() {
+        throw new RuntimeException("not implemented");
+    }
+
+    @Override
+    public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
+        throw new RuntimeException("not implemented");
+    }
+
+    @Override
+    public boolean getAvoidBadWifi() {
+        return true;
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
index beeba62..411ed1d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
@@ -144,6 +144,15 @@
         throw new RuntimeException("not implemented");
     }
     @Override
+    public int addSubInfo(String uniqueId, String displayName, int slotIndex,
+            int subscriptionType) {
+        throw new RuntimeException("not implemented");
+    }
+    @Override
+    public int removeSubInfo(String uniqueId, int subscriptionType) {
+        throw new RuntimeException("not implemented");
+    }
+    @Override
     public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,
             String spn) {
         throw new RuntimeException("not implemented");
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
index 418b46c..d5c6362 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
@@ -22,6 +22,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.telephony.CallQuality;
 import android.telephony.CellInfo;
 import android.telephony.DataFailCause;
 import android.telephony.PhoneCapability;
@@ -367,8 +368,13 @@
     }
 
     @Override
+    public void notifyCallQualityChanged(CallQuality callQuality, int phoneId) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
     public void notifyPreciseCallState(int ringingCallState, int foregroundCallState,
-            int backgroundCallState) {
+            int backgroundCallState, int phoneId) {
         throw new RuntimeException("Not implemented");
     }