Receive Mms Messages in Android Kitkat

Receive MMS messages in Android KitKat

There's zero documentation so here's some info to help.

1) com.google.android.mms.pdu from source. You need the Pdu utils.

2) You get the notification push from byte array extra of the incoming mms broadcast (intent.getByteArrayExtra("data")).

3) Parse the notification push into a GenericPdu (new PduParser(rawPdu).parse()).

4) You'll need TransactionSettings to communicate with the carrier's wap server. I get the transaction settings after #5 below. I use:

TransactionSettings transactionSettings = new TransactionSettings(mContext, mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS).getExtraInfo());

5) Force network comm over wifi. I use the following.

private boolean beginMmsConnectivity() {
try {
int result = mConnMgr.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);
NetworkInfo info = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
boolean isAvailable = info != null && info.isConnected() && result == Phone.APN_ALREADY_ACTIVE && !Phone.REASON_VOICE_CALL_ENDED.equals(info.getReason());
return isAvailable;
} catch(Exception e) {
return false;
}
}

6) You then need to ensure a route to the host.

private static void ensureRouteToHost(ConnectivityManager cm, String url, TransactionSettings settings) throws IOException {
int inetAddr;
if (settings.isProxySet()) {
String proxyAddr = settings.getProxyAddress();
inetAddr = lookupHost(proxyAddr);
if (inetAddr == -1) {
throw new IOException("Cannot establish route for " + url + ": Unknown host");
} else {
if (!cm.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_MMS, inetAddr))
throw new IOException("Cannot establish route to proxy " + inetAddr);
}
} else {
Uri uri = Uri.parse(url);
inetAddr = lookupHost(uri.getHost());
if (inetAddr == -1) {
throw new IOException("Cannot establish route for " + url + ": Unknown host");
} else {
if (!cm.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_MMS, inetAddr))
throw new IOException("Cannot establish route to " + inetAddr + " for " + url);
}
}
}

Here's the lookupHost method:

private static int lookupHost(String hostname) {
InetAddress inetAddress;
try {
inetAddress = InetAddress.getByName(hostname);
} catch (UnknownHostException e) {
return -1;
}
byte[] addrBytes;
int addr;
addrBytes = inetAddress.getAddress();
addr = ((addrBytes[3] & 0xff) << 24) | ((addrBytes[2] & 0xff) << 16) | ((addrBytes[1] & 0xff) << 8) | (addrBytes[0] & 0xff);
return addr;
}

I also like to use a reflection based method for improved ensureRouteToHost functionality:

private static void ensureRouteToHostFancy(ConnectivityManager cm, String url, TransactionSettings settings) throws IOException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method m = cm.getClass().getMethod("requestRouteToHostAddress", new Class[] { int.class, InetAddress.class });
InetAddress inetAddr;
if (settings.isProxySet()) {
String proxyAddr = settings.getProxyAddress();
try {
inetAddr = InetAddress.getByName(proxyAddr);
} catch (UnknownHostException e) {
throw new IOException("Cannot establish route for " + url + ": Unknown proxy " + proxyAddr);
}
if (!(Boolean) m.invoke(cm, new Object[] { ConnectivityManager.TYPE_MOBILE_MMS, inetAddr }))
throw new IOException("Cannot establish route to proxy " + inetAddr);
} else {
Uri uri = Uri.parse(url);
try {
inetAddr = InetAddress.getByName(uri.getHost());
} catch (UnknownHostException e) {
throw new IOException("Cannot establish route for " + url + ": Unknown host");
}
if (!(Boolean) m.invoke(cm, new Object[] { ConnectivityManager.TYPE_MOBILE_MMS, inetAddr }))
throw new IOException("Cannot establish route to " + inetAddr + " for " + url);
}
}

7) After ensuring a route to the host you can then need HttpUtls from source. I've heavily modified my implementation using OkHttp for improved communications.

byte[] rawPdu = HttpUtils.httpConnection(mContext, mContentLocation, null, HttpUtils.HTTP_GET_METHOD, mTransactionSettings.isProxySet(), mTransactionSettings.getProxyAddress(), mTransactionSettings.getProxyPort());

8) From the resulting byte array use the PduParser to parge the GenericPdu. Then you can extract the body and cast to a MultimediaMessagePdu.

9) Then you can iterate the parts of the PDU.

There are countless things to consider with MMS. One thing that comes to mind is how annoying Slideshows are, so what I do is detect if there are more than 1 parts in the PDU, then I copy the headers and create separate MultimediaMessagePdu of which I save them to the phone's mms content provider separately. Don't forget to copy the headers especially if you are supporting group messaging. Group messaging is another story because the incomging telephone number in the PDU doesn't tell the whole story (MultimediaMessagePdu.mmpdu()). There's more contacts in the header that you extract using the following code.

