Merge tag 'AU_LINUX_ANDROID_LA.BF64.1.2.1.05.01.00.066.050' into HEAD

AU_LINUX_ANDROID_LA.BF64.1.2.1.05.01.00.066.050 based on quic/aosp/LA.BF64.1.2.1

Change-Id: Ia70919ca311bdbcc69a86ad80f52fba38e9a5519
diff --git a/Android.mk b/Android.mk
index 20d7357..e78401a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,7 +22,7 @@
 LOCAL_PACKAGE_NAME := MmsService
 LOCAL_PRIVILEGED_MODULE := true
 
-LOCAL_JAVA_LIBRARIES := telephony-common
+LOCAL_JAVA_LIBRARIES := telephony-common okhttp
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 905d0d3..6063108 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -24,6 +24,8 @@
     <uses-permission android:name="android.permission.READ_SMS"/>
     <uses-permission android:name="android.permission.WRITE_SMS"/>
     <uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"/>
+    <uses-permission android:name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <application android:label="MmsService"
                  android:process="com.android.phone">
diff --git a/res/xml-mcc208-mnc20/mms_config.xml b/res/xml-mcc208-mnc20/mms_config.xml
index 486c468..1fbf09c 100644
--- a/res/xml-mcc208-mnc20/mms_config.xml
+++ b/res/xml-mcc208-mnc20/mms_config.xml
@@ -19,9 +19,10 @@
         version 2 - added recipientLimit.
         version 3 - added min/max recycler values.
         version 4 - added sms to mms text threshold.
+        version 5 - added supportHttpCharsetHeader.
 -->
 
-<mms_config version="4">
+<mms_config version="5">
     <!-- Maximum message size in bytes for a MMS message -->
     <int name="maxMessageSize">614400</int>
 
@@ -30,4 +31,7 @@
 
     <!-- Maximum width for an attached image -->
     <int name="maxImageWidth">2592</int>
+
+    <!-- Flag indicating whether charset should be added to Content-Type header-->
+    <bool name="supportHttpCharsetHeader">false</bool>
 </mms_config>
diff --git a/res/xml-mcc214-mnc01/mms_config.xml b/res/xml-mcc214-mnc01/mms_config.xml
index 0a9201b..d6fad05 100644
--- a/res/xml-mcc214-mnc01/mms_config.xml
+++ b/res/xml-mcc214-mnc01/mms_config.xml
@@ -17,12 +17,10 @@
 <!-- Version History
         version 1 - initial version.
         version 2 - enable force 7 bit encoding
+        version 3 - removing v2 changes and move to correct location
 -->
 
 <mms_config version="2">
     <!-- Disable SMS to MMS conversion for multiple recipient SMS -->
     <bool name="enableGroupMms">false</bool>
-
-    <!-- Enable force 7 bit encoding for Vodafone Spain -->
-    <bool name="config_sms_force_7bit_encoding">true</bool>
 </mms_config>
diff --git a/res/xml-mcc310-mnc004/mms_config.xml b/res/xml-mcc310-mnc004/mms_config.xml
index f0615f7..346218b 100644
--- a/res/xml-mcc310-mnc004/mms_config.xml
+++ b/res/xml-mcc310-mnc004/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-up-calling-line-id: 1##LINE1##|X-VzW-MDN: 1##LINE1##</string>
+    <string name="httpParams">x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc310-mnc005/mms_config.xml b/res/xml-mcc310-mnc005/mms_config.xml
index f0615f7..346218b 100644
--- a/res/xml-mcc310-mnc005/mms_config.xml
+++ b/res/xml-mcc310-mnc005/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-up-calling-line-id: 1##LINE1##|X-VzW-MDN: 1##LINE1##</string>
+    <string name="httpParams">x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc012/mms_config.xml b/res/xml-mcc311-mnc012/mms_config.xml
index f0615f7..346218b 100644
--- a/res/xml-mcc311-mnc012/mms_config.xml
+++ b/res/xml-mcc311-mnc012/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-up-calling-line-id: 1##LINE1##|X-VzW-MDN: 1##LINE1##</string>
+    <string name="httpParams">x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc220/mms_config.xml b/res/xml-mcc311-mnc220/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc220/mms_config.xml
+++ b/res/xml-mcc311-mnc220/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc221/mms_config.xml b/res/xml-mcc311-mnc221/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc221/mms_config.xml
+++ b/res/xml-mcc311-mnc221/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc222/mms_config.xml b/res/xml-mcc311-mnc222/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc222/mms_config.xml
+++ b/res/xml-mcc311-mnc222/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc223/mms_config.xml b/res/xml-mcc311-mnc223/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc223/mms_config.xml
+++ b/res/xml-mcc311-mnc223/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc224/mms_config.xml b/res/xml-mcc311-mnc224/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc224/mms_config.xml
+++ b/res/xml-mcc311-mnc224/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc225/mms_config.xml b/res/xml-mcc311-mnc225/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc225/mms_config.xml
+++ b/res/xml-mcc311-mnc225/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc226/mms_config.xml b/res/xml-mcc311-mnc226/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc226/mms_config.xml
+++ b/res/xml-mcc311-mnc226/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc227/mms_config.xml b/res/xml-mcc311-mnc227/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc227/mms_config.xml
+++ b/res/xml-mcc311-mnc227/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc228/mms_config.xml b/res/xml-mcc311-mnc228/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc228/mms_config.xml
+++ b/res/xml-mcc311-mnc228/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc229/mms_config.xml b/res/xml-mcc311-mnc229/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc229/mms_config.xml
+++ b/res/xml-mcc311-mnc229/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc230/mms_config.xml b/res/xml-mcc311-mnc230/mms_config.xml
index 3a81c58..b82f1d6 100755
--- a/res/xml-mcc311-mnc230/mms_config.xml
+++ b/res/xml-mcc311-mnc230/mms_config.xml
@@ -90,6 +90,6 @@
     <bool name="enableMMSDeliveryReports">false</bool>
 
     <!-- Reference for additional http parameters used in MMS http request. -->
-    <string name="httpParams">X-CS3G-MDN: 1##LINE1##</string>
+    <string name="httpParams">X-CS3G-MDN: 1##LINE1NOCOUNTRYCODE##</string>
 
 </mms_config>
diff --git a/res/xml-mcc311-mnc480/mms_config.xml b/res/xml-mcc311-mnc480/mms_config.xml
index cd9f0c7..1929b6a 100644
--- a/res/xml-mcc311-mnc480/mms_config.xml
+++ b/res/xml-mcc311-mnc480/mms_config.xml
@@ -22,7 +22,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-up-calling-line-id: 1##LINE1##|X-VzW-MDN: 1##LINE1##</string>
+    <string name="httpParams">x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc580/mms_config.xml b/res/xml-mcc311-mnc580/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc580/mms_config.xml
+++ b/res/xml-mcc311-mnc580/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc581/mms_config.xml b/res/xml-mcc311-mnc581/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc581/mms_config.xml
+++ b/res/xml-mcc311-mnc581/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc582/mms_config.xml b/res/xml-mcc311-mnc582/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc582/mms_config.xml
+++ b/res/xml-mcc311-mnc582/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc583/mms_config.xml b/res/xml-mcc311-mnc583/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc583/mms_config.xml
+++ b/res/xml-mcc311-mnc583/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc584/mms_config.xml b/res/xml-mcc311-mnc584/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc584/mms_config.xml
+++ b/res/xml-mcc311-mnc584/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc585/mms_config.xml b/res/xml-mcc311-mnc585/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc585/mms_config.xml
+++ b/res/xml-mcc311-mnc585/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc586/mms_config.xml b/res/xml-mcc311-mnc586/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc586/mms_config.xml
+++ b/res/xml-mcc311-mnc586/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc587/mms_config.xml b/res/xml-mcc311-mnc587/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc587/mms_config.xml
+++ b/res/xml-mcc311-mnc587/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc588/mms_config.xml b/res/xml-mcc311-mnc588/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc588/mms_config.xml
+++ b/res/xml-mcc311-mnc588/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc311-mnc589/mms_config.xml b/res/xml-mcc311-mnc589/mms_config.xml
index a3613f0..196d4f7 100644
--- a/res/xml-mcc311-mnc589/mms_config.xml
+++ b/res/xml-mcc311-mnc589/mms_config.xml
@@ -35,7 +35,7 @@
 
     <!-- Additional http parameters used in MMS http request.
          Parameters are seperated by '|'. Optional. -->
-    <string name="httpParams">x-vzw-mdn: 1##LINE1##</string>
+    <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
 
     <!-- Maximum height for an attached image -->
     <int name="maxImageHeight">1944</int>
diff --git a/res/xml-mcc312-mnc530/mms_config.xml b/res/xml-mcc312-mnc530/mms_config.xml
index 8c1ae11..7b1b093 100755
--- a/res/xml-mcc312-mnc530/mms_config.xml
+++ b/res/xml-mcc312-mnc530/mms_config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2013 The Android Open Source Project
+<!-- Copyright 2014 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.
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<mms_config version="4">
+<mms_config version="1">
     <!-- Flag indicating whether MMS should be enabled -->
     <bool name="enabledMMS">true</bool>
 
diff --git a/src/com/android/mms/service/ApnSettings.java b/src/com/android/mms/service/ApnSettings.java
index 4e43685..7efd7aa 100644
--- a/src/com/android/mms/service/ApnSettings.java
+++ b/src/com/android/mms/service/ApnSettings.java
@@ -16,9 +16,6 @@
 
 package com.android.mms.service;
 
-import com.android.internal.telephony.PhoneConstants;
-import com.android.mms.service.exception.ApnException;
-
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SqliteWrapper;
@@ -28,6 +25,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.telephony.PhoneConstants;
+import com.android.mms.service.exception.ApnException;
+
 import java.net.URI;
 import java.net.URISyntaxException;
 
@@ -43,17 +43,45 @@
     private final String mProxyAddress;
     // MMSC proxy port
     private final int mProxyPort;
+    // Debug text for this APN: a concatenation of interesting columns of this APN
+    private final String mDebugText;
 
     private static final String[] APN_PROJECTION = {
-            Telephony.Carriers.TYPE,            // 0
-            Telephony.Carriers.MMSC,            // 1
-            Telephony.Carriers.MMSPROXY,        // 2
-            Telephony.Carriers.MMSPORT          // 3
+            Telephony.Carriers.TYPE,
+            Telephony.Carriers.MMSC,
+            Telephony.Carriers.MMSPROXY,
+            Telephony.Carriers.MMSPORT,
+            Telephony.Carriers.NAME,
+            Telephony.Carriers.APN,
+            Telephony.Carriers.BEARER,
+            Telephony.Carriers.PROTOCOL,
+            Telephony.Carriers.ROAMING_PROTOCOL,
+            Telephony.Carriers.AUTH_TYPE,
+            Telephony.Carriers.MVNO_TYPE,
+            Telephony.Carriers.MVNO_MATCH_DATA,
+            Telephony.Carriers.PROXY,
+            Telephony.Carriers.PORT,
+            Telephony.Carriers.SERVER,
+            Telephony.Carriers.USER,
+            Telephony.Carriers.PASSWORD,
     };
     private static final int COLUMN_TYPE         = 0;
     private static final int COLUMN_MMSC         = 1;
     private static final int COLUMN_MMSPROXY     = 2;
     private static final int COLUMN_MMSPORT      = 3;
+    private static final int COLUMN_NAME         = 4;
+    private static final int COLUMN_APN          = 5;
+    private static final int COLUMN_BEARER       = 6;
+    private static final int COLUMN_PROTOCOL     = 7;
+    private static final int COLUMN_ROAMING_PROTOCOL = 8;
+    private static final int COLUMN_AUTH_TYPE    = 9;
+    private static final int COLUMN_MVNO_TYPE    = 10;
+    private static final int COLUMN_MVNO_MATCH_DATA = 11;
+    private static final int COLUMN_PROXY        = 12;
+    private static final int COLUMN_PORT         = 13;
+    private static final int COLUMN_SERVER       = 14;
+    private static final int COLUMN_USER         = 15;
+    private static final int COLUMN_PASSWORD     = 16;
 
 
     /**
@@ -62,7 +90,7 @@
      * @param context
      * @param apnName the optional APN name to match
      */
-    public static ApnSettings load(Context context, String apnName, long subId)
+    public static ApnSettings load(Context context, String apnName, int subId)
             throws ApnException {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "ApnSettings: apnName " + apnName);
@@ -119,7 +147,8 @@
                                 }
                             }
                         }
-                        return new ApnSettings(mmscUrl, proxyAddress, proxyPort);
+                        return new ApnSettings(
+                                mmscUrl, proxyAddress, proxyPort, getDebugText(cursor));
                     }
                 }
 
@@ -132,14 +161,33 @@
         throw new ApnException("Can not find valid APN");
     }
 
+    private static String getDebugText(Cursor cursor) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("APN [");
+        for (int i = 0; i < cursor.getColumnCount(); i++) {
+            final String name = cursor.getColumnName(i);
+            final String value = cursor.getString(i);
+            if (TextUtils.isEmpty(value)) {
+                continue;
+            }
+            if (i > 0) {
+                sb.append(' ');
+            }
+            sb.append(name).append('=').append(value);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
     private static String trimWithNullCheck(String value) {
         return value != null ? value.trim() : null;
     }
 
-    public ApnSettings(String mmscUrl, String proxyAddr, int proxyPort) {
+    public ApnSettings(String mmscUrl, String proxyAddr, int proxyPort, String debugText) {
         mServiceCenter = mmscUrl;
         mProxyAddress = proxyAddr;
         mProxyPort = proxyPort;
+        mDebugText = debugText;
    }
 
     public String getMmscUrl() {
@@ -173,11 +221,6 @@
     }
 
     public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("APN:");
-        sb.append(" mmsc=").append(mServiceCenter);
-        sb.append(" proxy=").append(mProxyAddress);
-        sb.append(" port=").append(mProxyPort);
-        return sb.toString();
+        return mDebugText;
     }
 }
diff --git a/src/com/android/mms/service/DownloadRequest.java b/src/com/android/mms/service/DownloadRequest.java
index 7e3010e..e54be0f 100644
--- a/src/com/android/mms/service/DownloadRequest.java
+++ b/src/com/android/mms/service/DownloadRequest.java
@@ -16,6 +16,33 @@
 
 package com.android.mms.service;
 
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Telephony;
+import android.service.carrier.CarrierMessagingService;
+import android.service.carrier.ICarrierMessagingCallback;
+import android.service.carrier.ICarrierMessagingService;
+import android.telephony.CarrierMessagingServiceManager;
+import android.telephony.TelephonyManager;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.mms.service.exception.MmsHttpException;
+
 import com.google.android.mms.MmsException;
 import com.google.android.mms.pdu.GenericPdu;
 import com.google.android.mms.pdu.PduHeaders;
@@ -24,26 +51,6 @@
 import com.google.android.mms.pdu.RetrieveConf;
 import com.google.android.mms.util.SqliteWrapper;
 
-import com.android.mms.service.exception.MmsHttpException;
-
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.provider.Telephony;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-
-import android.text.TextUtils;
-import android.util.Log;
-
 import java.util.List;
 
 /**
@@ -57,10 +64,10 @@
     private final PendingIntent mDownloadedIntent;
     private final Uri mContentUri;
 
-    public DownloadRequest(RequestManager manager, long subId, String locationUrl,
+    public DownloadRequest(RequestManager manager, int subId, String locationUrl,
             Uri contentUri, PendingIntent downloadedIntent, String creator,
             Bundle configOverrides) {
-        super(manager, null/*messageUri*/, subId, creator, configOverrides);
+        super(manager, subId, creator, configOverrides);
         mLocationUrl = locationUrl;
         mDownloadedIntent = downloadedIntent;
         mContentUri = contentUri;
@@ -69,12 +76,19 @@
     @Override
     protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
             throws MmsHttpException {
-        return doHttpForResolvedAddresses(context,
-                netMgr,
+        final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient();
+        if (mmsHttpClient == null) {
+            Log.e(MmsService.TAG, "MMS network is not ready!");
+            throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready");
+        }
+        return mmsHttpClient.execute(
                 mLocationUrl,
-                null/*pdu*/,
-                HttpUtils.HTTP_GET_METHOD,
-                apn);
+                null/*pud*/,
+                MmsHttpClient.METHOD_GET,
+                apn.isProxySet(),
+                apn.getProxyAddress(),
+                apn.getProxyPort(),
+                mMmsConfig);
     }
 
     @Override
@@ -83,14 +97,140 @@
     }
 
     @Override
