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 (>=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);
- }
-}