在 Android KitKat 中接收彩信 [英] Receive MMS messages in Android KitKat

查看:26
本文介绍了在 Android KitKat 中接收彩信的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,#DevBytes 的这个视频Android 4.4 SMS API 解释了 KitKat 中 SMS API 的最新变化.他们还提供了一个示例项目的链接.http://goo.gl/uQ3Nih

他们建议您在服务中处理彩信的接收.一切看起来都很好,只是他们忽略了最无证的部分.如何实际处理收到的彩信.

这是项目的示例https://gist.github.com/lawloretienne/8970938

我试图处理彩信"

https://gist.github.com/lawloretienne/8971050

我可以从意图中获取额外信息,但我能提取的唯一有意义的是发送彩信的号码.

谁能为我指明正确的方向?

我注意到 WAP_PUSH_MESSAGE 包含一些内容:FROM、SUBJECT 和 CONTENT_LOCATION.

内容位置似乎是包含彩信内容的网址.我如何访问它?

这是该网址的示例

https://atl1mmsget.msg.eng.t-mobile.com/mms/wapenc?location=XXXXXXXXXXXX_14zbwk&rid=027

其中 X 是我正在测试的设备的电话号码中的一个数字.

看起来美国 T-Mobile 的 MMSC(多媒体消息服务中心)是 http://mms.msg.eng.t-mobile.com/mms/wapenc

根据这个列表:http://www.activexperts.com/xmstoolkit/mmsclist/

解决方案

文档为零,所以这里有一些信息可以提供帮助.

1) 来自源代码的 com.google.android.mms.pdu.您需要 Pdu 实用程序.

2) 您从传入的 mms 广播的字节数组 extra 中获得通知推送 (intent.getByteArrayExtra("data")).

3) 将通知推送解析为 GenericPdu (new PduParser(rawPdu).parse()).

4) 您需要 TransactionSettings 来与运营商的 wap 服务器通信.我在下面的#5 之后得到交易设置.我使用:

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

5) 强制通过 wifi 进行网络通信.我使用以下.

private boolean beginMmsConnectivity() {尝试 {int 结果 = mConnMgr.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);网络信息信息 = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);boolean isAvailable = info != null &&info.isConnected() &&结果 == Phone.APN_ALREADY_ACTIVE &&!Phone.REASON_VOICE_CALL_ENDED.equals(info.getReason());返回是可用的;} 捕获(异常 e){返回假;}}

6) 然后您需要确保到主机的路由.

private static void ensureRouteToHost(ConnectivityManager cm, String url, TransactionSettings settings) 抛出 IOException {内部地址;如果 (settings.isProxySet()) {String proxyAddr = settings.getProxyAddress();inetAddr = lookupHost(proxyAddr);如果(inetAddr == -1){throw new IOException("无法为" + url + "建立路由:未知主机");} 别的 {if (!cm.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_MMS, inetAddr))throw new IOException("无法建立到代理的路由" + inetAddr);}} 别的 {uri uri = uri.parse(url);inetAddr = lookupHost(uri.getHost());如果(inetAddr == -1){throw new IOException("无法为" + url + "建立路由:未知主机");} 别的 {if (!cm.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_MMS, inetAddr))throw new IOException("无法为" + url 建立到" + inetAddr + " 的路由);}}}

这是lookupHost方法:

private static int lookupHost(String hostname) {InetAddress inetAddress;尝试 {inetAddress = InetAddress.getByName(hostname);} catch (UnknownHostException e) {返回-1;}字节[] addrBytes;国际地址;addrBytes = inetAddress.getAddress();addr = ((addrBytes[3] & 0xff) <<24) |((addrBytes[2] & 0xff) <<16) |((addrBytes[1] & 0xff) << 8) |(addrBytes[0] & 0xff);返回地址;}

我还喜欢使用基于反射的方法来改进 ensureRouteToHost 功能:

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;如果 (settings.isProxySet()) {String proxyAddr = settings.getProxyAddress();尝试 {inetAddr = InetAddress.getByName(proxyAddr);} catch (UnknownHostException e) {throw new IOException("无法为"+url+"建立路由:未知代理"+proxyAddr);}if (!(Boolean) m.invoke(cm, new Object[] { ConnectivityManager.TYPE_MOBILE_MMS, inetAddr }))throw new IOException("无法建立到代理的路由" + inetAddr);} 别的 {uri uri = uri.parse(url);尝试 {inetAddr = InetAddress.getByName(uri.getHost());} catch (UnknownHostException e) {throw new IOException("无法为" + url + "建立路由:未知主机");}if (!(Boolean) m.invoke(cm, new Object[] { ConnectivityManager.TYPE_MOBILE_MMS, inetAddr }))throw new IOException("无法为" + url 建立到" + inetAddr + " 的路由);}}