-    protected int getRunningQueue() {
+    protected int getQueueType() {
         return MmsService.QUEUE_INDEX_DOWNLOAD;
     }
 
     @Override
-    protected void updateStatus(Context context, int result, byte[] response) {
-        if (mRequestManager.getAutoPersistingPref()) {
-            storeInboxMessage(context, result, response);
+    protected Uri persistIfRequired(Context context, int result, byte[] response) {
+        // Let any mms apps running as secondary user know that a new mms has been downloaded.
+        notifyOfDownload(context);
+
+        if (!mRequestManager.getAutoPersistingPref()) {
+            return null;
+        }
+        Log.d(MmsService.TAG, "DownloadRequest.persistIfRequired");
+        if (response == null || response.length < 1) {
+            Log.e(MmsService.TAG, "DownloadRequest.persistIfRequired: empty response");
+            return null;
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final GenericPdu pdu =
+                    (new PduParser(response, mMmsConfig.getSupportMmsContentDisposition())).parse();
+            if (pdu == null || !(pdu instanceof RetrieveConf)) {
+                Log.e(MmsService.TAG, "DownloadRequest.persistIfRequired: invalid parsed PDU");
+                return null;
+            }
+            final RetrieveConf retrieveConf = (RetrieveConf) pdu;
+            final int status = retrieveConf.getRetrieveStatus();
+            if (status != PduHeaders.RETRIEVE_STATUS_OK) {
+                Log.e(MmsService.TAG, "DownloadRequest.persistIfRequired: retrieve failed "
+                        + status);
+                // Update the retrieve status of the NotificationInd
+                final ContentValues values = new ContentValues(1);
+                values.put(Telephony.Mms.RETRIEVE_STATUS, status);
+                SqliteWrapper.update(
+                        context,
+                        context.getContentResolver(),
+                        Telephony.Mms.CONTENT_URI,
+                        values,
+                        LOCATION_SELECTION,
+                        new String[] {
+                                Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
+                                mLocationUrl
+                        });
+                return null;
+            }
+            // Store the downloaded message
+            final PduPersister persister = PduPersister.getPduPersister(context);
+            final Uri messageUri = persister.persist(
+                    pdu,
+                    Telephony.Mms.Inbox.CONTENT_URI,
+                    true/*createThreadId*/,
+                    true/*groupMmsEnabled*/,
+                    null/*preOpenedFiles*/);
+            if (messageUri == null) {
+                Log.e(MmsService.TAG, "DownloadRequest.persistIfRequired: can not persist message");
+                return null;
+            }
+            // Update some of the properties of the message
+            final ContentValues values = new ContentValues();
+            values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
+            values.put(Telephony.Mms.READ, 0);
+            values.put(Telephony.Mms.SEEN, 0);
+            if (!TextUtils.isEmpty(mCreator)) {
+                values.put(Telephony.Mms.CREATOR, mCreator);
+            }
+            values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId);
+            if (SqliteWrapper.update(
+                    context,
+                    context.getContentResolver(),
+                    messageUri,
+                    values,
+                    null/*where*/,
+                    null/*selectionArg*/) != 1) {
+                Log.e(MmsService.TAG, "DownloadRequest.persistIfRequired: can not update message");
+            }
+            // Delete the corresponding NotificationInd
+            SqliteWrapper.delete(context,
+                    context.getContentResolver(),
+                    Telephony.Mms.CONTENT_URI,
+                    LOCATION_SELECTION,
+                    new String[]{
+                            Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
+                            mLocationUrl
+                    });
+
+            return messageUri;
+        } catch (MmsException e) {
+            Log.e(MmsService.TAG, "DownloadRequest.persistIfRequired: can not persist message", e);
+        } catch (SQLiteException e) {
+            Log.e(MmsService.TAG, "DownloadRequest.persistIfRequired: can not update message", e);
+        } catch (RuntimeException e) {
+            Log.e(MmsService.TAG, "DownloadRequest.persistIfRequired: can not parse response", e);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return null;
+    }
+
+    private void notifyOfDownload(Context context) {
+        final Intent intent = new Intent(Telephony.Sms.Intents.MMS_DOWNLOADED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+
+        // Get a list of currently started users.
+        int[] users = null;
+        try {
+            users = ActivityManagerNative.getDefault().getRunningUserIds();
+        } catch (RemoteException re) {
+        }
+        if (users == null) {
+            users = new int[] {UserHandle.ALL.getIdentifier()};
+        }
+        final UserManager userManager =
+                (UserManager) context.getSystemService(Context.USER_SERVICE);
+
+        // Deliver the broadcast only to those running users that are permitted
+        // by user policy.
+        for (int i = users.length - 1; i >= 0; i--) {
+            UserHandle targetUser = new UserHandle(users[i]);
+            if (users[i] != UserHandle.USER_OWNER) {
+                // Is the user not allowed to use SMS?
+                if (userManager.hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) {
+                    continue;
+                }
+                // Skip unknown users and managed profiles as well
+                UserInfo info = userManager.getUserInfo(users[i]);
+                if (info == null || info.isManagedProfile()) {
+                    continue;
+                }
+            }
+            context.sendOrderedBroadcastAsUser(intent, targetUser,
+                    android.Manifest.permission.RECEIVE_MMS,
+                    AppOpsManager.OP_RECEIVE_MMS,
+                    null,
+                    null, Activity.RESULT_OK, null, null);
         }
     }
 
@@ -105,106 +245,92 @@
         return mRequestManager.writePduToContentUri(mContentUri, response);
     }
 
-    private void storeInboxMessage(Context context, int result, byte[] response) {
-        if (response == null || response.length < 1) {
-            return;
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final GenericPdu pdu = (new PduParser(response)).parse();
-            if (pdu == null || !(pdu instanceof RetrieveConf)) {
-                Log.e(MmsService.TAG, "DownloadRequest.updateStatus: invalid parsed PDU");
-                return;
-            }
-            // Store the downloaded message
-            final PduPersister persister = PduPersister.getPduPersister(context);
-            mMessageUri = persister.persist(
-                    pdu,
-                    Telephony.Mms.Inbox.CONTENT_URI,
-                    true/*createThreadId*/,
-                    true/*groupMmsEnabled*/,
-                    null/*preOpenedFiles*/);
-            if (mMessageUri == null) {
-                Log.e(MmsService.TAG, "DownloadRequest.updateStatus: can not persist message");
-                return;
-            }
-            // Update some of the properties of the message
-            ContentValues values = new ContentValues(5);
-            values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
-            values.put(Telephony.Mms.READ, 0);
-            values.put(Telephony.Mms.SEEN, 0);
-            if (!TextUtils.isEmpty(mCreator)) {
-                values.put(Telephony.Mms.CREATOR, mCreator);
-            }
-            values.put(Telephony.Mms.PHONE_ID, SubscriptionManager.getPhoneId(mSubId));
-            if (SqliteWrapper.update(
-                    context,
-                    context.getContentResolver(),
-                    mMessageUri,
-                    values,
-                    null/*where*/,
-                    null/*selectionArg*/) != 1) {
-                Log.e(MmsService.TAG, "DownloadRequest.updateStatus: can not update message");
-            }
-            // Delete the corresponding NotificationInd
-            SqliteWrapper.delete(context,
-                    context.getContentResolver(),
-                    Telephony.Mms.CONTENT_URI,
-                    LOCATION_SELECTION,
-                    new String[]{
-                            Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
-                            mLocationUrl
-                    }
-            );
-        } catch (MmsException e) {
-            Log.e(MmsService.TAG, "DownloadRequest.updateStatus: can not persist message", e);
-        } catch (SQLiteException e) {
-            Log.e(MmsService.TAG, "DownloadRequest.updateStatus: can not update message", e);
-        } catch (RuntimeException e) {
-            Log.e(MmsService.TAG, "DownloadRequest.updateStatus: can not parse response", e);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
+    @Override
     protected boolean prepareForHttpRequest() {
         return true;
     }
 
     /**
-     * Try downloading via the carrier app by sending intent.
+     * Try downloading via the carrier app.
      *
      * @param context The context
+     * @param carrierMessagingServicePackage The carrier messaging service handling the download
      */
-    public void tryDownloadingByCarrierApp(Context context) {
-        TelephonyManager telephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        Intent intent = new Intent(Telephony.Mms.Intents.MMS_DOWNLOAD_ACTION);
-        List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent(
-                intent);
-
-        if (carrierPackages == null || carrierPackages.size() != 1) {
-            mRequestManager.addRunning(this);
-        } else {
-            intent.setPackage(carrierPackages.get(0));
-            intent.putExtra(Telephony.Mms.Intents.EXTRA_MMS_LOCATION_URL, mLocationUrl);
-            intent.putExtra(Telephony.Mms.Intents.EXTRA_MMS_CONTENT_URI, mContentUri);
-            intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
-            context.sendOrderedBroadcastAsUser(
-                    intent,
-                    UserHandle.OWNER,
-                    android.Manifest.permission.RECEIVE_MMS,
-                    AppOpsManager.OP_RECEIVE_MMS,
-                    mCarrierAppResultReceiver,
-                    null/*scheduler*/,
-                    Activity.RESULT_CANCELED,
-                    null/*initialData*/,
-                    null/*initialExtras*/);
-        }
+    public void tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage) {
+        final CarrierDownloadManager carrierDownloadManger = new CarrierDownloadManager();
+        final CarrierDownloadCompleteCallback downloadCallback =
+                new CarrierDownloadCompleteCallback(context, carrierDownloadManger);
+        carrierDownloadManger.downloadMms(context, carrierMessagingServicePackage,
+                downloadCallback);
     }
 
     @Override
     protected void revokeUriPermission(Context context) {
         context.revokeUriPermission(mContentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
     }
+
+    /**
+     * Downloads the MMS through through the carrier app.
+     */
+    private final class CarrierDownloadManager extends CarrierMessagingServiceManager {
+        // Initialized in downloadMms
+        private volatile CarrierDownloadCompleteCallback mCarrierDownloadCallback;
+
+        void downloadMms(Context context, String carrierMessagingServicePackage,
+                CarrierDownloadCompleteCallback carrierDownloadCallback) {
+            mCarrierDownloadCallback = carrierDownloadCallback;
+            if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
+                Log.v(MmsService.TAG, "bindService() for carrier messaging service succeeded");
+            } else {
+                Log.e(MmsService.TAG, "bindService() for carrier messaging service failed");
+                carrierDownloadCallback.onDownloadMmsComplete(
+                        CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK);
+            }
+        }
+
+        @Override
+        protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
+            try {
+                carrierMessagingService.downloadMms(mContentUri, mSubId, Uri.parse(mLocationUrl),
+                        mCarrierDownloadCallback);
+            } catch (RemoteException e) {
+                Log.e(MmsService.TAG,
+                        "Exception downloading MMS using the carrier messaging service: " + e);
+                mCarrierDownloadCallback.onDownloadMmsComplete(
+                        CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK);
+            }
+        }
+    }
+
+    /**
+     * A callback which notifies carrier messaging app send result. Once the result is ready, the
+     * carrier messaging service connection is disposed.
+     */
+    private final class CarrierDownloadCompleteCallback extends
+            MmsRequest.CarrierMmsActionCallback {
+        private final Context mContext;
+        private final CarrierDownloadManager mCarrierDownloadManager;
+
+        public CarrierDownloadCompleteCallback(Context context,
+                CarrierDownloadManager carrierDownloadManager) {
+            mContext = context;
+            mCarrierDownloadManager = carrierDownloadManager;
+        }
+
+        @Override
+        public void onSendMmsComplete(int result, byte[] sendConfPdu) {
+            Log.e(MmsService.TAG, "Unexpected onSendMmsComplete call with result: " + result);
+        }
+
+        @Override
+        public void onDownloadMmsComplete(int result) {
+            Log.d(MmsService.TAG, "Carrier app result for download: " + result);
+            mCarrierDownloadManager.disposeConnection(mContext);
+
+            if (!maybeFallbackToRegularDelivery(result)) {
+                processResult(mContext, toSmsManagerResult(result), null/* response */,
+                        0/* httpStatusCode */);
+            }
+        }
+    }
 }
diff --git a/src/com/android/mms/service/HttpUtils.java b/src/com/android/mms/service/HttpUtils.java
deleted file mode 100644
index 43f70c3..0000000
--- a/src/com/android/mms/service/HttpUtils.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2014 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.mms.service;
-
-import com.android.mms.service.exception.MmsHttpException;
-import com.android.mms.service.http.NameResolver;
-import com.android.mms.service.http.NetworkAwareHttpClient;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.conn.params.ConnRouteParams;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-
-import android.content.Context;
-import android.net.http.AndroidHttpClient;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * HTTP utils to make HTTP request to MMSC
- */
-public class HttpUtils {
-    private static final String TAG = MmsService.TAG;
-
-    public static final int HTTP_POST_METHOD = 1;
-    public static final int HTTP_GET_METHOD = 2;
-
-    // Definition for necessary HTTP headers.
-    private static final String HDR_KEY_ACCEPT = "Accept";
-    private static final String HDR_KEY_ACCEPT_LANGUAGE = "Accept-Language";
-
-    private static final String HDR_VALUE_ACCEPT =
-        "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic";
-
-    private HttpUtils() {
-        // To forbidden instantiate this class.
-    }
-
-    /**
-     * A helper method to send or retrieve data through HTTP protocol.
-     *
-     * @param url The URL used in a GET request. Null when the method is
-     *         HTTP_POST_METHOD.
-     * @param pdu The data to be POST. Null when the method is HTTP_GET_METHOD.
-     * @param method HTTP_POST_METHOD or HTTP_GET_METHOD.
-     * @param isProxySet If proxy is set
-     * @param proxyHost The host of the proxy
-     * @param proxyPort The port of the proxy
-     * @param resolver The custom name resolver to use
-     * @param useIpv6 If we should use IPv6 address when the HTTP client resolves the host name
-     * @param mmsConfig The MmsConfig to use
-     * @return A byte array which contains the response data.
-     *         If an HTTP error code is returned, an IOException will be thrown.
-     * @throws com.android.mms.service.exception.MmsHttpException if HTTP request gets error response (&gt;=400)
-     */
-    public static byte[] httpConnection(Context context, String url, byte[] pdu, int method,
-            boolean isProxySet, String proxyHost, int proxyPort, NameResolver resolver,
-            boolean useIpv6, MmsConfig.Overridden mmsConfig) throws MmsHttpException {
-        final String methodString = getMethodString(method);
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "HttpUtils: request param list\n"
-                    + "url=" + url + "\n"
-                    + "method=" + methodString + "\n"
-                    + "isProxySet=" + isProxySet + "\n"
-                    + "proxyHost=" + proxyHost + "\n"
-                    + "proxyPort=" + proxyPort + "\n"
-                    + "size=" + (pdu != null ? pdu.length : 0));
-        } else {
-            Log.d(TAG, "HttpUtils: " + methodString + " " + url);
-        }
-
-        NetworkAwareHttpClient client = null;
-        try {
-            // Make sure to use a proxy which supports CONNECT.
-            URI hostUrl = new URI(url);
-            HttpHost target = new HttpHost(hostUrl.getHost(), hostUrl.getPort(),
-                    HttpHost.DEFAULT_SCHEME_NAME);
-            client = createHttpClient(context, resolver, useIpv6, mmsConfig);
-            HttpRequest req = null;
-
-            switch (method) {
-                case HTTP_POST_METHOD:
-                    ByteArrayEntity entity = new ByteArrayEntity(pdu);
-                    // Set request content type.
-                    entity.setContentType("application/vnd.wap.mms-message");
-                    HttpPost post = new HttpPost(url);
-                    post.setEntity(entity);
-                    req = post;
-                    break;
-                case HTTP_GET_METHOD:
-                    req = new HttpGet(url);
-                    break;
-            }
-
-            // Set route parameters for the request.
-            HttpParams params = client.getParams();
-            if (isProxySet) {
-                ConnRouteParams.setDefaultProxy(params, new HttpHost(proxyHost, proxyPort));
-            }
-            req.setParams(params);
-
-            // Set necessary HTTP headers for MMS transmission.
-            req.addHeader(HDR_KEY_ACCEPT, HDR_VALUE_ACCEPT);
-
-            // UA Profile URL header
-            String xWapProfileTagName = mmsConfig.getUaProfTagName();
-            String xWapProfileUrl = mmsConfig.getUaProfUrl();
-            if (xWapProfileUrl != null) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "HttpUtils: xWapProfUrl=" + xWapProfileUrl);
-                }
-                req.addHeader(xWapProfileTagName, xWapProfileUrl);
-            }
-
-            // Extra http parameters. Split by '|' to get a list of value pairs.
-            // Separate each pair by the first occurrence of ':' to obtain a name and
-            // value. Replace the occurrence of the string returned by
-            // MmsConfig.getHttpParamsLine1Key() with the users telephone number inside
-            // the value. And replace the occurrence of the string returned by
-            // MmsConfig.getHttpParamsNaiKey() with the users NAI(Network Access Identifier)
-            // inside the value.
-            String extraHttpParams = mmsConfig.getHttpParams();
-
-            if (!TextUtils.isEmpty(extraHttpParams)) {
-                // Parse the parameter list
-                String paramList[] = extraHttpParams.split("\\|");
-                for (String paramPair : paramList) {
-                    String splitPair[] = paramPair.split(":", 2);
-                    if (splitPair.length == 2) {
-                        final String name = splitPair[0].trim();
-                        final String value = resolveMacro(context, splitPair[1].trim(), mmsConfig);
-                        if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value)) {
-                            req.addHeader(name, value);
-                        }
-                    }
-                }
-            }
-            req.addHeader(HDR_KEY_ACCEPT_LANGUAGE, getCurrentAcceptLanguage(Locale.getDefault()));
-
-            final HttpResponse response = client.execute(target, req);
-            final StatusLine status = response.getStatusLine();
-            final HttpEntity entity = response.getEntity();
-            Log.d(TAG, "HttpUtils: status=" + status + " size="
-                    + (entity != null ? entity.getContentLength() : -1));
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                for (Header header : req.getAllHeaders()) {
-                    if (header != null) {
-                        Log.v(TAG, "HttpUtils: header "
-                                + header.getName() + "=" + header.getValue());
-                    }
-                }
-            }
-            byte[] body = null;
-            if (entity != null) {
-                try {
-                    if (entity.getContentLength() > 0) {
-                        body = new byte[(int) entity.getContentLength()];
-                        DataInputStream dis = new DataInputStream(entity.getContent());
-                        try {
-                            dis.readFully(body);
-                        } finally {
-                            try {
-                                dis.close();
-                            } catch (IOException e) {
-                                Log.e(TAG, "HttpUtils: Error closing input stream: "
-                                        + e.getMessage());
-                            }
-                        }
-                    }
-                    if (entity.isChunked()) {
-                        Log.d(TAG, "HttpUtils: transfer encoding is chunked");
-                        int bytesTobeRead = mmsConfig.getMaxMessageSize();
-                        byte[] tempBody = new byte[bytesTobeRead];
-                        DataInputStream dis = new DataInputStream(entity.getContent());
-                        try {
-                            int bytesRead = 0;
-                            int offset = 0;
-                            boolean readError = false;
-                            do {
-                                try {
-                                    bytesRead = dis.read(tempBody, offset, bytesTobeRead);
-                                } catch (IOException e) {
-                                    readError = true;
-                                    Log.e(TAG, "HttpUtils: error reading input stream", e);
-                                    break;
-                                }
-                                if (bytesRead > 0) {
-                                    bytesTobeRead -= bytesRead;
-                                    offset += bytesRead;
-                                }
-                            } while (bytesRead >= 0 && bytesTobeRead > 0);
-                            if (bytesRead == -1 && offset > 0 && !readError) {
-                                // offset is same as total number of bytes read
-                                // bytesRead will be -1 if the data was read till the eof
-                                body = new byte[offset];
-                                System.arraycopy(tempBody, 0, body, 0, offset);
-                                Log.d(TAG, "HttpUtils: Chunked response length " + offset);
-                            } else {
-                                Log.e(TAG, "HttpUtils: Response entity too large or empty");
-                            }
-                        } finally {
-                            try {
-                                dis.close();
-                            } catch (IOException e) {
-                                Log.e(TAG, "HttpUtils: Error closing input stream", e);
-                            }
-                        }
-                    }
-                } finally {
-                    if (entity != null) {
-                        entity.consumeContent();
-                    }
-                }
-            }
-            if (status.getStatusCode() != 200) { // HTTP 200 is success.
-                StringBuilder sb = new StringBuilder();
-                if (body != null) {
-                    sb.append("response: text=").append(new String(body)).append('\n');
-                }
-                for (Header header : req.getAllHeaders()) {
-                    if (header != null) {
-                        sb.append("req header: ")
-                                .append(header.getName())
-                                .append('=')
-                                .append(header.getValue())
-                                .append('\n');
-                    }
-                }
-                for (Header header : response.getAllHeaders()) {
-                    if (header != null) {
-                        sb.append("resp header: ")
-                                .append(header.getName())
-                                .append('=')
-                                .append(header.getValue())
-                                .append('\n');
-                    }
-                }
-                Log.e(TAG, "HttpUtils: error response -- \n"
-                        + "mStatusCode=" + status.getStatusCode() + "\n"
-                        + "reason=" + status.getReasonPhrase() + "\n"
-                        + "url=" + url + "\n"
-                        + "method=" + methodString + "\n"
-                        + "isProxySet=" + isProxySet + "\n"
-                        + "proxyHost=" + proxyHost + "\n"
-                        + "proxyPort=" + proxyPort
-                        + (sb != null ? "\n" + sb.toString() : ""));
-                throw new MmsHttpException(status.getReasonPhrase());
-            }
-            return body;
-        } catch (IOException e) {
-            Log.e(TAG, "HttpUtils: IO failure", e);
-            throw new MmsHttpException(e);
-        } catch (URISyntaxException e) {
-            Log.e(TAG, "HttpUtils: invalid url " + url);
-            throw new MmsHttpException("Invalid url " + url);
-        } finally {
-            if (client != null) {
-                client.close();
-            }
-        }
-    }
-
-    private static String getMethodString(int method) {
-        return ((method == HTTP_POST_METHOD) ?
-                "POST" : ((method == HTTP_GET_METHOD) ? "GET" : "UNKNOWN"));
-    }
-
-    /**
-     * Create an HTTP client
-     *
-     * @param context
-     * @return {@link android.net.http.AndroidHttpClient}
-     */
-    private static NetworkAwareHttpClient createHttpClient(Context context, NameResolver resolver,
-            boolean useIpv6, MmsConfig.Overridden mmsConfig) {
-        final String userAgent = mmsConfig.getUserAgent();
-        final NetworkAwareHttpClient client = NetworkAwareHttpClient.newInstance(userAgent, context,
-                resolver, useIpv6);
-        final HttpParams params = client.getParams();
-        HttpProtocolParams.setContentCharset(params, "UTF-8");
-
-        // set the socket timeout
-        int soTimeout = mmsConfig.getHttpSocketTimeout();
-
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "HttpUtils: createHttpClient w/ socket timeout "
-                    + soTimeout + " ms, UA=" + userAgent);
-        }
-        HttpConnectionParams.setSoTimeout(params, soTimeout);
-        return client;
-    }
-
-    private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
-
-    /**
-     * Return the Accept-Language header.  Use the current locale plus
-     * US if we are in a different locale than US.
-     * This code copied from the browser's WebSettings.java
-     *
-     * @return Current AcceptLanguage String.
-     */
-    public static String getCurrentAcceptLanguage(Locale locale) {
-        final StringBuilder buffer = new StringBuilder();
-        addLocaleToHttpAcceptLanguage(buffer, locale);
-
-        if (!Locale.US.equals(locale)) {
-            if (buffer.length() > 0) {
-                buffer.append(", ");
-            }
-            buffer.append(ACCEPT_LANG_FOR_US_LOCALE);
-        }
-
-        return buffer.toString();
-    }
-
-    /**
-     * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish,
-     * to new standard.
-     */
-    private static String convertObsoleteLanguageCodeToNew(String langCode) {
-        if (langCode == null) {
-            return null;
-        }
-        if ("iw".equals(langCode)) {
-            // Hebrew
-            return "he";
-        } else if ("in".equals(langCode)) {
-            // Indonesian
-            return "id";
-        } else if ("ji".equals(langCode)) {
-            // Yiddish
-            return "yi";
-        }
-        return langCode;
-    }
-
-    private static void addLocaleToHttpAcceptLanguage(StringBuilder builder, Locale locale) {
-        final String language = convertObsoleteLanguageCodeToNew(locale.getLanguage());
-        if (language != null) {
-            builder.append(language);
-            final String country = locale.getCountry();
-            if (country != null) {
-                builder.append("-");
-                builder.append(country);
-            }
-        }
-    }
-
-    private static final Pattern MACRO_P = Pattern.compile("##(\\S+)##");
-    /**
-     * Resolve the macro in HTTP param value text
-     * For example, "something##LINE1##something" is resolved to "something9139531419something"
-     *
-     * @param value The HTTP param value possibly containing macros
-     * @return The HTTP param with macro resolved to real value
-     */
-    private static String resolveMacro(Context context, String value,
-            MmsConfig.Overridden mmsConfig) {
-        if (TextUtils.isEmpty(value)) {
-            return value;
-        }
-        final Matcher matcher = MACRO_P.matcher(value);
-        int nextStart = 0;
-        StringBuilder replaced = null;
-        while (matcher.find()) {
-            if (replaced == null) {
-                replaced = new StringBuilder();
-            }
-            final int matchedStart = matcher.start();
-            if (matchedStart > nextStart) {
-                replaced.append(value.substring(nextStart, matchedStart));
-            }
-            final String macro = matcher.group(1);
-            final String macroValue = mmsConfig.getHttpParamMacro(context, macro);
-            if (macroValue != null) {
-                replaced.append(macroValue);
-            } else {
-                Log.w(TAG, "HttpUtils: invalid macro " + macro);
-            }
-            nextStart = matcher.end();
-        }
-        if (replaced != null && nextStart < value.length()) {
-            replaced.append(value.substring(nextStart));
-        }
-        return replaced == null ? value : replaced.toString();
-    }
-}
diff --git a/src/com/android/mms/service/MmsConfig.java b/src/com/android/mms/service/MmsConfig.java
index 201accc..4eea339 100755
--- a/src/com/android/mms/service/MmsConfig.java
+++ b/src/com/android/mms/service/MmsConfig.java
@@ -16,11 +16,10 @@
 
 package com.android.mms.service;
 
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.res.XmlResourceParser;
 import android.os.Bundle;