private HashSet<String> getRecipients(GenericPdu pdu) {
PduHeaders header = pdu.getPduHeaders();
HashMap<Integer, EncodedStringValue[]> addressMap = new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
for (int addrType : ADDRESS_FIELDS) {
EncodedStringValue[] array = null;
if (addrType == PduHeaders.FROM) {
EncodedStringValue v = header.getEncodedStringValue(addrType);
if (v != null) {
array = new EncodedStringValue[1];
array[0] = v;
}
} else {
array = header.getEncodedStringValues(addrType);
}
addressMap.put(addrType, array);
}
HashSet<String> recipients = new HashSet<String>();
loadRecipients(PduHeaders.FROM, recipients, addressMap, false);
loadRecipients(PduHeaders.TO, recipients, addressMap, true);
return recipients;
}

Here's the load recipients method:

private void loadRecipients(int addressType, HashSet<String> recipients, HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber) {
EncodedStringValue[] array = addressMap.get(addressType);
if (array == null) {
return;
}
// If the TO recipients is only a single address, then we can skip loadRecipients when
// we're excluding our own number because we know that address is our own.
if (excludeMyNumber && array.length == 1) {
return;
}
String myNumber = excludeMyNumber ? mTelephonyManager.getLine1Number() : null;
for (EncodedStringValue v : array) {
if (v != null) {
String number = v.getString();
if ((myNumber == null || !PhoneNumberUtils.compare(number, myNumber)) && !recipients.contains(number)) {
// Only add numbers which aren't my own number.
recipients.add(number);
}
}
}
}

Here's how to iterate the MultimediaMessagePdu parts.

private void processPduAttachments() throws Exception {
if (mGenericPdu instanceof MultimediaMessagePdu) {
PduBody body = ((MultimediaMessagePdu) mGenericPdu).getBody();
if (body != null) {
int partsNum = body.getPartsNum();
for (int i = 0; i < partsNum; i++) {
try {
PduPart part = body.getPart(i);
if (part == null || part.getData() == null || part.getContentType() == null || part.getName() == null)
continue;
String partType = new String(part.getContentType());
String partName = new String(part.getName());
Log.d("Part Name: " + partName);
Log.d("Part Type: " + partType);
if (ContentType.isTextType(partType)) {
} else if (ContentType.isImageType(partType)) {
} else if (ContentType.isVideoType(partType)) {
} else if (ContentType.isAudioType(partType)) {
}
} catch (Exception e) {
e.printStackTrace();
// Bad part shouldn't ruin the party for the other parts
}
}
}
} else {
Log.d("Not a MultimediaMessagePdu PDU");
}
}

There's many more considerations such as animated GIF support, which is entirely possible :) Some carriers support acknowledge reports, and delivery reports too, you can most likely neglect these wap communications unless a user really really wants mms delivery reports.

Receive MMS - no default application [KITKAT]

maybe can I save mms to the SMS Provider from my app in onReceive() method and next launch native (no default) sms/mms app to display them?

This, I would imagine, is possible, but I don't think it's going to work out well in the end. To do this, your app, upon first startup, would need to get the already-set default app using Telephony.Sms.getDefaultSmsPackage(), and store this package name in order to launch it after your app's writes upon MMS receipt. This part I see no problem with.

The catch comes when the now-demoted app tries to send an MMS. Quoting the blog you've linked:

Although the system writes sent SMS messages to the SMS Provider while your app is not the default SMS app, it does not write sent MMS messages

Now, this might be acceptable, if your app could do the outgoing writes as well. The first problem I see is that, as far as I know, there is no system-wide broadcast when an MMS is sent, so your app wouldn't know it's happening, let alone have access to data not explicitly handed to it. Secondly, again quoting:

while your app is not selected as the default, it's important that you understand the limitations placed upon your app and disable features as appropriate

Assuming the platform app follows these prescribed practices, when it is no longer the default, it may very well disable its outgoing MMS functions. But, even if it doesn't, and it reverts from its direct sends to passing the request and data through an Intent to the default app, well, at this point you're handling both incoming and outgoing MMS, thus defeating the whole point of your workaround.

In the end, it's probably just easier to go ahead and make your app totally compliant.

manually backup sms/mms database on kitkat

got impatient and tested this out myself.

This still works for kitkat.

copied both

/data/data/com.android.providers.telephony/databases/mmssms.db
/data/data/com.android.providers.telephony/databases/mmssms.db-journal

using the method outlined in the other SO question.

Sending mms in android 4.4

Easiest way i found for sending mms is android-smsmms library found here: https://github.com/klinker41/android-smsmms

For gettings mmsc, proxy and port i used:

 final Cursor apnCursor = SqliteWrapper.query(mContext, this.mContext.getContentResolver(),
Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current"), APN_PROJECTION, null, null, null);
String type = null;
if (apnCursor.moveToFirst()) {
do {
type = apnCursor.getString(3);
if(type.equals("default,supl,mms") ||
type.equals("mms")) {
mmsc = apnCursor.getString(0);
proxy = apnCursor.getString(1);
port = apnCursor.getString(2);
}while (apnCursor.moveToNext());

In if loop i am checking if APN has MMS data that i need otherwise go to next one.



Related Topics



Leave a reply



Submit