7) 在确保到主机的路由后,您就可以需要来自源的 HttpUtls.我使用 OkHttp 对我的实现进行了大量修改,以改善通信.

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

8) 从生成的字节数组中使用 PduParser 解析 GenericPdu.然后您可以提取正文并转换为 MultimediaMessagePdu.

9) 然后你可以迭代 PDU 的各个部分.

使用彩信需要考虑的事情数不胜数.我想到的一件事是幻灯片有多烦人,所以我要做的是检测 PDU 中是否有 1 个以上的部分,然后我复制标题并创建单独的 MultimediaMessagePdu,我将它们分别保存到手机的彩信内容提供商.不要忘记复制标题,特别是如果您支持群发消息.群发消息是另一回事,因为 PDU 中传入的电话号码并不能说明全部情况 (MultimediaMessagePdu.mmpdu()).您可以使用以下代码提取标题中的更多联系人.

私有HashSetgetRecipients(GenericPdu pdu) {PduHeaders header = pdu.getPduHeaders();HashMapaddressMap = new HashMap(ADDRESS_FIELDS.length);for (int addrType : ADDRESS_FIELDS) {EncodedStringValue[] array = null;如果(addrType == PduHeaders.FROM){EncodedStringValue v = header.getEncodedStringValue(addrType);如果 (v != null) {数组 = 新编码字符串值 [1];数组[0] = v;}} 别的 {数组 = header.getEncodedStringValues(addrType);}addressMap.put(addrType, array);}哈希集<字符串>接收者 = new HashSet();loadRecipients(PduHeaders.FROM, 收件人, addressMap, false);加载接收者(PduHeaders.TO,接收者,地址映射,真);返回收件人;}

这是加载接收者方法:

private void loadRecipients(int addressType, HashSet receivers, HashMap addressMap, boolean excludeMyNumber) {EncodedStringValue[] array = addressMap.get(addressType);如果(数组==空){返回;}//如果 TO 收件人只有一个地址,那么我们可以跳过 loadRecipients 当//我们不包括我们自己的号码,因为我们知道地址是我们自己的.if (excludeMyNumber && array.length == 1) {返回;}字符串 myNumber = excludeMyNumber ?mTelephonyManager.getLine1Number() : null;for (EncodedStringValue v : array) {如果 (v != null) {字符串编号 = v.getString();if ((myNumber == null || !PhoneNumberUtils.compare(number, myNumber)) && !recipients.contains(number)) {//只添加不是我自己号码的数字.收件人.添加(号码);}}}}

这是迭代 MultimediaMessagePdu 部分的方法.

private void processPduAttachments() 抛出异常 {if (mGenericPdu instanceof MultimediaMessagePdu) {PduBody body = ((MultimediaMessagePdu) mGenericPdu).getBody();如果(身体!= null){int partsNum = body.getPartsNum();for (int i = 0; i 

还有更多考虑因素,例如动画 GIF 支持,这是完全可能的 :) 一些运营商支持确认报告和交付报告,您很可能会忽略这些 wap 通信,除非用户真的真的想要 mms 交付报告.

So this video Android 4.4 SMS APIs from #DevBytes explains the recent changes to the SMS APIs in KitKat. They also provide a link with a sample project. http://goo.gl/uQ3Nih

They suggest that you handle the receive of an MMS in a service. Which all looks fine, except they neglect to mention the most undocumented piece. How to actually handle an incoming MMS.

Here is the sample from the project https://gist.github.com/lawloretienne/8970938

I have tried to "handle the MMS"

https://gist.github.com/lawloretienne/8971050

I can get the extras from the intent but the only meaningful thing that I can extract is the number from which the MMS was sent.

Can anyone point me in the right direction about how to go about this?

I noticed that a WAP_PUSH_MESSAGE contains a few things, a FROM, SUBJECT, and CONTENT_LOCATION.

The content location appears to be the url where the content of the MMS is contained. How can I access this?

Here is an example of that URL

https://atl1mmsget.msg.eng.t-mobile.com/mms/wapenc?location=XXXXXXXXXXX_14zbwk&rid=027

Where the X is a digit in the phone number of the device I am testing on.

It looks like the MMSC (Multimedia Messaging Service Center) for T-Mobile in the U.S. is http://mms.msg.eng.t-mobile.com/mms/wapenc

According to this list : http://www.activexperts.com/xmstoolkit/mmsclist/

解决方案

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.

这篇关于在 Android KitKat 中接收彩信的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