-import android.os.SystemProperties;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Base64;
@@ -93,6 +92,10 @@
     // FLAG(ywen): the following two is not supported yet.
     public static final String CONFIG_ENABLE_MMS_READ_REPORTS = "enableMMSReadReports";
     public static final String CONFIG_ENABLE_MMS_DELIVERY_REPORTS = "enableMMSDeliveryReports";
+    // Bouygues Telecom (20820) MMSC does not support "charset" with "Content-Type" header
+    // It would fail and return 500. See b/18604507
+    // If this is false, then we don't add "charset" to "Content-Type"
+    public static final String CONFIG_SUPPORT_HTTP_CHARSET_HEADER = "supportHttpCharsetHeader";
     public static final String CONFIG_MAX_MESSAGE_SIZE = "maxMessageSize"; // in bytes
     public static final String CONFIG_MAX_IMAGE_HEIGHT = "maxImageHeight"; // in pixels
     public static final String CONFIG_MAX_IMAGE_WIDTH = "maxImageWidth"; // in pixels
@@ -133,6 +136,8 @@
      */
     // The raw phone number from TelephonyManager.getLine1Number
     public static final String MACRO_LINE1 = "LINE1";
+    // The phone number without country code
+    public static final String MACRO_LINE1NOCOUNTRYCODE = "LINE1NOCOUNTRYCODE";
     // NAI (Network Access Identifier), used by Sprint for authentication
     public static final String MACRO_NAI = "NAI";
 
@@ -153,6 +158,7 @@
         DEFAULTS.put(CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES, Boolean.valueOf(false));
         DEFAULTS.put(CONFIG_ENABLE_MMS_READ_REPORTS, Boolean.valueOf(false));
         DEFAULTS.put(CONFIG_ENABLE_MMS_DELIVERY_REPORTS, Boolean.valueOf(false));
+        DEFAULTS.put(CONFIG_SUPPORT_HTTP_CHARSET_HEADER, Boolean.valueOf(false));
         DEFAULTS.put(CONFIG_MAX_MESSAGE_SIZE, Integer.valueOf(300 * 1024));
         DEFAULTS.put(CONFIG_MAX_IMAGE_HEIGHT, Integer.valueOf(MAX_IMAGE_HEIGHT));
         DEFAULTS.put(CONFIG_MAX_IMAGE_WIDTH, Integer.valueOf(MAX_IMAGE_WIDTH));
@@ -172,7 +178,7 @@
         DEFAULTS.put(CONFIG_NAI_SUFFIX, "");
     }
 
-    private final long mSubId;
+    private final int mSubId;
 
     /**
      * This class manages a cached copy of current MMS configuration key values for a particular
@@ -182,7 +188,7 @@
      * should be set to that of the subscription id
      * @param subId Subscription id of the mcc/mnc in the context
      */
-    public MmsConfig(Context context, long subId) {
+    public MmsConfig(Context context, int subId) {
         mSubId = subId;
         // Load defaults
         mKeyValues.clear();
@@ -200,7 +206,7 @@
      *
      * @return subId the subId associated with this MmsConfig
      */
-    public long getSubId() {
+    public int getSubId() {
         return mSubId;
     }
 
@@ -490,6 +496,10 @@
             return getBoolean(CONFIG_ENABLE_MMS_DELIVERY_REPORTS);
         }
 
+        public boolean getSupportHttpCharsetHeader() {
+            return getBoolean(CONFIG_SUPPORT_HTTP_CHARSET_HEADER);
+        }
+
         /**
          * Return the HTTP param macro value.
          * Example: LINE1 returns the phone number, etc.
@@ -499,9 +509,11 @@
          */
         public String getHttpParamMacro(Context context, String macro) {
             if (MACRO_LINE1.equals(macro)) {
-                return getLine1(context);
+                return getLine1(context, mBase.getSubId());
+            } else if (MACRO_LINE1NOCOUNTRYCODE.equals(macro)) {
+                return getLine1NoCountryCode(context, mBase.getSubId());
             } else if (MACRO_NAI.equals(macro)) {
-                return getNai();
+                return getNai(context, mBase.getSubId());
             }
             return null;
         }
@@ -509,18 +521,32 @@
         /**
          * @return the phone number
          */
-        private static String getLine1(Context context) {
-            // TODO: for MSIM, we will need to pass in the subId
+        private static String getLine1(Context context, int subId) {
             final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
                     Context.TELEPHONY_SERVICE);
-            return telephonyManager.getLine1Number();
+            return telephonyManager.getLine1NumberForSubscriber(subId);
+        }
+
+        private static String getLine1NoCountryCode(Context context, int subId) {
+            final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            return PhoneUtils.getNationalNumber(
+                    telephonyManager,
+                    subId,
+                    telephonyManager.getLine1NumberForSubscriber(subId));
         }
 
         /**
          * @return the NAI (Network Access Identifier) from SystemProperties
          */
-        private String getNai() {
-            String nai = SystemProperties.get("persist.radio.cdma.nai");
+        private String getNai(Context context, int subId) {
+            final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            String nai = telephonyManager.getNai(SubscriptionManager.getSlotId(subId));
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "MmsConfig.getNai: nai=" + nai);
+            }
+
             if (!TextUtils.isEmpty(nai)) {
                 String naiSuffix = getNaiSuffix();
                 if (!TextUtils.isEmpty(naiSuffix)) {
diff --git a/src/com/android/mms/service/MmsConfigManager.java b/src/com/android/mms/service/MmsConfigManager.java
index 807c29a..de1522c 100644
--- a/src/com/android/mms/service/MmsConfigManager.java
+++ b/src/com/android/mms/service/MmsConfigManager.java
@@ -21,9 +21,9 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.telephony.SubInfoRecord;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.text.TextUtils;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -31,7 +31,6 @@
 import java.util.Map;
 
 import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.TelephonyIntents;
 
 /**
  * This class manages cached copies of all the MMS configuration for each subscription ID.
@@ -49,8 +48,9 @@
     }
 
     // Map the various subIds to their corresponding MmsConfigs.
-    private final Map<Long, MmsConfig> mSubIdConfigMap = new ArrayMap<Long, MmsConfig>();
+    private final Map<Integer, MmsConfig> mSubIdConfigMap = new ArrayMap<Integer, MmsConfig>();
     private Context mContext;
+    private SubscriptionManager mSubscriptionManager;
 
     /**
      * This receiver listens for changes made to SubInfoRecords and for a broadcast telling us
@@ -63,27 +63,39 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
             Log.i(TAG, "mReceiver action: " + action);
-            if (action.equals(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED) ||
-                    action.equals(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE) ||
-                    action.equals(IccCardConstants.INTENT_VALUE_ICC_LOADED)) {
+            if (action.equals(IccCardConstants.INTENT_VALUE_ICC_LOADED)) {
                 loadInBackground();
             }
         }
     };
 
+    private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
+            new OnSubscriptionsChangedListener() {
+        @Override
+        public void onSubscriptionsChanged() {
+            loadInBackground();
+        }
+    };
+
+
     public void init(final Context context) {
-        IntentFilter intentFilter =
-                new IntentFilter(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
-        context.registerReceiver(mReceiver, intentFilter);
-        IntentFilter intentFilterChange =
-                new IntentFilter(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
-        context.registerReceiver(mReceiver, intentFilterChange);
+        mContext = context;
+        mSubscriptionManager = SubscriptionManager.from(context);
+
+        // TODO: When this object "finishes" we should unregister.
         IntentFilter intentFilterLoaded =
                 new IntentFilter(IccCardConstants.INTENT_VALUE_ICC_LOADED);
         context.registerReceiver(mReceiver, intentFilterLoaded);
 
-        mContext = context;
-        loadInBackground();
+        // TODO: When this object "finishes" we should unregister by invoking
+        // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener);
+        // This is not strictly necessary because it will be unregistered if the
+        // notification fails but it is good form.
+
+        // Register for SubscriptionInfo list changes which is guaranteed
+        // to invoke onSubscriptionsChanged the first time.
+        SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
+                mOnSubscriptionsChangedListener);
     }
 
     private void loadInBackground() {
@@ -108,9 +120,9 @@
      * @return MmsConfig for the particular subscription id. This function can return null if
      *         the MmsConfig cannot be found or if this function is called before the
      *         TelephonyManager has setup the SIMs or if loadInBackground is still spawning a
-     *         thread after a recent ACTION_SUBINFO_RECORD_UPDATED event.
+     *         thread after a recent LISTEN_SUBSCRIPTION_INFO_LIST_CHANGED event.
      */
-    public MmsConfig getMmsConfigBySubId(long subId) {
+    public MmsConfig getMmsConfigBySubId(int subId) {
         MmsConfig mmsConfig;
         synchronized(mSubIdConfigMap) {
             mmsConfig = mSubIdConfigMap.get(subId);
@@ -126,17 +138,17 @@
      *
      */
     private void load(Context context) {
-        List<SubInfoRecord> subs = SubscriptionManager.getActiveSubInfoList();
+        List<SubscriptionInfo> subs = mSubscriptionManager.getActiveSubscriptionInfoList();
         if (subs == null || subs.size() < 1) {
             Log.e(TAG, "MmsConfigManager.load -- empty getActiveSubInfoList");
             return;
         }
         // Load all the mms_config.xml files in a separate map and then swap with the
         // real map at the end so we don't block anyone sync'd on the real map.
-        final Map<Long, MmsConfig> newConfigMap = new ArrayMap<Long, MmsConfig>();
-        for (SubInfoRecord sub : subs) {
+        final Map<Integer, MmsConfig> newConfigMap = new ArrayMap<Integer, MmsConfig>();
+        for (SubscriptionInfo sub : subs) {
             Configuration configuration = new Configuration();
-            if (sub.mcc == 0 && sub.mnc == 0) {
+            if (sub.getMcc() == 0 && sub.getMnc() == 0) {
                 Configuration config = mContext.getResources().getConfiguration();
                 configuration.mcc = config.mcc;
                 configuration.mnc = config.mnc;
@@ -146,12 +158,13 @@
             } else {
                 Log.i(TAG, "MmsConfigManager.load -- mcc/mnc for sub: " + sub);
 
-                configuration.mcc = sub.mcc;
-                configuration.mnc = sub.mnc;
+                configuration.mcc = sub.getMcc();
+                configuration.mnc = sub.getMnc();
             }
             Context subContext = context.createConfigurationContext(configuration);
 
-            newConfigMap.put(sub.subId, new MmsConfig(subContext, sub.subId));
+            int subId = sub.getSubscriptionId();
+            newConfigMap.put(subId, new MmsConfig(subContext, subId));
         }
         synchronized(mSubIdConfigMap) {
             mSubIdConfigMap.clear();
diff --git a/src/com/android/mms/service/MmsHttpClient.java b/src/com/android/mms/service/MmsHttpClient.java
new file mode 100644
index 0000000..281d9b6
--- /dev/null
+++ b/src/com/android/mms/service/MmsHttpClient.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2014 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.mms.service;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.mms.service.exception.MmsHttpException;
+import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.HostResolver;
+import com.android.okhttp.HttpHandler;
+import com.android.okhttp.HttpsHandler;
+import com.android.okhttp.OkHttpClient;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.URL;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.net.SocketFactory;
+
+/**
+ * MMS HTTP client for sending and downloading MMS messages
+ */
+public class MmsHttpClient {
+    public static final String METHOD_POST = "POST";
+    public static final String METHOD_GET = "GET";
+
+    private static final String HEADER_CONTENT_TYPE = "Content-Type";
+    private static final String HEADER_ACCEPT = "Accept";
+    private static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language";
+    private static final String HEADER_USER_AGENT = "User-Agent";
+
+    // The "Accept" header value
+    private static final String HEADER_VALUE_ACCEPT =
+            "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic";
+    // The "Content-Type" header value
+    private static final String HEADER_VALUE_CONTENT_TYPE_WITH_CHARSET =
+            "application/vnd.wap.mms-message; charset=utf-8";
+    private static final String HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET =
+            "application/vnd.wap.mms-message";
+
+    private final Context mContext;
+    private final SocketFactory mSocketFactory;
+    private final HostResolver mHostResolver;
+    private final ConnectionPool mConnectionPool;
+
+    /**
+     * Constructor
+     *
+     * @param context The Context object
+     * @param socketFactory The socket factory for creating an OKHttp client
+     * @param hostResolver The host name resolver for creating an OKHttp client
+     * @param connectionPool The connection pool for creating an OKHttp client
+     */
+    public MmsHttpClient(Context context, SocketFactory socketFactory, HostResolver hostResolver,
+            ConnectionPool connectionPool) {
+        mContext = context;
+        mSocketFactory = socketFactory;
+        mHostResolver = hostResolver;
+        mConnectionPool = connectionPool;
+    }
+
+    /**
+     * Execute an MMS HTTP request, either a POST (sending) or a GET (downloading)
+     *
+     * @param urlString The request URL, for sending it is usually the MMSC, and for downloading
+     *                  it is the message URL
+     * @param pdu For POST (sending) only, the PDU to send
+     * @param method HTTP method, POST for sending and GET for downloading
+     * @param isProxySet Is there a proxy for the MMSC
+     * @param proxyHost The proxy host
+     * @param proxyPort The proxy port
+     * @param mmsConfig The MMS config to use
+     * @return The HTTP response body
+     * @throws MmsHttpException For any failures
+     */
+    public byte[] execute(String urlString, byte[] pdu, String method, boolean isProxySet,
+            String proxyHost, int proxyPort, MmsConfig.Overridden mmsConfig)
+            throws MmsHttpException {
+        Log.d(MmsService.TAG, "HTTP: " + method + " " + urlString
+                + (isProxySet ? (", proxy=" + proxyHost + ":" + proxyPort) : "")
+                + ", PDU size=" + (pdu != null ? pdu.length : 0));
+        checkMethod(method);
+        HttpURLConnection connection = null;
+        try {
+            Proxy proxy = null;
+            if (isProxySet) {
+                proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
+            }
+            final URL url = new URL(urlString);
+            // Now get the connection
+            connection = openConnection(url, proxy);
+            connection.setDoInput(true);
+            connection.setConnectTimeout(mmsConfig.getHttpSocketTimeout());
+            // ------- COMMON HEADERS ---------
+            // Header: Accept
+            connection.setRequestProperty(HEADER_ACCEPT, HEADER_VALUE_ACCEPT);
+            // Header: Accept-Language
+            connection.setRequestProperty(
+                    HEADER_ACCEPT_LANGUAGE, getCurrentAcceptLanguage(Locale.getDefault()));
+            // Header: User-Agent
+            final String userAgent = mmsConfig.getUserAgent();
+            Log.i(MmsService.TAG, "HTTP: User-Agent=" + userAgent);
+            connection.setRequestProperty(HEADER_USER_AGENT, userAgent);
+            // Header: x-wap-profile
+            final String uaProfUrlTagName = mmsConfig.getUaProfTagName();
+            final String uaProfUrl = mmsConfig.getUaProfUrl();
+            if (uaProfUrl != null) {
+                Log.i(MmsService.TAG, "HTTP: UaProfUrl=" + uaProfUrl);
+                connection.setRequestProperty(uaProfUrlTagName, uaProfUrl);
+            }
+            // Add extra headers specified by mms_config.xml's httpparams
+            addExtraHeaders(connection, mmsConfig);
+            // Different stuff for GET and POST
+            if (METHOD_POST.equals(method)) {
+                if (pdu == null || pdu.length < 1) {
+                    Log.e(MmsService.TAG, "HTTP: empty pdu");
+                    throw new MmsHttpException(0/*statusCode*/, "Sending empty PDU");
+                }
+                connection.setDoOutput(true);
+                connection.setRequestMethod(METHOD_POST);
+                if (mmsConfig.getSupportHttpCharsetHeader()) {
+                    connection.setRequestProperty(HEADER_CONTENT_TYPE,
+                            HEADER_VALUE_CONTENT_TYPE_WITH_CHARSET);
+                } else {
+                    connection.setRequestProperty(HEADER_CONTENT_TYPE,
+                            HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET);
+                }
+                if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) {
+                    logHttpHeaders(connection.getRequestProperties());
+                }
+                connection.setFixedLengthStreamingMode(pdu.length);
+                // Sending request body
+                final OutputStream out =
+                        new BufferedOutputStream(connection.getOutputStream());
+                out.write(pdu);
+                out.flush();
+                out.close();
+            } else if (METHOD_GET.equals(method)) {
+                if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) {
+                    logHttpHeaders(connection.getRequestProperties());
+                }
+                connection.setRequestMethod(METHOD_GET);
+            }
+            // Get response
+            final int responseCode = connection.getResponseCode();
+            final String responseMessage = connection.getResponseMessage();
+            Log.d(MmsService.TAG, "HTTP: " + responseCode + " " + responseMessage);
+            if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) {
+                logHttpHeaders(connection.getHeaderFields());
+            }
+            if (responseCode / 100 != 2) {
+                throw new MmsHttpException(responseCode, responseMessage);
+            }
+            final InputStream in = new BufferedInputStream(connection.getInputStream());
+            final ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+            final byte[] buf = new byte[4096];
+            int count = 0;
+            while ((count = in.read(buf)) > 0) {
+                byteOut.write(buf, 0, count);
+            }
+            in.close();
+            final byte[] responseBody = byteOut.toByteArray();
+            Log.d(MmsService.TAG, "HTTP: response size="
+                    + (responseBody != null ? responseBody.length : 0));
+            return responseBody;
+        } catch (MalformedURLException e) {
+            Log.e(MmsService.TAG, "HTTP: invalid URL " + urlString, e);
+            throw new MmsHttpException(0/*statusCode*/, "Invalid URL " + urlString, e);
+        } catch (ProtocolException e) {
+            Log.e(MmsService.TAG, "HTTP: invalid URL protocol " + urlString, e);
+            throw new MmsHttpException(0/*statusCode*/, "Invalid URL protocol " + urlString, e);
+        } catch (IOException e) {
+            Log.e(MmsService.TAG, "HTTP: IO failure", e);
+            throw new MmsHttpException(0/*statusCode*/, e);
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * Open an HTTP connection
+     *
+     * TODO: The following code is borrowed from android.net.Network.openConnection
+     * Once that method supports proxy, we should use that instead
+     * Also we should remove the associated HostResolver and ConnectionPool from
+     * MmsNetworkManager
+     *
+     * @param url The URL to connect to
+     * @param proxy The proxy to use
+     * @return The opened HttpURLConnection
+     * @throws MalformedURLException If URL is malformed
+     */
+    private HttpURLConnection openConnection(URL url, Proxy proxy) throws MalformedURLException {
+        final String protocol = url.getProtocol();
+        OkHttpClient okHttpClient;
+        if (protocol.equals("http")) {
+            okHttpClient = HttpHandler.createHttpOkHttpClient(proxy);
+        } else if (protocol.equals("https")) {
+            okHttpClient = HttpsHandler.createHttpsOkHttpClient(proxy);
+        } else {
+            throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);
+        }
+        return okHttpClient.setSocketFactory(mSocketFactory)
+                .setHostResolver(mHostResolver)
+                .setConnectionPool(mConnectionPool)
+                .open(url);
+    }
+
+    private static void logHttpHeaders(Map<String, List<String>> headers) {
+        final StringBuilder sb = new StringBuilder();
+        if (headers != null) {
+            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+                final String key = entry.getKey();
+                final List<String> values = entry.getValue();
+                if (values != null) {
+                    for (String value : values) {
+                        sb.append(key).append('=').append(value).append('\n');
+                    }
+                }
+            }
+            Log.v(MmsService.TAG, "HTTP: headers\n" + sb.toString());
+        }
+    }
+
+    private static void checkMethod(String method) throws MmsHttpException {
+        if (!METHOD_GET.equals(method) && !METHOD_POST.equals(method)) {
+            throw new MmsHttpException(0/*statusCode*/, "Invalid method " + method);
+        }
+    }
+
+    private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
+
+    /**
+     * Return the Accept-Language header.  Use the current locale plus
+     * US if we are in a different locale than US.
+     * This code copied from the browser's WebSettings.java
+     *
+     * @return Current AcceptLanguage String.
+     */
+    public static String getCurrentAcceptLanguage(Locale locale) {
+        final StringBuilder buffer = new StringBuilder();
+        addLocaleToHttpAcceptLanguage(buffer, locale);
+
+        if (!Locale.US.equals(locale)) {
+            if (buffer.length() > 0) {
+                buffer.append(", ");
+            }
+            buffer.append(ACCEPT_LANG_FOR_US_LOCALE);
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish,
+     * to new standard.
+     */
+    private static String convertObsoleteLanguageCodeToNew(String langCode) {
+        if (langCode == null) {
+            return null;
+        }
+        if ("iw".equals(langCode)) {
+            // Hebrew
+            return "he";
+        } else if ("in".equals(langCode)) {
+            // Indonesian
+            return "id";
+        } else if ("ji".equals(langCode)) {
+            // Yiddish
+            return "yi";
+        }
+        return langCode;
+    }
+
+    private static void addLocaleToHttpAcceptLanguage(StringBuilder builder, Locale locale) {
+        final String language = convertObsoleteLanguageCodeToNew(locale.getLanguage());
+        if (language != null) {
+            builder.append(language);
+            final String country = locale.getCountry();
+            if (country != null) {
+                builder.append("-");
+                builder.append(country);
+            }
+        }
+    }
+
+    private static final Pattern MACRO_P = Pattern.compile("##(\\S+)##");
+    /**
+     * Resolve the macro in HTTP param value text
+     * For example, "something##LINE1##something" is resolved to "something9139531419something"
+     *
+     * @param value The HTTP param value possibly containing macros
+     * @return The HTTP param with macro resolved to real value
+     */
+    private static String resolveMacro(Context context, String value,
+            MmsConfig.Overridden mmsConfig) {
+        if (TextUtils.isEmpty(value)) {
+            return value;
+        }
+        final Matcher matcher = MACRO_P.matcher(value);
+        int nextStart = 0;
+        StringBuilder replaced = null;
+        while (matcher.find()) {
+            if (replaced == null) {
+                replaced = new StringBuilder();
+            }
+            final int matchedStart = matcher.start();
+            if (matchedStart > nextStart) {
+                replaced.append(value.substring(nextStart, matchedStart));
+            }
+            final String macro = matcher.group(1);
+            final String macroValue = mmsConfig.getHttpParamMacro(context, macro);
+            if (macroValue != null) {
+                replaced.append(macroValue);
+            } else {
+                Log.w(MmsService.TAG, "HTTP: invalid macro " + macro);
+            }
+            nextStart = matcher.end();
+        }
+        if (replaced != null && nextStart < value.length()) {
+            replaced.append(value.substring(nextStart));
+        }
+        return replaced == null ? value : replaced.toString();
+    }
+
+    /**
+     * Add extra HTTP headers from mms_config.xml's httpParams, which is a list of key/value
+     * pairs separated by "|". Each key/value pair is separated by ":". Value may contain
+     * macros like "##LINE1##" or "##NAI##" which is resolved with methods in this class
+     *
+     * @param connection The HttpURLConnection that we add headers to
+     * @param mmsConfig The MmsConfig object
+     */
+    private void addExtraHeaders(HttpURLConnection connection, MmsConfig.Overridden mmsConfig) {
+        final String extraHttpParams = mmsConfig.getHttpParams();
+        if (!TextUtils.isEmpty(extraHttpParams)) {
+            // Parse the parameter list
+            String paramList[] = extraHttpParams.split("\\|");
+            for (String paramPair : paramList) {
+                String splitPair[] = paramPair.split(":", 2);
+                if (splitPair.length == 2) {
+                    final String name = splitPair[0].trim();
+                    final String value = resolveMacro(mContext, splitPair[1].trim(), mmsConfig);
+                    if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value)) {
+                        // Add the header if the param is valid
+                        connection.setRequestProperty(name, value);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/mms/service/MmsNetworkManager.java b/src/com/android/mms/service/MmsNetworkManager.java
index cefafc7..2f61232 100644
--- a/src/com/android/mms/service/MmsNetworkManager.java
+++ b/src/com/android/mms/service/MmsNetworkManager.java
@@ -16,25 +16,27 @@
 
 package com.android.mms.service;
 
-import com.android.mms.service.exception.MmsNetworkException;
-import com.android.mms.service.http.NameResolver;
-
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.NetworkInfo;
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.mms.service.exception.MmsNetworkException;
+import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.HostResolver;
+
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
 /**
  * Manages the MMS network connectivity
  */
-public class MmsNetworkManager implements NameResolver {
+public class MmsNetworkManager implements HostResolver {
     // Timeout used to call ConnectivityManager.requestNetwork
     private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
     // Wait timeout for this class, a little bit longer than the above timeout
@@ -42,7 +44,16 @@
     private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS =
             NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000);
 
-    private Context mContext;
+    // Borrowed from {@link android.net.Network}
+    private static final boolean httpKeepAlive =
+            Boolean.parseBoolean(System.getProperty("http.keepAlive", "true"));
+    private static final int httpMaxConnections =
+            httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0;
+    private static final long httpKeepAliveDurationMs =
+            Long.parseLong(System.getProperty("http.keepAliveDuration", "300000"));  // 5 minutes.
+
+    private final Context mContext;
+
     // The requested MMS {@link android.net.Network} we are holding
     // We need this when we unbind from it. This is also used to indicate if the
     // MMS network is available.
@@ -50,31 +61,36 @@
     // The current count of MMS requests that require the MMS network
     // If mMmsRequestCount is 0, we should release the MMS network.
     private int mMmsRequestCount;
-
     // This is really just for using the capability
-    private NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
-            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-            .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-            .build();
-
+    private final NetworkRequest mNetworkRequest;
     // The callback to register when we request MMS network
     private ConnectivityManager.NetworkCallback mNetworkCallback;
 
-    private ConnectivityManager mConnectivityManager;
+    private volatile ConnectivityManager mConnectivityManager;
 
-    // TODO: we need to re-architect this when we support MSIM, like maybe one manager for each SIM?
-    public MmsNetworkManager(Context context) {
+    // The OkHttp's ConnectionPool used by the HTTP client associated with this network manager
+    private ConnectionPool mConnectionPool;
+
+    // The MMS HTTP client for this network
+    private MmsHttpClient mMmsHttpClient;
+
+    // The SIM ID which we use to connect
+    private final int mSubId;
+
+    public MmsNetworkManager(Context context, int subId) {
         mContext = context;
         mNetworkCallback = null;
         mNetwork = null;
         mMmsRequestCount = 0;
         mConnectivityManager = null;
-    }
-
-    public Network getNetwork() {
-        synchronized (this) {
-            return mNetwork;
-        }
+        mConnectionPool = null;
+        mMmsHttpClient = null;
+        mSubId = subId;
+        mNetworkRequest = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
+                .setNetworkSpecifier(Integer.toString(mSubId))
+                .build();
     }
 
     /**
@@ -83,10 +99,6 @@
      * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
      */
     public void acquireNetwork() throws MmsNetworkException {
-        if (inAirplaneMode()) {
-            // Fast fail airplane mode
-            throw new MmsNetworkException("In airplane mode");
-        }
         synchronized (this) {
             mMmsRequestCount += 1;
             if (mNetwork != null) {
@@ -114,8 +126,7 @@
             }
             // Timed out, so release the request and fail
             Log.d(MmsService.TAG, "MmsNetworkManager: timed out");
-            releaseRequest(mNetworkCallback);
-            resetLocked();
+            releaseRequestLocked(mNetworkCallback);
             throw new MmsNetworkException("Acquiring network timed out");
         }
     }
@@ -129,8 +140,7 @@
                 mMmsRequestCount -= 1;
                 Log.d(MmsService.TAG, "MmsNetworkManager: release, count=" + mMmsRequestCount);
                 if (mMmsRequestCount < 1) {
-                    releaseRequest(mNetworkCallback);
-                    resetLocked();
+                    releaseRequestLocked(mNetworkCallback);
                 }
             }
         }
@@ -157,10 +167,7 @@
                 super.onLost(network);
                 Log.d(MmsService.TAG, "NetworkCallbackListener.onLost: network=" + network);
                 synchronized (MmsNetworkManager.this) {
-                    releaseRequest(this);
-                    if (mNetworkCallback == this) {
-                        resetLocked();
-                    }
+                    releaseRequestLocked(this);
                     MmsNetworkManager.this.notifyAll();
                 }
             }
@@ -170,10 +177,7 @@
                 super.onUnavailable();
                 Log.d(MmsService.TAG, "NetworkCallbackListener.onUnavailable");
                 synchronized (MmsNetworkManager.this) {
-                    releaseRequest(this);
-                    if (mNetworkCallback == this) {
-                        resetLocked();
-                    }
+                    releaseRequestLocked(this);
                     MmsNetworkManager.this.notifyAll();
                 }
             }
@@ -187,11 +191,12 @@
      *
      * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister
      */
-    private void releaseRequest(ConnectivityManager.NetworkCallback callback) {
+    private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) {
         if (callback != null) {
             final ConnectivityManager connectivityManager = getConnectivityManager();
             connectivityManager.unregisterNetworkCallback(callback);
         }
+        resetLocked();
     }
 
     /**
@@ -201,16 +206,25 @@
         mNetworkCallback = null;
         mNetwork = null;
         mMmsRequestCount = 0;
+        // Currently we follow what android.net.Network does with ConnectionPool,
+        // which is per Network object. So if Network changes, we should clear
+        // out the ConnectionPool and thus the MmsHttpClient (since it is linked
+        // to a specific ConnectionPool).
+        mConnectionPool = null;
+        mMmsHttpClient = null;
     }
 
+    private static final InetAddress[] EMPTY_ADDRESS_ARRAY = new InetAddress[0];
     @Override
     public InetAddress[] getAllByName(String host) throws UnknownHostException {
+        Network network = null;
         synchronized (this) {
-            if (mNetwork != null) {
-                return mNetwork.getAllByName(host);
+            if (mNetwork == null) {
+                return EMPTY_ADDRESS_ARRAY;
             }
-            return new InetAddress[0];
+            network = mNetwork;
         }
+        return network.getAllByName(host);
     }
 
     private ConnectivityManager getConnectivityManager() {
@@ -221,8 +235,55 @@
         return mConnectivityManager;
     }
 
-    private boolean inAirplaneMode() {
-        return Settings.System.getInt(mContext.getContentResolver(),
-                Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+    private ConnectionPool getOrCreateConnectionPoolLocked() {
+        if (mConnectionPool == null) {
+            mConnectionPool = new ConnectionPool(httpMaxConnections, httpKeepAliveDurationMs);
+        }
+        return mConnectionPool;
+    }
+
+    /**
+     * Get an MmsHttpClient for the current network
+     *
+     * @return The MmsHttpClient instance
+     */
+    public MmsHttpClient getOrCreateHttpClient() {
+        synchronized (this) {
+            if (mMmsHttpClient == null) {
+                if (mNetwork != null) {
+                    // Create new MmsHttpClient for the current Network
+                    mMmsHttpClient = new MmsHttpClient(
+                            mContext,
+                            mNetwork.getSocketFactory(),
+                            MmsNetworkManager.this,
+                            getOrCreateConnectionPoolLocked());
+                }
+            }
+            return mMmsHttpClient;
+        }
+    }
+
+    /**
+     * Get the APN name for the active network
+     *
+     * @return The APN name if available, otherwise null
+     */
+    public String getApnName() {
+        Network network = null;
+        synchronized (this) {
+            if (mNetwork == null) {
+                Log.d(MmsService.TAG, "MmsNetworkManager: getApnName: network not available");
+                return null;
+            }
+            network = mNetwork;
+        }
+        String apnName = null;
+        final ConnectivityManager connectivityManager = getConnectivityManager();
+        NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network);
+        if (mmsNetworkInfo != null) {
+            apnName = mmsNetworkInfo.getExtraInfo();
+        }
+        Log.d(MmsService.TAG, "MmsNetworkManager: getApnName: " + apnName);
+        return apnName;
     }
 }
diff --git a/src/com/android/mms/service/MmsRequest.java b/src/com/android/mms/service/MmsRequest.java
index d4558b3..00aea57 100644
--- a/src/com/android/mms/service/MmsRequest.java
+++ b/src/com/android/mms/service/MmsRequest.java
@@ -16,57 +16,39 @@
 
 package com.android.mms.service;
 
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.service.carrier.CarrierMessagingService;
+import android.service.carrier.ICarrierMessagingCallback;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
 import com.android.mms.service.exception.ApnException;
 import com.android.mms.service.exception.MmsHttpException;
 import com.android.mms.service.exception.MmsNetworkException;
 
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Telephony;
-import android.telephony.SmsManager;
-import android.util.Log;
-
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Base class for MMS requests. This has the common logic of sending/downloading MMS.
  */
 public abstract class MmsRequest {
     private static final int RETRY_TIMES = 3;
 
-    protected static final String EXTRA_MESSAGE_REF = "messageref";
-
     /**
      * Interface for certain functionalities from MmsService
      */
     public static interface RequestManager {
         /**
-         * Add a request to pending queue when it is executed by carrier app
-         *
-         * @param key The message ref key from carrier app
-         * @param request The request in pending
-         */
-        public void addPending(int key, MmsRequest request);
-
-        /**
-         * Enqueue an MMS request for running
+         * Enqueue an MMS request
          *
          * @param request the request to enqueue
          */
-        public void addRunning(MmsRequest request);
+        public void addSimRequest(MmsRequest request);
 
         /*
          * @return Whether to auto persist received MMS
@@ -90,12 +72,10 @@
         public boolean writePduToContentUri(final Uri contentUri, final byte[] pdu);
     }
 
-    // The URI of persisted message
-    protected Uri mMessageUri;
     // The reference to the pending requests manager (i.e. the MmsService)
     protected RequestManager mRequestManager;
     // The SIM id
-    protected long mSubId;
+    protected int mSubId;
     // The creator app
     protected String mCreator;
     // MMS config
@@ -103,49 +83,19 @@
     // MMS config overrides
     protected Bundle mMmsConfigOverrides;
 
-    // Intent result receiver for carrier app
-    protected final BroadcastReceiver mCarrierAppResultReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (action.equals(Telephony.Mms.Intents.MMS_SEND_ACTION) ||
-                    action.equals(Telephony.Mms.Intents.MMS_DOWNLOAD_ACTION)) {
-                Log.d(MmsService.TAG, "Carrier app result for " + action);
-                final int rc = getResultCode();
-                if (rc == Activity.RESULT_OK) {
-                    // Handled by carrier app, waiting for result
-                    Log.d(MmsService.TAG, "Sending/downloading MMS by IP pending.");
-                    final Bundle resultExtras = getResultExtras(false);
-                    if (resultExtras != null && resultExtras.containsKey(EXTRA_MESSAGE_REF)) {
-                        final int ref = resultExtras.getInt(EXTRA_MESSAGE_REF);
-                        Log.d(MmsService.TAG, "messageref = " + ref);
-                        mRequestManager.addPending(ref, MmsRequest.this);
-                    } else {
-                        // Bad, no message ref provided
-                        Log.e(MmsService.TAG, "Can't find messageref in result extras.");
-                    }
-                } else {
-                    // No carrier app present, sending normally
-                    Log.d(MmsService.TAG, "Sending/downloading MMS by IP failed.");
-                    mRequestManager.addRunning(MmsRequest.this);
-                }
-            } else {
-                Log.e(MmsService.TAG, "unexpected BroadcastReceiver action: " + action);
-            }
-
-        }
-    };
-
-    public MmsRequest(RequestManager requestManager, Uri messageUri, long subId,
-            String creator, Bundle configOverrides) {
+    public MmsRequest(RequestManager requestManager, int subId, String creator,
+            Bundle configOverrides) {
         mRequestManager = requestManager;
-        mMessageUri = messageUri;
         mSubId = subId;
         mCreator = creator;
         mMmsConfigOverrides = configOverrides;
         mMmsConfig = null;
     }
 
+    public int getSubId() {
+        return mSubId;
+    }
+
     private boolean ensureMmsConfigLoaded() {
         if (mMmsConfig == null) {
             // Not yet retrieved from mms config manager. Try getting it.
@@ -157,6 +107,21 @@
         return mMmsConfig != null;
     }
 
+    private static boolean inAirplaneMode(final Context context) {
+        return Settings.System.getInt(context.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+    }
+
+    private static boolean isMobileDataEnabled(final Context context, final int subId) {
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        return telephonyManager.getDataEnabled(subId);
+    }
+
+    private static boolean isDataNetworkAvailable(final Context context, final int subId) {
+        return !inAirplaneMode(context) && isMobileDataEnabled(context, subId);
+    }
+
     /**
      * Execute the request
      *
@@ -165,6 +130,7 @@
      */
     public void execute(Context context, MmsNetworkManager networkManager) {
         int result = SmsManager.MMS_ERROR_UNSPECIFIED;
+        int httpStatusCode = 0;
         byte[] response = null;
         if (!ensureMmsConfigLoaded()) { // Check mms config
             Log.e(MmsService.TAG, "MmsRequest: mms config is not loaded yet");
@@ -172,14 +138,31 @@
         } else if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user
             Log.e(MmsService.TAG, "MmsRequest: failed to prepare for request");
             result = SmsManager.MMS_ERROR_IO_ERROR;
+        } else if (!isDataNetworkAvailable(context, mSubId)) {
+            Log.e(MmsService.TAG, "MmsRequest: in airplane mode or mobile data disabled");
+            result = SmsManager.MMS_ERROR_NO_DATA_NETWORK;
         } else { // Execute
             long retryDelaySecs = 2;
             // Try multiple times of MMS HTTP request
             for (int i = 0; i < RETRY_TIMES; i++) {
                 try {
                     networkManager.acquireNetwork();
+                    final String apnName = networkManager.getApnName();
                     try {
-                        final ApnSettings apn = ApnSettings.load(context, null/*apnName*/, mSubId);
+                        ApnSettings apn = null;
+                        try {
+                            apn = ApnSettings.load(context, apnName, mSubId);
+                        } catch (ApnException e) {
+                            // If no APN could be found, fall back to trying without the APN name
+                            if (apnName == null) {
+                                // If the APN name was already null then don't need to retry
+                                throw (e);
+                            }
+                            Log.i(MmsService.TAG, "MmsRequest: No match with APN name:"
+                                    + apnName + ", try with no name");
+                            apn = ApnSettings.load(context, null, mSubId);
+                        }
+                        Log.i(MmsService.TAG, "MmsRequest: using " + apn.toString());
                         response = doHttp(context, networkManager, apn);
                         result = Activity.RESULT_OK;
                         // Success
@@ -198,6 +181,7 @@
                 } catch (MmsHttpException e) {
                     Log.e(MmsService.TAG, "MmsRequest: HTTP or network I/O failure", e);
                     result = SmsManager.MMS_ERROR_HTTP_FAILURE;
+                    httpStatusCode = e.getStatusCode();
                     // Retry
                 } catch (Exception e) {
                     Log.e(MmsService.TAG, "MmsRequest: unexpected failure", e);
@@ -210,174 +194,19 @@
                 retryDelaySecs <<= 1;
             }
         }
-        processResult(context, result, response);
-    }
-
-    /**
-     * Try running MMS HTTP request for all the addresses that we can resolve to
-     *
-     * @param context The context
-     * @param netMgr The {@link com.android.mms.service.MmsNetworkManager}
-     * @param url The HTTP URL
-     * @param pdu The PDU to send
-     * @param method The HTTP method to use
-     * @param apn The APN setting to use
-     * @return The response data
-     * @throws MmsHttpException If there is any HTTP/network failure
-     */
-    protected byte[] doHttpForResolvedAddresses(Context context, MmsNetworkManager netMgr,
-            String url, byte[] pdu, int method, ApnSettings apn) throws MmsHttpException {
-        MmsHttpException lastException = null;
-        final ConnectivityManager connMgr =
-                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        // Do HTTP on all the addresses we can resolve to
-        for (final InetAddress address : resolveDestination(connMgr, netMgr, url, apn)) {
-            try {
-                // TODO: we have to use a deprecated API here because with the new
-                // ConnectivityManager APIs in LMP, we need to either use a bound process
-                // or a bound socket. The former can not be used since we share the
-                // phone process with others. The latter is not supported by any HTTP
-                // library yet. We have to rely on this API to get things work. Once
-                // a multinet aware HTTP lib is ready, we should switch to that and
-                // remove all the unnecessary code.
-                if (!connMgr.requestRouteToHostAddress(
-                        ConnectivityManager.TYPE_MOBILE_MMS, address)) {
-                    throw new MmsHttpException("MmsRequest: can not request a route for host "
-                            + address);
-                }
-                return HttpUtils.httpConnection(
-                        context,
-                        url,
-                        pdu,
-                        method,
-                        apn.isProxySet(),
-                        apn.getProxyAddress(),
-                        apn.getProxyPort(),
-                        netMgr,
-                        address instanceof Inet6Address,
-                        mMmsConfig);
-            } catch (MmsHttpException e) {
-                lastException = e;
-                Log.e(MmsService.TAG, "MmsRequest: failure in trying address " + address, e);
-            }
-        }
-        if (lastException != null) {
-            throw lastException;
-        } else {
-            // Should not reach here
-            throw new MmsHttpException("MmsRequest: unknown failure");
-        }
-    }
-
-    /**
-     * Resolve the name of the host we are about to connect to, which can be the URL host or
-     * the proxy host. We only resolve to the supported address types (IPv4 or IPv6 or both)
-     * based on the MMS network interface's address type, i.e. we only need addresses that
-     * match the link address type.
-     *
-     * @param connMgr The connectivity manager
-     * @param netMgr The current {@link MmsNetworkManager}
-     * @param url The HTTP URL
-     * @param apn The APN setting to use
-     * @return A list of matching resolved addresses
-     * @throws MmsHttpException For any network failure
-     */
-    private static List<InetAddress> resolveDestination(ConnectivityManager connMgr,
-            MmsNetworkManager netMgr, String url, ApnSettings apn) throws MmsHttpException {
-        Log.d(MmsService.TAG, "MmsRequest: resolve url " + url);
-        // Find the real host to connect to
-        String host = null;
-        if (apn.isProxySet()) {
-            host = apn.getProxyAddress();
-        } else {
-            final Uri uri = Uri.parse(url);
-            host = uri.getHost();
-        }
-        // Find out the link address types: ipv4 or ipv6 or both
-        final int addressTypes = getMmsLinkAddressTypes(connMgr, netMgr.getNetwork());
-        Log.d(MmsService.TAG, "MmsRequest: addressTypes=" + addressTypes);
-        // Resolve the host to a list of addresses based on supported address types
-        return resolveHostName(netMgr, host, addressTypes);
-    }
-
-    // Address type masks
-    private static final int ADDRESS_TYPE_IPV4 = 1;
-    private static final int ADDRESS_TYPE_IPV6 = 1 << 1;
-
-    /**
-     * Try to find out if we should use IPv6 or IPv4 for MMS. Basically we check if the MMS
-     * network interface has IPv6 address or not. If so, we will use IPv6. Otherwise, use
-     * IPv4.
-     *
-     * @param connMgr The connectivity manager
-     * @return A bit mask indicating what address types we have
-     */
-    private static int getMmsLinkAddressTypes(ConnectivityManager connMgr, Network network) {
-        int result = 0;
-        // Return none if network is not available
-        if (network == null) {
-            return result;
-        }
-        final LinkProperties linkProperties = connMgr.getLinkProperties(network);
-        if (linkProperties != null) {
-            for (InetAddress addr : linkProperties.getAddresses()) {
-                if (addr instanceof Inet4Address) {
-                    result |= ADDRESS_TYPE_IPV4;
-                } else if (addr instanceof Inet6Address) {
-                    result |= ADDRESS_TYPE_IPV6;
-                }
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Resolve host name to address by specified address types.
-     *
-     * @param netMgr The current {@link MmsNetworkManager}
-     * @param host The host name
-     * @param addressTypes The required address type in a bit mask
-     *  (0x01: IPv4, 0x10: IPv6, 0x11: both)
-     * @return
-     * @throws MmsHttpException
-     */
-    private static List<InetAddress> resolveHostName(MmsNetworkManager netMgr, String host,
-            int addressTypes) throws MmsHttpException {
-        final List<InetAddress> resolved = new ArrayList<InetAddress>();
-        try {
-            if (addressTypes != 0) {
-                for (final InetAddress addr : netMgr.getAllByName(host)) {
-                    if ((addressTypes & ADDRESS_TYPE_IPV6) != 0
-                            && addr instanceof Inet6Address) {
-                        // Should use IPv6 and this is IPv6 address, add it
-                        resolved.add(addr);
-                    } else if ((addressTypes & ADDRESS_TYPE_IPV4) != 0
-                            && addr instanceof Inet4Address) {
-                        // Should use IPv4 and this is IPv4 address, add it
-                        resolved.add(addr);
-                    }
-                }
-            }
-            if (resolved.size() < 1) {
-                throw new MmsHttpException("Failed to resolve " + host
-                        + " for allowed address types: " + addressTypes);
-            }
-            return resolved;
-        } catch (final UnknownHostException e) {
-            throw new MmsHttpException("Failed to resolve " + host, e);
-        }
+        processResult(context, result, response, httpStatusCode);
     }
 
     /**
      * Process the result of the completed request, including updating the message status
      * in database and sending back the result via pending intents.
-     *
-     * @param context The context
+     *  @param context The context
      * @param result The result code of execution
      * @param response The response body
+     * @param httpStatusCode The optional http status code in case of http failure
      */
-    public void processResult(Context context, int result, byte[] response) {
-        updateStatus(context, result, response);
+    public void processResult(Context context, int result, byte[] response, int httpStatusCode) {
+        final Uri messageUri = persistIfRequired(context, result, response);
 
         // Return MMS HTTP request result via PendingIntent
         final PendingIntent pendingIntent = getPendingIntent();
@@ -388,8 +217,11 @@
             if (response != null) {
                 succeeded = transferResponse(fillIn, response);
             }
-            if (mMessageUri != null) {
-                fillIn.putExtra("uri", mMessageUri.toString());
+            if (messageUri != null) {
+                fillIn.putExtra("uri", messageUri.toString());
+            }
+            if (result == SmsManager.MMS_ERROR_HTTP_FAILURE && httpStatusCode != 0) {
+                fillIn.putExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, httpStatusCode);
             }
             try {
                 if (!succeeded) {
@@ -405,6 +237,37 @@
     }
 
     /**
+     * Returns true if sending / downloading using the carrier app has failed and completes the
+     * action using platform API's, otherwise false.
+     */
+    protected boolean maybeFallbackToRegularDelivery(int carrierMessagingAppResult) {
+        if (carrierMessagingAppResult
+                == CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK
+                || carrierMessagingAppResult
+                        == CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK) {
+            Log.d(MmsService.TAG, "Sending/downloading MMS by IP failed.");
+            mRequestManager.addSimRequest(MmsRequest.this);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Converts from {@code carrierMessagingAppResult} to a platform result code.
+     */
+    protected static int toSmsManagerResult(int carrierMessagingAppResult) {
+        switch (carrierMessagingAppResult) {
+            case CarrierMessagingService.SEND_STATUS_OK:
+                return Activity.RESULT_OK;
+            case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
+                return SmsManager.MMS_ERROR_RETRY;
+            default:
+                return SmsManager.MMS_ERROR_UNSPECIFIED;
+        }
+    }
+
+    /**
      * Making the HTTP request to MMSC
      *
      * @param context The context
@@ -422,18 +285,20 @@
     protected abstract PendingIntent getPendingIntent();
 
     /**
-     * @return The running queue should be used by this request
+     * @return The queue should be used by this request, 0 is sending and 1 is downloading
      */
-    protected abstract int getRunningQueue();
+    protected abstract int getQueueType();
 
     /**
-     * Update database status of the message represented by this request
+     * Persist message into telephony if required (i.e. when auto-persisting is on or
+     * the calling app is non-default sms app for sending)
      *
      * @param context The context
      * @param result The result code of execution
      * @param response The response body
+     * @return The persisted URI of the message or null if we don't persist or fail
      */
-    protected abstract void updateStatus(Context context, int result, byte[] response);
+    protected abstract Uri persistIfRequired(Context context, int result, byte[] response);
 
     /**
      * Prepare to make the HTTP request - will download message for sending
@@ -456,4 +321,25 @@
      * @param context The context
      */
     protected abstract void revokeUriPermission(Context context);
+
+    /**
+     * Base class for handling carrier app send / download result.
+     */
+    protected abstract class CarrierMmsActionCallback extends ICarrierMessagingCallback.Stub {
+        @Override
+        public void onSendSmsComplete(int result, int messageRef) {
+            Log.e(MmsService.TAG, "Unexpected onSendSmsComplete call with result: " + result);
+        }
+
+        @Override
+        public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
+            Log.e(MmsService.TAG, "Unexpected onSendMultipartSmsComplete call with result: "
+                  + result);
+        }
+
+        @Override
+        public void onFilterComplete(boolean keepMessage) {
+            Log.e(MmsService.TAG, "Unexpected onFilterComplete call with result: " + keepMessage);
+        }
+    }
 }
diff --git a/src/com/android/mms/service/MmsService.java b/src/com/android/mms/service/MmsService.java
index 3a9a689..09a86a9 100644
--- a/src/com/android/mms/service/MmsService.java
+++ b/src/com/android/mms/service/MmsService.java
@@ -16,22 +16,7 @@
 
 package com.android.mms.service;
 
-import com.google.android.mms.MmsException;
-import com.google.android.mms.pdu.DeliveryInd;
-import com.google.android.mms.pdu.GenericPdu;
-import com.google.android.mms.pdu.NotificationInd;
-import com.google.android.mms.pdu.PduComposer;
-import com.google.android.mms.pdu.PduParser;
-import com.google.android.mms.pdu.PduPersister;
-import com.google.android.mms.pdu.ReadOrigInd;
-import com.google.android.mms.pdu.RetrieveConf;
-import com.google.android.mms.pdu.SendReq;
-import com.google.android.mms.util.SqliteWrapper;
-
-import com.android.internal.telephony.IMms;
-import com.android.internal.telephony.SmsApplication;
-
-import android.app.Activity;
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.app.Service;
 import android.content.ContentResolver;
@@ -40,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.os.Binder;
@@ -49,17 +33,36 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
-import android.os.Message;
 import android.os.RemoteException;
 import android.provider.Telephony;
+import android.service.carrier.CarrierMessagingService;
 import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.telephony.IMms;
+import com.google.android.mms.MmsException;
+import com.google.android.mms.pdu.DeliveryInd;
+import com.google.android.mms.pdu.GenericPdu;
+import com.google.android.mms.pdu.NotificationInd;
+import com.google.android.mms.pdu.PduParser;
+import com.google.android.mms.pdu.PduPersister;
+import com.google.android.mms.pdu.ReadOrigInd;
+import com.google.android.mms.pdu.RetrieveConf;
+import com.google.android.mms.pdu.SendReq;
+import com.google.android.mms.util.SqliteWrapper;
 
 import java.io.IOException;
+import java.util.ArrayDeque;
 import java.util.Arrays;
+import java.util.List;
+import java.util.Queue;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
@@ -86,17 +89,25 @@
     // specific size limit should not be used (as it could be lower on some carriers).
     private static final int MAX_MMS_FILE_SIZE = 8 * 1024 * 1024;
 
-    // Pending requests that are currently executed by carrier app
+    // Pending requests that are waiting for the SIM to be available
+    // If a different SIM is currently used by previous requests, the following
+    // requests will stay in this queue until that SIM finishes its current requests in
+    // RequestQueue.
+    // Requests are not reordered. So, e.g. if current SIM is SIM1, a request for SIM2 will be
+    // blocked in the queue. And a later request for SIM1 will be appended to the queue, ordered
+    // after the request for SIM2, instead of being put into the running queue.
     // TODO: persist this in case MmsService crashes
-    private final ConcurrentHashMap<Integer, MmsRequest> mPendingRequests =
-            new ConcurrentHashMap<Integer, MmsRequest>();
+    private final Queue<MmsRequest> mPendingSimRequestQueue = new ArrayDeque<>();
 
     private final ExecutorService mExecutor = Executors.newCachedThreadPool();
 
-    @Override
-    public void addPending(int key, MmsRequest request) {
-        mPendingRequests.put(key, request);
-    }
+    // A cache of MmsNetworkManager for SIMs
+    private final SparseArray<MmsNetworkManager> mNetworkManagerCache = new SparseArray<>();
+
+    // The current SIM ID for the running requests. Only one SIM can send/download MMS at a time.
+    private int mCurrentSubId;
+    // The current running MmsRequest count.
+    private int mRunningRequestCount;
 
     /**
      * A thread-based request queue for executing the MMS requests in serial order
@@ -110,89 +121,109 @@
         public void handleMessage(Message msg) {
             final MmsRequest request = (MmsRequest) msg.obj;
             if (request != null) {
-                request.execute(MmsService.this, mMmsNetworkManager);
+                try {
+                    request.execute(MmsService.this, getNetworkManager(request.getSubId()));
+                } finally {
+                    synchronized (MmsService.this) {
+                        mRunningRequestCount--;
+                        if (mRunningRequestCount <= 0) {
+                            movePendingSimRequestsToRunningSynchronized();
+                        }
+                    }
+                }
+            } else {
+                Log.e(TAG, "RequestQueue: handling empty request");
             }
         }
     }
 
+    private MmsNetworkManager getNetworkManager(int subId) {
+        synchronized (mNetworkManagerCache) {
+            MmsNetworkManager manager = mNetworkManagerCache.get(subId);
+            if (manager == null) {
+                manager = new MmsNetworkManager(this, subId);
+                mNetworkManagerCache.put(subId, manager);
+            }
+            return manager;
+        }
+    }
+
     private void enforceSystemUid() {
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
             throw new SecurityException("Only system can call this service");
         }
     }
 
+    private int checkSubId(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new RuntimeException("Invalid subId " + subId);
+        }
+        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+            return SubscriptionManager.getDefaultSmsSubId();
+        }
+        return subId;
+    }
+
+    @Nullable
+    private String getCarrierMessagingServicePackageIfExists() {
+        Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE);
+        TelephonyManager telephonyManager =
+                (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
+        List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent(intent);
+
+        if (carrierPackages == null || carrierPackages.size() != 1) {
+            return null;
+        } else {
+            return carrierPackages.get(0);
+        }
+    }
+
     private IMms.Stub mStub = new IMms.Stub() {
         @Override
-        public void sendMessage(long subId, String callingPkg, Uri contentUri,
+        public void sendMessage(int subId, String callingPkg, Uri contentUri,
                 String locationUrl, Bundle configOverrides, PendingIntent sentIntent)
                         throws RemoteException {
             Log.d(TAG, "sendMessage");
             enforceSystemUid();
+            // Make sure the subId is correct
+            subId = checkSubId(subId);
             final SendRequest request = new SendRequest(MmsService.this, subId, contentUri,
-                    null/*messageUri*/, locationUrl, sentIntent, callingPkg, configOverrides);
-            if (SmsApplication.shouldWriteMessageForPackage(callingPkg, MmsService.this)) {
-                // Store the message in outbox first before sending
-                request.storeInOutbox(MmsService.this);
+                    locationUrl, sentIntent, callingPkg, configOverrides);
+
+            final String carrierMessagingServicePackage =
+                    getCarrierMessagingServicePackageIfExists();
+            if (carrierMessagingServicePackage != null) {
+                Log.d(TAG, "sending message by carrier app");
+                request.trySendingByCarrierApp(MmsService.this, carrierMessagingServicePackage);
+            } else {
+                addSimRequest(request);
             }
-            // Try sending via carrier app
-            request.trySendingByCarrierApp(MmsService.this);
         }
 
         @Override
-        public void downloadMessage(long subId, String callingPkg, String locationUrl,
+        public void downloadMessage(int subId, String callingPkg, String locationUrl,
                 Uri contentUri, Bundle configOverrides,
                 PendingIntent downloadedIntent) throws RemoteException {
             Log.d(TAG, "downloadMessage: " + locationUrl);
             enforceSystemUid();
+            // Make sure the subId is correct
+            subId = checkSubId(subId);
             final DownloadRequest request = new DownloadRequest(MmsService.this, subId,
                     locationUrl, contentUri, downloadedIntent, callingPkg, configOverrides);
-            // Try downloading via carrier app
-            request.tryDownloadingByCarrierApp(MmsService.this);
-        }
-
-        @Override
-        public void updateMmsSendStatus(int messageRef, byte[] pdu, int status) {
-            Log.d(TAG, "updateMmsSendStatus: ref=" + messageRef
-                    + ", pdu=" + (pdu == null ? null : pdu.length) + ", status=" + status);
-            enforceSystemUid();
-            final MmsRequest request = mPendingRequests.get(messageRef);
-            if (request != null) {
-                if (status != SmsManager.MMS_ERROR_RETRY) {
-                    // Sent completed (maybe success or fail) by carrier app, finalize the request.
-                    request.processResult(MmsService.this, status, pdu);
-                } else {
-                    // Failed, try sending via carrier network
-                    addRunning(request);
-                }
+            final String carrierMessagingServicePackage =
+                    getCarrierMessagingServicePackageIfExists();
+            if (carrierMessagingServicePackage != null) {
+                Log.d(TAG, "downloading message by carrier app");
+                request.tryDownloadingByCarrierApp(MmsService.this, carrierMessagingServicePackage);
             } else {
-                // Really wrong here: can't find the request to update
-                Log.e(TAG, "Failed to find the request to update send status");
+                addSimRequest(request);
             }
         }
 
-        @Override
-        public void updateMmsDownloadStatus(int messageRef, int status) {
-            Log.d(TAG, "updateMmsDownloadStatus: ref=" + messageRef + ", status=" + status);
-            enforceSystemUid();
-            final MmsRequest request = mPendingRequests.get(messageRef);
-            if (request != null) {
-                if (status != SmsManager.MMS_ERROR_RETRY) {
-                    // Downloaded completed (maybe success or fail) by carrier app, finalize the
-                    // request.
-                    request.processResult(MmsService.this, status, null/*response*/);
-                } else {
-                    // Failed, try downloading via the carrier network
-                    addRunning(request);
-                }
-            } else {
-                // Really wrong here: can't find the request to update
-                Log.e(TAG, "Failed to find the request to update download status");
-            }
-        }
-
-        @Override
-        public Bundle getCarrierConfigValues(long subId) {
+        public Bundle getCarrierConfigValues(int subId) {
             Log.d(TAG, "getCarrierConfigValues");
+            // Make sure the subId is correct
+            subId = checkSubId(subId);
             final MmsConfig mmsConfig = MmsConfigManager.getInstance().getMmsConfigBySubId(subId);
             if (mmsConfig == null) {
                 return new Bundle();
@@ -307,7 +338,7 @@
         }
 
         @Override
-        public void sendStoredMessage(long subId, String callingPkg, Uri messageUri,
+        public void sendStoredMessage(int subId, String callingPkg, Uri messageUri,
                 Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
             throw new UnsupportedOperationException();
         }
@@ -330,13 +361,10 @@
         }
     };
 
-    // Request queue threads
+    // Running request queues, one thread per queue
     // 0: send queue
     // 1: download queue
-    private final RequestQueue[] mRequestQueues = new RequestQueue[2];
-
-    // Manages MMS connectivity related stuff
-    private final MmsNetworkManager mMmsNetworkManager = new MmsNetworkManager(this);
+    private final RequestQueue[] mRunningRequestQueues = new RequestQueue[2];
 
     /**
      * Lazy start the request queue threads
@@ -344,29 +372,81 @@
      * @param queueIndex index of the queue to start
      */
     private void startRequestQueueIfNeeded(int queueIndex) {
-        if (queueIndex < 0 || queueIndex >= mRequestQueues.length) {
+        if (queueIndex < 0 || queueIndex >= mRunningRequestQueues.length) {
+            Log.e(TAG, "Start request queue if needed: invalid queue " + queueIndex);
             return;
         }
         synchronized (this) {
-            if (mRequestQueues[queueIndex] == null) {
+            if (mRunningRequestQueues[queueIndex] == null) {
                 final HandlerThread thread =
                         new HandlerThread("MmsService RequestQueue " + queueIndex);
                 thread.start();
-                mRequestQueues[queueIndex] = new RequestQueue(thread.getLooper());
+                mRunningRequestQueues[queueIndex] = new RequestQueue(thread.getLooper());
             }
         }
     }
 
     @Override
-    public void addRunning(MmsRequest request) {
+    public void addSimRequest(MmsRequest request) {
         if (request == null) {
+            Log.e(TAG, "Add running or pending: empty request");
             return;
         }
-        final int queue = request.getRunningQueue();
+        Log.d(TAG, "Current running=" + mRunningRequestCount + ", "
+                + "current subId=" + mCurrentSubId + ", "
+                + "pending=" + mPendingSimRequestQueue.size());
+        synchronized (this) {
+            if (mPendingSimRequestQueue.size() > 0 ||
+                    (mRunningRequestCount > 0 && request.getSubId() != mCurrentSubId)) {
+                Log.d(TAG, "Add request to pending queue."
+                        + " Request subId=" + request.getSubId() + ","
+                        + " current subId=" + mCurrentSubId);
+                mPendingSimRequestQueue.add(request);
+                if (mRunningRequestCount <= 0) {
+                    Log.e(TAG, "Nothing's running but queue's not empty");
+                    // Nothing is running but we are accumulating on pending queue.
+                    // This should not happen. But just in case...
+                    movePendingSimRequestsToRunningSynchronized();
+                }
+            } else {
+                addToRunningRequestQueueSynchronized(request);
+            }
+        }
+    }
+
+    private void addToRunningRequestQueueSynchronized(MmsRequest request) {
+        Log.d(TAG, "Add request to running queue for subId " + request.getSubId());
+        // Update current state of running requests
+        mCurrentSubId = request.getSubId();
+        mRunningRequestCount++;
+        // Send to the corresponding request queue for execution
+        final int queue = request.getQueueType();
         startRequestQueueIfNeeded(queue);
         final Message message = Message.obtain();
         message.obj = request;
-        mRequestQueues[queue].sendMessage(message);
+        mRunningRequestQueues[queue].sendMessage(message);
+    }
+
+    private void movePendingSimRequestsToRunningSynchronized() {
+        Log.d(TAG, "Schedule requests pending on SIM");
+        mCurrentSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        while (mPendingSimRequestQueue.size() > 0) {
+            final MmsRequest request = mPendingSimRequestQueue.peek();
+            if (request != null) {
+                if (!SubscriptionManager.isValidSubscriptionId(mCurrentSubId)
+                        || mCurrentSubId == request.getSubId()) {
+                    // First or subsequent requests with same SIM ID
+                    mPendingSimRequestQueue.remove();
+                    addToRunningRequestQueueSynchronized(request);
+                } else {
+                    // Stop if we see a different SIM ID
+                    break;
+                }
+            } else {
+                Log.e(TAG, "Schedule pending: found empty request");
+                mPendingSimRequestQueue.remove();
+            }
+        }
     }
 
     @Override
@@ -383,8 +463,12 @@
         super.onCreate();
         Log.d(TAG, "onCreate");
         // Load mms_config
-        // TODO (ywen): make sure we start request queues after mms_config is loaded
         MmsConfigManager.getInstance().init(this);
+        // Initialize running request state
+        synchronized (this) {
+            mCurrentSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+            mRunningRequestCount = 0;
+        }
     }
 
     private Uri importSms(String address, int type, String text, long timestampMillis,
@@ -438,7 +522,7 @@
         // between the calling uid and the package uid
         final long identity = Binder.clearCallingIdentity();
         try {
-            final GenericPdu pdu = (new PduParser(pduData)).parse();
+            final GenericPdu pdu = parsePduForAnyCarrier(pduData);
             if (pdu == null) {
                 Log.e(TAG, "importMessage: can't parse input PDU");
                 return null;
@@ -611,7 +695,7 @@
         // between the calling uid and the package uid
         final long identity = Binder.clearCallingIdentity();
         try {
-            final GenericPdu pdu = (new PduParser(pduData)).parse();
+            final GenericPdu pdu = parsePduForAnyCarrier(pduData);
             if (pdu == null) {
                 Log.e(TAG, "addMmsDraft: can't parse input PDU");
                 return null;
@@ -652,67 +736,29 @@
         return null;
     }
 
-    private boolean isFailedOrDraft(Uri messageUri) {
-        // Clear the calling identity and query the database using the phone user id
-        // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
-        // between the calling uid and the package uid
-        final long identity = Binder.clearCallingIdentity();
-        Cursor cursor = null;
+    /**
+     * Try parsing a PDU without knowing the carrier. This is useful for importing
+     * MMS or storing draft when carrier info is not available
+     *
+     * @param data The PDU data
+     * @return Parsed PDU, null if failed to parse
+     */
+    private static GenericPdu parsePduForAnyCarrier(final byte[] data) {
+        GenericPdu pdu = null;
         try {
-            cursor = getContentResolver().query(
-                    messageUri,
-                    new String[]{ Telephony.Mms.MESSAGE_BOX },
-                    null/*selection*/,
-                    null/*selectionArgs*/,
-                    null/*sortOrder*/);
-            if (cursor != null && cursor.moveToFirst()) {
-                final int box = cursor.getInt(0);
-                return box == Telephony.Mms.MESSAGE_BOX_DRAFTS
-                        || box == Telephony.Mms.MESSAGE_BOX_FAILED;
-            }
-        } catch (SQLiteException e) {
-            Log.e(TAG, "isFailedOrDraft: query message type failed", e);
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-            Binder.restoreCallingIdentity(identity);
-        }
-        return false;
-    }
-
-    private byte[] loadPdu(Uri messageUri) {
-        // Clear the calling identity and query the database using the phone user id
-        // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
-        // between the calling uid and the package uid
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final PduPersister persister = PduPersister.getPduPersister(this);
-            final GenericPdu pdu = persister.load(messageUri);
-            if (pdu == null) {
-                Log.e(TAG, "loadPdu: failed to load PDU from " + messageUri.toString());
-                return null;
-            }
-            final PduComposer composer = new PduComposer(this, pdu);
-            return composer.make();
-        } catch (MmsException e) {
-            Log.e(TAG, "loadPdu: failed to load PDU from " + messageUri.toString(), e);
+            pdu = (new PduParser(data, true/*parseContentDisposition*/)).parse();
         } catch (RuntimeException e) {
-            Log.e(TAG, "loadPdu: failed to serialize PDU", e);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+            Log.d(TAG, "parsePduForAnyCarrier: Failed to parse PDU with content disposition", e);
         }
-        return null;
-    }
-
-    private void returnUnspecifiedFailure(PendingIntent pi) {
-        if (pi != null) {
+        if (pdu == null) {
             try {
-                pi.send(SmsManager.MMS_ERROR_UNSPECIFIED);
-            } catch (PendingIntent.CanceledException e) {
-                // ignore
+                pdu = (new PduParser(data, false/*parseContentDisposition*/)).parse();
+            } catch (RuntimeException e) {
+                Log.d(TAG, "parsePduForAnyCarrier: Failed to parse PDU without content disposition",
+                        e);
             }
         }
+        return pdu;
     }
 
     @Override
diff --git a/src/com/android/mms/service/PhoneUtils.java b/src/com/android/mms/service/PhoneUtils.java
new file mode 100644
index 0000000..fc1ff4b
--- /dev/null
+++ b/src/com/android/mms/service/PhoneUtils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 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.mms.service;
+
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.Phonenumber;
+
+import java.util.Locale;
+
+/**
+ * Utility to handle phone numbers.
+ */
+public class PhoneUtils {
+
+    /**
+     * Get a canonical national format phone number. If parsing fails, just return the
+     * original number.
+     *
+     * @param telephonyManager
+     * @param subId The SIM ID associated with this number
+     * @param phoneText The input phone number text
+     * @return The formatted number or the original phone number if failed to parse
+     */
+    public static String getNationalNumber(TelephonyManager telephonyManager, int subId,
+            String phoneText) {
+        final String country = getSimOrDefaultLocaleCountry(telephonyManager, subId);
+        final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+        final Phonenumber.PhoneNumber parsed = getParsedNumber(phoneNumberUtil, phoneText, country);
+        if (parsed == null) {
+            return phoneText;
+        }
+        return phoneNumberUtil
+                .format(parsed, PhoneNumberUtil.PhoneNumberFormat.NATIONAL)
+                .replaceAll("\\D", "");
+    }
+
+    // Parse the input number into internal format
+    private static Phonenumber.PhoneNumber getParsedNumber(PhoneNumberUtil phoneNumberUtil,
+            String phoneText, String country) {
+        try {
+            final Phonenumber.PhoneNumber phoneNumber = phoneNumberUtil.parse(phoneText, country);
+            if (phoneNumberUtil.isValidNumber(phoneNumber)) {
+                return phoneNumber;
+            } else {
+                Log.e(MmsService.TAG, "getParsedNumber: not a valid phone number " + phoneText
+                        + " for country " + country);
+                return null;
+            }
+        } catch (final NumberParseException e) {
+            Log.e(MmsService.TAG, "getParsedNumber: Not able to parse phone number " + phoneText);
+            return null;
+        }
+    }
+
+    // Get the country/region either from the SIM ID or from locale
+    private static String getSimOrDefaultLocaleCountry(TelephonyManager telephonyManager,
+            int subId) {
+        String country = getSimCountry(telephonyManager, subId);
+        if (TextUtils.isEmpty(country)) {
+            country = Locale.getDefault().getCountry();
+        }
+
+        return country;
+    }
+
+    // Get country/region from SIM ID
+    private static String getSimCountry(TelephonyManager telephonyManager, int subId) {
+        final String country = telephonyManager.getSimCountryIso(subId);
+        if (TextUtils.isEmpty(country)) {
+            return null;
+        }
+        return country.toUpperCase();
+    }
+}
diff --git a/src/com/android/mms/service/SendRequest.java b/src/com/android/mms/service/SendRequest.java
index 756e891..ff6ad65 100644
--- a/src/com/android/mms/service/SendRequest.java
+++ b/src/com/android/mms/service/SendRequest.java
@@ -16,34 +16,39 @@
 
 package com.android.mms.service;
 
-import com.google.android.mms.MmsException;
-import com.google.android.mms.pdu.GenericPdu;
-import com.google.android.mms.pdu.PduParser;
-import com.google.android.mms.pdu.PduPersister;
-import com.google.android.mms.pdu.SendConf;
-import com.google.android.mms.pdu.SendReq;
-import com.google.android.mms.util.SqliteWrapper;
-
-import com.android.mms.service.exception.MmsHttpException;
-
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Telephony;
+import android.service.carrier.CarrierMessagingService;
+import android.service.carrier.ICarrierMessagingService;
+import android.telephony.CarrierMessagingServiceManager;
 import android.telephony.SmsManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.telephony.SmsApplication;
+import com.android.mms.service.exception.MmsHttpException;
+
+import com.google.android.mms.MmsException;
+import com.google.android.mms.pdu.GenericPdu;
+import com.google.android.mms.pdu.PduHeaders;
+import com.google.android.mms.pdu.PduParser;
+import com.google.android.mms.pdu.PduPersister;
+import com.google.android.mms.pdu.SendConf;
+import com.google.android.mms.pdu.SendReq;
+import com.google.android.mms.util.SqliteWrapper;
+
 import java.util.List;
 
 /**
@@ -55,10 +60,9 @@
     private final String mLocationUrl;
     private final PendingIntent mSentIntent;
 
-    public SendRequest(RequestManager manager, long subId, Uri contentUri, Uri messageUri,
-            String locationUrl, PendingIntent sentIntent, String creator,
-            Bundle configOverrides) {
-        super(manager, messageUri, subId, creator, configOverrides);
+    public SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl,
+            PendingIntent sentIntent, String creator, Bundle configOverrides) {
+        super(manager, subId, creator, configOverrides);
         mPduUri = contentUri;
         mPduData = null;
         mLocationUrl = locationUrl;
@@ -68,12 +72,19 @@
     @Override
     protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
             throws MmsHttpException {
-        return doHttpForResolvedAddresses(context,
-                netMgr,
+        final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient();
+        if (mmsHttpClient == null) {
+            Log.e(MmsService.TAG, "MMS network is not ready!");
+            throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready");
+        }
+        return mmsHttpClient.execute(
                 mLocationUrl != null ? mLocationUrl : apn.getMmscUrl(),
                 mPduData,
-                HttpUtils.HTTP_POST_METHOD,
-                apn);
+                MmsHttpClient.METHOD_POST,
+                apn.isProxySet(),
+                apn.getProxyAddress(),
+                apn.getProxyPort(),
+                mMmsConfig);
     }
 
     @Override
@@ -82,72 +93,89 @@
     }
 
     @Override
-    protected int getRunningQueue() {
+    protected int getQueueType() {
         return MmsService.QUEUE_INDEX_SEND;
     }
 
-    public void storeInOutbox(Context context) {
+    @Override
+    protected Uri persistIfRequired(Context context, int result, byte[] response) {
+        if (!SmsApplication.shouldWriteMessageForPackage(mCreator, context)) {
+            // Not required to persist
+            return null;
+        }
+        Log.d(MmsService.TAG, "SendRequest.persistIfRequired");
+        if (mPduData == null) {
+            Log.e(MmsService.TAG, "SendRequest.persistIfRequired: empty PDU");
+            return null;
+        }
         final long identity = Binder.clearCallingIdentity();
         try {
-            // Read message using phone process identity
-            if (!readPduFromContentUri()) {
-                Log.e(MmsService.TAG, "SendRequest.storeInOutbox: empty PDU");
-                return;
+            final boolean supportContentDisposition = mMmsConfig.getSupportMmsContentDisposition();
+            // Persist the request PDU first
+            GenericPdu pdu = (new PduParser(mPduData, supportContentDisposition)).parse();
+            if (pdu == null) {
+                Log.e(MmsService.TAG, "SendRequest.persistIfRequired: can't parse input PDU");
+                return null;
             }
-            if (mMessageUri == null) {
-                // This is a new message to send
-                final GenericPdu pdu = (new PduParser(mPduData)).parse();
-                if (pdu == null) {
-                    Log.e(MmsService.TAG, "SendRequest.storeInOutbox: can't parse input PDU");
-                    return;
-                }
-                if (!(pdu instanceof SendReq)) {
-                    Log.d(MmsService.TAG, "SendRequest.storeInOutbox: not SendReq");
-                    return;
-                }
-                final PduPersister persister = PduPersister.getPduPersister(context);
-                mMessageUri = persister.persist(
-                        pdu,
-                        Telephony.Mms.Outbox.CONTENT_URI,
-                        true/*createThreadId*/,
-                        true/*groupMmsEnabled*/,
-                        null/*preOpenedFiles*/);
-                if (mMessageUri == null) {
-                    Log.e(MmsService.TAG, "SendRequest.storeInOutbox: can not persist message");
-                    return;
-                }
-                final ContentValues values = new ContentValues(5);
-                values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
-                values.put(Telephony.Mms.READ, 1);
-                values.put(Telephony.Mms.SEEN, 1);
-                if (!TextUtils.isEmpty(mCreator)) {
-                    values.put(Telephony.Mms.CREATOR, mCreator);
-                }
-                values.put(Telephony.Mms.PHONE_ID, SubscriptionManager.getPhoneId(mSubId));
-                if (SqliteWrapper.update(context, context.getContentResolver(), mMessageUri, values,
-                        null/*where*/, null/*selectionArg*/) != 1) {
-                    Log.e(MmsService.TAG, "SendRequest.storeInOutbox: failed to update message");
-                }
-            } else {
-                // This is a stored message, either in FAILED or DRAFT
-                // Move this to OUTBOX for sending
-                final ContentValues values = new ContentValues(3);
-                // Reset the timestamp
-                values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
-                values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_OUTBOX);
-                values.put(Telephony.Mms.PHONE_ID, SubscriptionManager.getPhoneId(mSubId));
-                if (SqliteWrapper.update(context, context.getContentResolver(), mMessageUri, values,
-                        null/*where*/, null/*selectionArg*/) != 1) {
-                    Log.e(MmsService.TAG, "SendRequest.storeInOutbox: failed to update message");
+            if (!(pdu instanceof SendReq)) {
+                Log.d(MmsService.TAG, "SendRequest.persistIfRequired: not SendReq");
+                return null;
+            }
+            final PduPersister persister = PduPersister.getPduPersister(context);
+            final Uri messageUri = persister.persist(
+                    pdu,
+                    Telephony.Mms.Sent.CONTENT_URI,
+                    true/*createThreadId*/,
+                    true/*groupMmsEnabled*/,
+                    null/*preOpenedFiles*/);
+            if (messageUri == null) {
+                Log.e(MmsService.TAG, "SendRequest.persistIfRequired: can not persist message");
+                return null;
+            }
+            // Update the additional columns based on the send result
+            final ContentValues values = new ContentValues();
+            SendConf sendConf = null;
+            if (response != null && response.length > 0) {
+                pdu = (new PduParser(response, supportContentDisposition)).parse();
+                if (pdu != null && pdu instanceof SendConf) {
+                    sendConf = (SendConf) pdu;
                 }
             }
+            if (result != Activity.RESULT_OK
+                    || sendConf == null
+                    || sendConf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
+                // Since we can't persist a message directly into FAILED box,
+                // we have to update the column after we persist it into SENT box.
+                // The gap between the state change is tiny so I would not expect
+                // it to cause any serious problem
+                // TODO: we should add a "failed" URI for this in MmsProvider?
+                values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_FAILED);
+            }
+            if (sendConf != null) {
+                values.put(Telephony.Mms.RESPONSE_STATUS, sendConf.getResponseStatus());
+                values.put(Telephony.Mms.MESSAGE_ID,
+                        PduPersister.toIsoString(sendConf.getMessageId()));
+            }
+            values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
+            values.put(Telephony.Mms.READ, 1);
+            values.put(Telephony.Mms.SEEN, 1);
+            if (!TextUtils.isEmpty(mCreator)) {
+                values.put(Telephony.Mms.CREATOR, mCreator);
+            }
+            values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId);
+            if (SqliteWrapper.update(context, context.getContentResolver(), messageUri, values,
+                    null/*where*/, null/*selectionArg*/) != 1) {
+                Log.e(MmsService.TAG, "SendRequest.persistIfRequired: failed to update message");
+            }
+            return messageUri;
         } catch (MmsException e) {
-            Log.e(MmsService.TAG, "SendRequest.storeInOutbox: can not persist/update message", e);
+            Log.e(MmsService.TAG, "SendRequest.persistIfRequired: can not persist message", e);
         } catch (RuntimeException e) {
-            Log.e(MmsService.TAG, "SendRequest.storeInOutbox: unexpected parsing failure", e);
+            Log.e(MmsService.TAG, "SendRequest.persistIfRequired: unexpected parsing failure", e);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
+        return null;
     }
 
     /**
@@ -163,40 +191,6 @@
         return (mPduData != null);
     }
 
-    @Override
-    protected void updateStatus(Context context, int result, byte[] response) {
-        if (mMessageUri == null) {
-            return;
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int messageStatus = result == Activity.RESULT_OK ?
-                    Telephony.Mms.MESSAGE_BOX_SENT : Telephony.Mms.MESSAGE_BOX_FAILED;
-            SendConf sendConf = null;
-            if (response != null && response.length > 0) {
-                final GenericPdu pdu = (new PduParser(response)).parse();
-                if (pdu != null && pdu instanceof SendConf) {
-                    sendConf = (SendConf) pdu;
-                }
-            }
-            final ContentValues values = new ContentValues(3);
-            values.put(Telephony.Mms.MESSAGE_BOX, messageStatus);
-            if (sendConf != null) {
-                values.put(Telephony.Mms.RESPONSE_STATUS, sendConf.getResponseStatus());
-                values.put(Telephony.Mms.MESSAGE_ID,
-                        PduPersister.toIsoString(sendConf.getMessageId()));
-            }
-            SqliteWrapper.update(context, context.getContentResolver(), mMessageUri, values,
-                    null/*where*/, null/*selectionArg*/);
-        } catch (SQLiteException e) {
-            Log.e(MmsService.TAG, "SendRequest.updateStatus: can not update message", e);
-        } catch (RuntimeException e) {
-            Log.e(MmsService.TAG, "SendRequest.updateStatus: can not parse response", e);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     /**
      * Transfer the received response to the caller (for send requests the pdu is small and can
      *  just include bytes as extra in the "returned" intent).
@@ -217,44 +211,96 @@
      * Read the data from the file descriptor if not yet done
      * @return whether data successfully read
      */
+    @Override
     protected boolean prepareForHttpRequest() {
         return readPduFromContentUri();
     }
 
     /**
-     * Try sending via the carrier app by sending an intent
+     * Try sending via the carrier app
      *
-     * @param context The context
+     * @param context the context
+     * @param carrierMessagingServicePackage the carrier messaging service sending the MMS
      */
-    public void trySendingByCarrierApp(Context context) {
-        TelephonyManager telephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        Intent intent = new Intent(Telephony.Mms.Intents.MMS_SEND_ACTION);
-        List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent(
-                intent);
-
-        if (carrierPackages == null || carrierPackages.size() != 1) {
-            mRequestManager.addRunning(this);
-        } else {
-            intent.setPackage(carrierPackages.get(0));
-            intent.putExtra(Telephony.Mms.Intents.EXTRA_MMS_CONTENT_URI, mPduUri);
-            intent.putExtra(Telephony.Mms.Intents.EXTRA_MMS_LOCATION_URL, mLocationUrl);
-            intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
-            context.sendOrderedBroadcastAsUser(
-                    intent,
-                    UserHandle.OWNER,
-                    android.Manifest.permission.RECEIVE_MMS,
-                    AppOpsManager.OP_RECEIVE_MMS,
-                    mCarrierAppResultReceiver,
-                    null/*scheduler*/,
-                    Activity.RESULT_CANCELED,
-                    null/*initialData*/,
-                    null/*initialExtras*/);
-        }
+    public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) {
+        final CarrierSendManager carrierSendManger = new CarrierSendManager();
+        final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback(
+                context, carrierSendManger);
+        carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback);
     }
 
     @Override
     protected void revokeUriPermission(Context context) {
         context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
     }
+
+    /**
+     * Sends the MMS through through the carrier app.
+     */
+    private final class CarrierSendManager extends CarrierMessagingServiceManager {
+        // Initialized in sendMms
+        private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback;
+
+        void sendMms(Context context, String carrierMessagingServicePackage,
+                CarrierSendCompleteCallback carrierSendCompleteCallback) {
+            mCarrierSendCompleteCallback = carrierSendCompleteCallback;
+            if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
+                Log.v(MmsService.TAG, "bindService() for carrier messaging service succeeded");
+            } else {
+                Log.e(MmsService.TAG, "bindService() for carrier messaging service failed");
+                carrierSendCompleteCallback.onSendMmsComplete(
+                        CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
+                        null /* no sendConfPdu */);
+            }
+        }
+
+        @Override
+        protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
+            try {
+                Uri locationUri = null;
+                if (mLocationUrl != null) {
+                    locationUri = Uri.parse(mLocationUrl);
+                }
+                carrierMessagingService.sendMms(mPduUri, mSubId, locationUri,
+                        mCarrierSendCompleteCallback);
+            } catch (RemoteException e) {
+                Log.e(MmsService.TAG,
+                        "Exception sending MMS using the carrier messaging service: " + e);
+                mCarrierSendCompleteCallback.onSendMmsComplete(
+                        CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
+                        null /* no sendConfPdu */);
+            }
+        }
+    }
+
+    /**
+     * A callback which notifies carrier messaging app send result. Once the result is ready, the
+     * carrier messaging service connection is disposed.
+     */
+    private final class CarrierSendCompleteCallback extends
+            MmsRequest.CarrierMmsActionCallback {
+        private final Context mContext;
+        private final CarrierSendManager mCarrierSendManager;
+
+        public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) {
+            mContext = context;
+            mCarrierSendManager = carrierSendManager;
+        }
+
+        @Override
+        public void onSendMmsComplete(int result, byte[] sendConfPdu) {
+            Log.d(MmsService.TAG, "Carrier app result for send: " + result);
+            mCarrierSendManager.disposeConnection(mContext);
+
+            if (!maybeFallbackToRegularDelivery(result)) {
+                processResult(mContext, toSmsManagerResult(result), sendConfPdu,
+                        0/* httpStatusCode */);
+            }
+        }
+
+        @Override
+        public void onDownloadMmsComplete(int result) {
+            Log.e(MmsService.TAG, "Unexpected onDownloadMmsComplete call with result: " + result);
+        }
+    }
 }
diff --git a/src/com/android/mms/service/exception/MmsHttpException.java b/src/com/android/mms/service/exception/MmsHttpException.java
index e486961..ad83dd7 100644
--- a/src/com/android/mms/service/exception/MmsHttpException.java
+++ b/src/com/android/mms/service/exception/MmsHttpException.java
@@ -20,20 +20,31 @@
  * HTTP exception
  */
 public class MmsHttpException extends Exception {
+    // Optional HTTP status code. 0 means ignore. Otherwise this
+    // should be a valid HTTP status code.
+    private final int mStatusCode;
 
-    public MmsHttpException() {
+    public MmsHttpException(int statusCode) {
         super();
+        mStatusCode = statusCode;
     }
 
-    public MmsHttpException(String message) {
+    public MmsHttpException(int statusCode, String message) {
         super(message);
+        mStatusCode = statusCode;
     }
 
-    public MmsHttpException(Throwable cause) {
+    public MmsHttpException(int statusCode, Throwable cause) {
         super(cause);
+        mStatusCode = statusCode;
     }
 
-    public MmsHttpException(String message, Throwable cause) {
+    public MmsHttpException(int statusCode, String message, Throwable cause) {
         super(message, cause);
+        mStatusCode = statusCode;
+    }
+
+    public int getStatusCode() {
+        return mStatusCode;
     }
 }
diff --git a/src/com/android/mms/service/http/NameResolver.java b/src/com/android/mms/service/http/NameResolver.java
deleted file mode 100644
index 13b4046..0000000
--- a/src/com/android/mms/service/http/NameResolver.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2014 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.mms.service.http;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * An interface for DNS name resolver
- */
-public interface NameResolver {
-    public InetAddress[] getAllByName(String host) throws UnknownHostException;
-}
diff --git a/src/com/android/mms/service/http/NetworkAwareClientConnectionOperator.java b/src/com/android/mms/service/http/NetworkAwareClientConnectionOperator.java
deleted file mode 100644
index ec5fa59..0000000
--- a/src/com/android/mms/service/http/NetworkAwareClientConnectionOperator.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2014 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.mms.service.http;
-
-import com.android.mms.service.MmsService;
-
-import org.apache.http.HttpHost;
-import org.apache.http.conn.ConnectTimeoutException;
-import org.apache.http.conn.HttpHostConnectException;
-import org.apache.http.conn.OperatedClientConnection;
-import org.apache.http.conn.scheme.LayeredSocketFactory;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.scheme.SocketFactory;
-import org.apache.http.impl.conn.DefaultClientConnectionOperator;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HttpContext;
-
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.ConnectException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.SocketException;
-import java.util.ArrayList;
-
-/**
- * This is a subclass of {@link org.apache.http.impl.conn.DefaultClientConnectionOperator}
- * which allows us to use a custom name resolver and pick the address type when we resolve
- * the host name to connect.
- */
-public class NetworkAwareClientConnectionOperator extends DefaultClientConnectionOperator {
-    private static final PlainSocketFactory staticPlainSocketFactory = new PlainSocketFactory();
-
-    private NameResolver mResolver;
-    private boolean mShouldUseIpv6;
-
-    public NetworkAwareClientConnectionOperator(SchemeRegistry schemes) {
-        super(schemes);
-    }
-
-    public void setNameResolver(NameResolver resolver) {
-        mResolver = resolver;
-    }
-
-    public void setShouldUseIpv6(boolean value) {
-        mShouldUseIpv6 = value;
-    }
-
-    /**
-     * Resolve name by address type. Only returns IPv6 addresses if required, or IPv4 if not.
-     *
-     * @param hostName
-     * @return The list addresses resolved
-     * @throws java.io.IOException
-     */
-    private ArrayList<InetAddress> resolveHostName(final String hostName) throws IOException {
-        final ArrayList<InetAddress> addresses = new ArrayList<InetAddress>();
-        for (final InetAddress address : mResolver.getAllByName(hostName)) {
-            if (mShouldUseIpv6 && address instanceof Inet6Address) {
-                addresses.add(address);
-            } else if (!mShouldUseIpv6 && address instanceof Inet4Address){
-                addresses.add(address);
-            }
-        }
-        return addresses;
-    }
-
-    /**
-     * This method is mostly copied from the overridden one in parent. The only change
-     * is how we resolve host name.
-     */
-    @Override
-    public void openConnection(OperatedClientConnection conn, HttpHost target, InetAddress local,
-            HttpContext context, HttpParams params) throws IOException {
-        if (conn == null) {
-            throw new IllegalArgumentException
-                ("Connection must not be null.");
-        }
-        if (target == null) {
-            throw new IllegalArgumentException
-                ("Target host must not be null.");
-        }
-        // local address may be null
-        //@@@ is context allowed to be null?
-        if (params == null) {
-            throw new IllegalArgumentException
-                ("Parameters must not be null.");
-        }
-        if (conn.isOpen()) {
-            throw new IllegalArgumentException
-                ("Connection must not be open.");
-        }
-
-        final Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
-        final SocketFactory sf = schm.getSocketFactory();
-        final SocketFactory plain_sf;
-        final LayeredSocketFactory layered_sf;
-        if (sf instanceof LayeredSocketFactory) {
-            plain_sf = staticPlainSocketFactory;
-            layered_sf = (LayeredSocketFactory)sf;
-        } else {
-            plain_sf = sf;
-            layered_sf = null;
-        }
-        // CHANGE FOR MmsService
-        ArrayList<InetAddress> addresses = resolveHostName(target.getHostName());
-
-        for (int i = 0; i < addresses.size(); ++i) {
-            Log.d(MmsService.TAG, "NetworkAwareClientConnectionOperator: connecting "
-                    + addresses.get(i));
-            Socket sock = plain_sf.createSocket();
-            conn.opening(sock, target);
-
-            try {
-                Socket connsock = plain_sf.connectSocket(sock,
-                    addresses.get(i).getHostAddress(),
-                    schm.resolvePort(target.getPort()),
-                    local, 0, params);
-                if (sock != connsock) {
-                    sock = connsock;
-                    conn.opening(sock, target);
-                }
-                /*
-                 * prepareSocket is called on the just connected
-                 * socket before the creation of the layered socket to
-                 * ensure that desired socket options such as
-                 * TCP_NODELAY, SO_RCVTIMEO, SO_LINGER will be set
-                 * before any I/O is performed on the socket. This
-                 * happens in the common case as
-                 * SSLSocketFactory.createSocket performs hostname
-                 * verification which requires that SSL handshaking be
-                 * performed.
-                 */
-                prepareSocket(sock, context, params);
-                if (layered_sf != null) {
-                    Socket layeredsock = layered_sf.createSocket(sock,
-                        target.getHostName(),
-                        schm.resolvePort(target.getPort()),
-                        true);
-                    if (layeredsock != sock) {
-                        conn.opening(layeredsock, target);
-                    }
-                    conn.openCompleted(sf.isSecure(layeredsock), params);
-                } else {
-                    conn.openCompleted(sf.isSecure(sock), params);
-                }
-                break;
-            // BEGIN android-changed
-            //       catch SocketException to cover any kind of connect failure
-            } catch (SocketException ex) {
-                if (i == addresses.size() - 1) {
-                    ConnectException cause = ex instanceof ConnectException
-                            ? (ConnectException) ex :
-                                (ConnectException) new ConnectException(
-                                        ex.getMessage()).initCause(ex);
-                    throw new HttpHostConnectException(target, cause);
-                }
-            // END android-changed
-            } catch (ConnectTimeoutException ex) {
-                if (i == addresses.size() - 1) {
-                    throw ex;
-                }
-            }
-        }
-    }
-}
diff --git a/src/com/android/mms/service/http/NetworkAwareHttpClient.java b/src/com/android/mms/service/http/NetworkAwareHttpClient.java
deleted file mode 100644
index 646670d..0000000
--- a/src/com/android/mms/service/http/NetworkAwareHttpClient.java
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Copyright (C) 2014 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.mms.service.http;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpEntityEnclosingRequest;
-import org.apache.http.HttpException;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.params.HttpClientParams;
-import org.apache.http.client.protocol.ClientContext;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.client.RequestWrapper;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-import org.apache.http.protocol.BasicHttpContext;
-import org.apache.http.protocol.BasicHttpProcessor;
-import org.apache.http.protocol.HttpContext;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.SSLCertificateSocketFactory;
-import android.net.SSLSessionCache;
-import android.os.Looper;
-import android.util.Base64;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
-/**
- * This is a copy of {@link android.net.http.AndroidHttpClient} with changes
- * to allow us using a different {@org.apache.http.conn.ClientConnectionManager}
- */
-
-/**
- * Implementation of the Apache {@link org.apache.http.impl.client.DefaultHttpClient} that is configured with
- * reasonable default settings and registered schemes for Android.
- * Don't create this directly, use the {@link #newInstance} factory method.
- *
- * <p>This client processes cookies but does not retain them by default.
- * To retain cookies, simply add a cookie store to the HttpContext:</p>
- *
- * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
- */
-public final class NetworkAwareHttpClient implements HttpClient {
-
-    // Gzip of data shorter than this probably won't be worthwhile
-    public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
-
-    // Default connection and socket timeout of 60 seconds.  Tweak to taste.
-    private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000;
-
-    private static final String TAG = "MmsHttpClient";
-
-    private static String[] textContentTypes = new String[] {
-            "text/",
-            "application/xml",
-            "application/json"
-    };
-
-    /** Interceptor throws an exception if the executing thread is blocked */
-    private static final HttpRequestInterceptor sThreadCheckInterceptor =
-            new HttpRequestInterceptor() {
-        @Override
-        public void process(HttpRequest request, HttpContext context) {
-            // Prevent the HttpRequest from being sent on the main thread
-            if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) {
-                throw new RuntimeException("This thread forbids HTTP requests");
-            }
-        }
-    };
-
-    /**
-     * Create a new HttpClient with reasonable defaults (which you can update).
-     *
-     * @param userAgent to report in your HTTP requests
-     * @param context to use for caching SSL sessions (may be null for no caching)
-     * @return AndroidHttpClient for you to use for all your requests.
-     */
-    public static NetworkAwareHttpClient newInstance(String userAgent, Context context,
-            NameResolver resolver, boolean shouldUseIpv6) {
-        HttpParams params = new BasicHttpParams();
-
-        // Turn off stale checking.  Our connections break all the time anyway,
-        // and it's not worth it to pay the penalty of checking every time.
-        HttpConnectionParams.setStaleCheckingEnabled(params, false);
-
-        HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT);
-        HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT);
-        HttpConnectionParams.setSocketBufferSize(params, 8192);
-
-        // Don't handle redirects -- return them to the caller.  Our code
-        // often wants to re-POST after a redirect, which we must do ourselves.
-        HttpClientParams.setRedirecting(params, false);
-
-        // Use a session cache for SSL sockets
-        SSLSessionCache sessionCache = context == null ? null : new SSLSessionCache(context);
-
-        // Set the specified user agent and register standard protocols.
-        HttpProtocolParams.setUserAgent(params, userAgent);
-        SchemeRegistry schemeRegistry = new SchemeRegistry();
-        schemeRegistry.register(new Scheme("http",
-                PlainSocketFactory.getSocketFactory(), 80));
-        schemeRegistry.register(new Scheme("https",
-                SSLCertificateSocketFactory.getHttpSocketFactory(
-                SOCKET_OPERATION_TIMEOUT, sessionCache), 443));
-
-        /*
-         * CHANGE FOR MmsService: using a different ClientConnectionManager which
-         * uses a custom name resolver and can specify address type
-         */
-        ClientConnectionManager manager = new NetworkAwareThreadSafeClientConnManager(
-                params, schemeRegistry, resolver, shouldUseIpv6);
-
-        // We use a factory method to modify superclass initialization
-        // parameters without the funny call-a-static-method dance.
-        return new NetworkAwareHttpClient(manager, params);
-    }
-
-    private final HttpClient delegate;
-
-    private RuntimeException mLeakedException = new IllegalStateException(
-            "AndroidHttpClient created and never closed");
-
-    private NetworkAwareHttpClient(ClientConnectionManager ccm, HttpParams params) {
-        this.delegate = new DefaultHttpClient(ccm, params) {
-            @Override
-            protected BasicHttpProcessor createHttpProcessor() {
-                // Add interceptor to prevent making requests from main thread.
-                BasicHttpProcessor processor = super.createHttpProcessor();
-                processor.addRequestInterceptor(sThreadCheckInterceptor);
-                processor.addRequestInterceptor(new CurlLogger());
-
-                return processor;
-            }
-
-            @Override
-            protected HttpContext createHttpContext() {
-                // Same as DefaultHttpClient.createHttpContext() minus the
-                // cookie store.
-                HttpContext context = new BasicHttpContext();
-                context.setAttribute(
-                        ClientContext.AUTHSCHEME_REGISTRY,
-                        getAuthSchemes());
-                context.setAttribute(
-                        ClientContext.COOKIESPEC_REGISTRY,
-                        getCookieSpecs());
-                context.setAttribute(
-                        ClientContext.CREDS_PROVIDER,
-                        getCredentialsProvider());
-                return context;
-            }
-        };
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-        if (mLeakedException != null) {
-            Log.e(TAG, "Leak found", mLeakedException);
-            mLeakedException = null;
-        }
-    }
-
-    /**
-     * Modifies a request to indicate to the server that we would like a
-     * gzipped response.  (Uses the "Accept-Encoding" HTTP header.)
-     * @param request the request to modify
-     * @see #getUngzippedContent
-     */
-    public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
-        request.addHeader("Accept-Encoding", "gzip");
-    }
-
-    /**
-     * Gets the input stream from a response entity.  If the entity is gzipped
-     * then this will get a stream over the uncompressed data.
-     *
-     * @param entity the entity whose content should be read
-     * @return the input stream to read from
-     * @throws java.io.IOException
-     */
-    public static InputStream getUngzippedContent(HttpEntity entity)
-            throws IOException {
-        InputStream responseStream = entity.getContent();
-        if (responseStream == null) return responseStream;
-        Header header = entity.getContentEncoding();
-        if (header == null) return responseStream;
-        String contentEncoding = header.getValue();
-        if (contentEncoding == null) return responseStream;
-        if (contentEncoding.contains("gzip")) responseStream
-                = new GZIPInputStream(responseStream);
-        return responseStream;
-    }
-
-    /**
-     * Release resources associated with this client.  You must call this,
-     * or significant resources (sockets and memory) may be leaked.
-     */
-    public void close() {
-        if (mLeakedException != null) {
-            getConnectionManager().shutdown();
-            mLeakedException = null;
-        }
-    }
-
-    @Override
-    public HttpParams getParams() {
-        return delegate.getParams();
-    }
-
-    @Override
-    public ClientConnectionManager getConnectionManager() {
-        return delegate.getConnectionManager();
-    }
-
-    @Override
-    public HttpResponse execute(HttpUriRequest request) throws IOException {
-        return delegate.execute(request);
-    }
-
-    @Override
-    public HttpResponse execute(HttpUriRequest request, HttpContext context)
-            throws IOException {
-        return delegate.execute(request, context);
-    }
-
-    @Override
-    public HttpResponse execute(HttpHost target, HttpRequest request)
-            throws IOException {
-        return delegate.execute(target, request);
-    }
-
-    @Override
-    public HttpResponse execute(HttpHost target, HttpRequest request,
-            HttpContext context) throws IOException {
-        return delegate.execute(target, request, context);
-    }
-
-    @Override
-    public <T> T execute(HttpUriRequest request,
-            ResponseHandler<? extends T> responseHandler)
-            throws IOException, ClientProtocolException {
-        return delegate.execute(request, responseHandler);
-    }
-
-    @Override
-    public <T> T execute(HttpUriRequest request,
-            ResponseHandler<? extends T> responseHandler, HttpContext context)
-            throws IOException, ClientProtocolException {
-        return delegate.execute(request, responseHandler, context);
-    }
-
-    @Override
-    public <T> T execute(HttpHost target, HttpRequest request,
-            ResponseHandler<? extends T> responseHandler) throws IOException,
-            ClientProtocolException {
-        return delegate.execute(target, request, responseHandler);
-    }
-
-    @Override
-    public <T> T execute(HttpHost target, HttpRequest request,
-            ResponseHandler<? extends T> responseHandler, HttpContext context)
-            throws IOException, ClientProtocolException {
-        return delegate.execute(target, request, responseHandler, context);
-    }
-
-    /**
-     * Compress data to send to server.
-     * Creates a Http Entity holding the gzipped data.
-     * The data will not be compressed if it is too short.
-     * @param data The bytes to compress
-     * @return Entity holding the data
-     */
-    public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver)
-            throws IOException {
-        AbstractHttpEntity entity;
-        if (data.length < getMinGzipSize(resolver)) {
-            entity = new ByteArrayEntity(data);
-        } else {
-            ByteArrayOutputStream arr = new ByteArrayOutputStream();
-            OutputStream zipper = new GZIPOutputStream(arr);
-            zipper.write(data);
-            zipper.close();
-            entity = new ByteArrayEntity(arr.toByteArray());
-            entity.setContentEncoding("gzip");
-        }
-        return entity;
-    }
-
-    /**
-     * Retrieves the minimum size for compressing data.
-     * Shorter data will not be compressed.
-     */
-    public static long getMinGzipSize(ContentResolver resolver) {
-        return DEFAULT_SYNC_MIN_GZIP_BYTES;  // For now, this is just a constant.
-    }
-
-    /* cURL logging support. */
-
-    /**
-     * Logging tag and level.
-     */
-    private static class LoggingConfiguration {
-
-        private final String tag;
-        private final int level;
-
-        private LoggingConfiguration(String tag, int level) {
-            this.tag = tag;
-            this.level = level;
-        }
-
-        /**
-         * Returns true if logging is turned on for this configuration.
-         */
-        private boolean isLoggable() {
-            return Log.isLoggable(tag, level);
-        }
-
-        /**
-         * Prints a message using this configuration.
-         */
-        private void println(String message) {
-            Log.println(level, tag, message);
-        }
-    }
-
-    /** cURL logging configuration. */
-    private volatile LoggingConfiguration curlConfiguration;
-
-    /**
-     * Enables cURL request logging for this client.
-     *
-     * @param name to log messages with
-     * @param level at which to log messages (see {@link android.util.Log})
-     */
-    public void enableCurlLogging(String name, int level) {
-        if (name == null) {
-            throw new NullPointerException("name");
-        }
-        if (level < Log.VERBOSE || level > Log.ASSERT) {
-            throw new IllegalArgumentException("Level is out of range ["
-                + Log.VERBOSE + ".." + Log.ASSERT + "]");
-        }
-
-        curlConfiguration = new LoggingConfiguration(name, level);
-    }
-
-    /**
-     * Disables cURL logging for this client.
-     */
-    public void disableCurlLogging() {
-        curlConfiguration = null;
-    }
-
-    /**
-     * Logs cURL commands equivalent to requests.
-     */
-    private class CurlLogger implements HttpRequestInterceptor {
-        @Override
-        public void process(HttpRequest request, HttpContext context)
-                throws HttpException, IOException {
-            LoggingConfiguration configuration = curlConfiguration;
-            if (configuration != null
-                    && configuration.isLoggable()
-                    && request instanceof HttpUriRequest) {
-                // Never print auth token -- we used to check ro.secure=0 to
-                // enable that, but can't do that in unbundled code.
-                configuration.println(toCurl((HttpUriRequest) request, false));
-            }
-        }
-    }
-
-    /**
-     * Generates a cURL command equivalent to the given request.
-     */
-    private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
-        StringBuilder builder = new StringBuilder();
-
-        builder.append("curl ");
-
-        // add in the method
-        builder.append("-X ");
-        builder.append(request.getMethod());
-        builder.append(" ");
-
-        for (Header header: request.getAllHeaders()) {
-            if (!logAuthToken
-                    && (header.getName().equals("Authorization") ||
-                        header.getName().equals("Cookie"))) {
-                continue;
-            }
-            builder.append("--header \"");
-            builder.append(header.toString().trim());
-            builder.append("\" ");
-        }
-
-        URI uri = request.getURI();
-
-        // If this is a wrapped request, use the URI from the original
-        // request instead. getURI() on the wrapper seems to return a
-        // relative URI. We want an absolute URI.
-        if (request instanceof RequestWrapper) {
-            HttpRequest original = ((RequestWrapper) request).getOriginal();
-            if (original instanceof HttpUriRequest) {
-                uri = ((HttpUriRequest) original).getURI();
-            }
-        }
-
-        builder.append("\"");
-        builder.append(uri);
-        builder.append("\"");
-
-        if (request instanceof HttpEntityEnclosingRequest) {
-            HttpEntityEnclosingRequest entityRequest =
-                    (HttpEntityEnclosingRequest) request;
-            HttpEntity entity = entityRequest.getEntity();
-            if (entity != null && entity.isRepeatable()) {
-                if (entity.getContentLength() < 1024) {
-                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
-                    entity.writeTo(stream);
-
-                    if (isBinaryContent(request)) {
-                        String base64 = Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP);
-                        builder.insert(0, "echo '" + base64 + "' | base64 -d > /tmp/$$.bin; ");
-                        builder.append(" --data-binary @/tmp/$$.bin");
-                    } else {
-                        String entityString = stream.toString();
-                        builder.append(" --data-ascii \"")
-                                .append(entityString)
-                                .append("\"");
-                    }
-                } else {
-                    builder.append(" [TOO MUCH DATA TO INCLUDE]");
-                }
-            }
-        }
-
-        return builder.toString();
-    }
-
-    public final static String CONTENT_ENCODING = "content-encoding";
-    public final static String CONTENT_TYPE = "content-type";
-
-    private static boolean isBinaryContent(HttpUriRequest request) {
-        Header[] headers;
-        headers = request.getHeaders(CONTENT_ENCODING);
-        if (headers != null) {
-            for (Header header : headers) {
-                if ("gzip".equalsIgnoreCase(header.getValue())) {
-                    return true;
-                }
-            }
-        }
-
-        headers = request.getHeaders(CONTENT_TYPE);
-        if (headers != null) {
-            for (Header header : headers) {
-                for (String contentType : textContentTypes) {
-                    if (header.getValue().startsWith(contentType)) {
-                        return false;
-                    }
-                }
-            }
-        }
-        return true;
-    }
-}
diff --git a/src/com/android/mms/service/http/NetworkAwareThreadSafeClientConnManager.java b/src/com/android/mms/service/http/NetworkAwareThreadSafeClientConnManager.java
deleted file mode 100644
index a9175cd..0000000
--- a/src/com/android/mms/service/http/NetworkAwareThreadSafeClientConnManager.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2014 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.mms.service.http;
-
-import org.apache.http.conn.ClientConnectionOperator;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.params.HttpParams;
-
-/**
- * This is a subclass of {@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager}
- * which allows us to specify a custom name resolver and the address type
- */
-public class NetworkAwareThreadSafeClientConnManager extends ThreadSafeClientConnManager {
-    public NetworkAwareThreadSafeClientConnManager(HttpParams params,
-            SchemeRegistry schreg, NameResolver resolver, boolean shouldUseIpv6) {
-        super(params, schreg);
-        ((NetworkAwareClientConnectionOperator)connOperator).setNameResolver(resolver);
-        ((NetworkAwareClientConnectionOperator)connOperator).setShouldUseIpv6(shouldUseIpv6);
-    }
-
-    @Override
-    protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) {
-        return new NetworkAwareClientConnectionOperator(schreg);
-    }
-}