MMS in Android. Part 1. Sending and receiving MMS

August 13, 2011 § 5 Comments

Judging by forums questions (and my own experience) sending mms programmatically is quite complicate issue. That is because there is no documented API for it. To start working with mms we will need to use some undocumented classes. Happily Android OS is open source project. So we can download some classes from http://android.git.kernel.org/ and use it in our code. I know that it is not the best solution (while in future version this code could be modified) but it is the only one if you want to work with mms from your code.

Before starting working with mms I would recommend get to touch with Multimedia Messaging Service Encapsulation Protocol documentation.

There are different Multimedia Message Types. Each of them has own purpose. Here is the most important ones that we will use below:

  • “m-send-req” – sending message
  • “m-notification-ind” – provide the MMS Client with information about a MM located at the recipient MMS Proxy-Relay and waiting for retrieval. Client after that should fetch MM. It doesn’t contain Message Body but only Header.
  • “m-delivery-ind” – delivery report of MM

Here are inner classes that we can use.

We will need (you can copy the whole file or “borrow part of them”):

  • android.provider.Telephony
  • com.google.android.mms.ContentType
Files from com.google.android.mms.pdu package:
  • PduHeaders – contains map of headers
  • PduBody – includes PduParts
  • PduPart – consists of part header, data uri, part data
  • GenericPdu – wraps PduHeader
  • MultimediaMessagePdu – extends GenericPdu, adds PduBody to it
  • SendReq – extends MultimediaMessagePdu, represents sending request – “m-send-req” type
  • DeliveryInd – extends GenericPdu , represents delivery report – “m-delivery-ind” type
  • NotificationInd –  extends GenericPdu , represents notification about incoming message (that is stored in MMBOX and should be retrieved from there) – “m-notification-ind” type
  • PduComposer – translate Pdu to byte[]
  • PduParser – translate byte[] to Pdu
  • PduPersister – stores MM in storage.

Ok, here are the tasks to implement comprehencive mms support for your application:

  • Sending mms
  • Receiving delivery/read report
  • Receiving incoming mms
  • Getting list of mms from storage
  • Inserting/updating/deleting mms data to/at/from storage

First of all you should understand what points from above list you are going to implement.

So, in order…

Sending mms

MMS unlike SMS is send through http connection. Actually it is just request to mmsc (Multimedia Message System Center).

First of all we have to request Connectivity Service to enable mms:

  ConnectivityManager mConnMgr = 
        (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
  int result = mConnMgr.startUsingNetworkFeature(
        ConnectivityManager.TYPE_MOBILE, "enableMMS");

If result is 0 (APN_ALREADY_ACTIVE) we can start sending MMS. But likely it is not. In that case we have to wait while it become ACTIVE. We can register broadcast receiver that listens “ConnectivityManager.CONNECTIVITY_ACTION” action. At onReceive() method of our receiver we check whether connectivity manager is ready to send mms:

    String action = intent.getAction();
    if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
        return;
    }

    NetworkInfo mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
        ConnectivityManager.EXTRA_NETWORK_INFO);

    // Check availability of the mobile network.
    if ((mNetworkInfo == null) ||
        (mNetworkInfo.getType() != ConnectivityManager.TYPE_MOBILE_MMS)) {
        return;
    }

    if (!mNetworkInfo.isConnected()) {
        return;
    } else {
	//send mms
    }

How to send mms when we are ready to do it? We can watch sources of standard messaging client that are open as almost everything in Android. To implement the same solution is not too complicated but quite intricate.

Or we can use API that was designed by Nokia that encapsulates all complexity. You can find more information and links here.

But none of the solutions works with Samsung (at list Galaxy S and Galaxy Tab). It throws exception during http request to mmsc. It seems that Samsung have changed some network properties. I have not yet figured out how to cope with it.

Receiving delivery/read report

We should implement BroadcastReceiver with follow intent filter:

    <intent-filter>
        <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
        <data android:mimeType="application/vnd.wap.mms-message" />
    </intent-filter>

In onReceive() we get pdu byte[] from intent and transform it in GenericPdu object via PduParser:

    byte[] pushData = intent.getByteArrayExtra("data");
    PduParser parser = new PduParser(pushData);
    GenericPdu pdu = parser.parse();

Than we get the pdu’s type and check whether it delivery/read report:

    int type = pdu.getMessageType();
    if(type == PduHeaders.MESSAGE_TYPE_DELIVERY_IND) {
        String messageId = new String(((DeliveryInd)pdu).getMessageId());
        //Notify app that mms with messageId was delivered
    }

Ok, here we can make a try to update data in storage. But it is not necessary. Standard messenger will do it for us. While device is not rooted it is impossible to delete or shut down standard client. And it makes our way a bit easier.

But if we want to make it anyway we get delivery status and make appropriate update of storage:

    int status = ((DeliveryInd)pdu).getStatus();
    if(status == PduHeaders.STATUS_RETRIEVED) {
        //message delivered. update storage
    }

There could be follow statuses:

  • PduHeaders.STATUS_EXPIRED
  • PduHeaders.STATUS_RETRIEVED
  • PduHeaders.STATUS_REJECTED
  • PduHeaders.STATUS_DEFERRED
  • PduHeaders.STATUS_UNRECOGNIZED
  • PduHeaders.STATUS_INDETERMINATE
  • PduHeaders.STATUS_FORWARDED
  • PduHeaders.STATUS_UNREACHABLE
The meaning is clear from it name. We can define how to act in each case. For example that’s how we can save update if the status ==  STATUS_RETRIEVED (delivered):
    ContentValues values = new ContentValues();
    values.put("msg_box; //type 2 == MESSAGE_TYPE_SENT
    String where = "_id" + " = '" + messageId + "'";
    context.getContentResolver().update(
            Uri.parse("content://mmsalues, where, null);

Receiving incoming MMS

When we have new inbox MMS we receive (in our broadcast receiver that is described above) pdu that has “m-notification-ind” type. But this pdu doesn’t contain multimedia data but only location at MMBOX of our mmsc where the content is situated. And we have to download it from that location.

Actually standard message client will do it for us. I will not write how to do it by our own. It is complicated process that take a lot of code at dozen classes. You can follow the path of tasks starting from com.android.mms.transaction.PduReceiver onReceive() method. If you really need the solution you can always use the code from that package.

출처 : https://maximbogatov.wordpress.com/2011/08/13/mms-in-android/

SMSConvey 라는 앱을 만든 후 에러 정보가 한동안 없었는데 Action Bar 에 아이콘을 넣은 후부터

android.view.InflateException 이라는 에러 정보가 뜨기 시작했다.

구글링을 해보니 화면을 그릴때 먼가 이미지 크기가 맞지 않아서 나는 에러이며

특정 이미지가 해당 경로에 없으면 발생할수 있는 에러라고 한다.

일단 화면크기별로 아이콘을 준비하지 못해서 발생되는 에러라고 판단이 되었다.

그리고 특정 기기에서 에러가 발생되는것 같았다. (에러 원인은 http://kjcoder.tistory.com/270 )

Galaxy Mega 6.3(meliusltektt) , LG G3 A(tigers) ,Galaxy S3(c1skt)  등.. 화면크기가 제각각인데;;;

일단 오류가 나는 현상을 보고 싶었지만

나한테는 그런 폰들이 없어 테스트 해볼수가 없었다

지인들에게도 깔아봤을때도 이상이없었는데

불행히도 위 에러 목록에 있는 핸드폰이 있질않았다..

있다 하더라도 매번 부탁할수도 없는 노릇이고...

그런데 Xamarin Forms 대표 카페에서 웹에서 앱을 테스트 할수 있는 도구가 있다고 해서 해봤다.

결론은 잘된다.. 잘되서 소개해본다.^^

 

https://www.appvillage.or.kr/main/main.do

위 사이트로 들어가서 일단 가입을 하자

보통 사이트 가입과 비슷하다 한가지 특이한건 ID 를 한글로 만들어야 한다. 영어는 입력이 안된다.

적응이 안되지만 한글로 만들어보니 잘되었다.

여기선 비번 찾을때 이용자정보 찾기 질문 을 가지고 비번을 찾을 수가 있다.

그러므로 잘 기억될수 있는걸로 질문을 선택하고 답변을 적자...

가입을 완료 했으면 로그인을 하고 아래 초기화면에서 온라인 앱 테스트 도구를 선택하자.

모바일 테스트 시작 을 클릭하면

먼가 설치가 덜 되었다고 뜬다. JAVA 기반으로 만들어 져있나보다.

아래 알림창으로 뜬걸 설치 하니..

아래처럼 PASS 가 떴다. 이네 앰 테스트 시작하기 를 클릭하자

실행

사용할 수 있는 핸드폰 목록이 쫙 뜬다.

Note4 에서도 에러가 난다고 해서 Note4 를 선택했다.

예약하기 는 머지??

시작하기 를 선택하니 아래 처럼 핸드폰 화면이 바로떴다.

이제 내가 구글플레이에 배포했던 앱을 깔아 봐야겠다.

로컬에 apk 파일이 있다면 직접 apk 을 선택해 깔아 볼수 있다.

앱 설치하기를 선택하고

Browse 를 선택하여 apk 파일을 선택한다.

설치가 진행중이다.

아래 화면에서 보듯이 어플이 설치된 것을 볼수 있다. (SMSConvey)

실행해 보니 역시나 바로 중단되고 나가버린다.;;; 에러가 나긴 났구나;;;

예상되는 에러 부분을 찾아 수정하고 다시 설치해보고 테스트 해봐야겠다.

이렇게 핸드폰이 없이도 여러 핸드폰으로 앱 테스트가 가능하다.

 

참고로 아이폰도 가능한데 아이폰은 아래 그림처험 iPhone 6 Plus 하나만 지원한다.

 

 

 

 

 

 

MMS는 좀 어려운데, 검색하면 PduParser 등을 이용해야 하는 걸로 많이 나오는데, 딸린 파일이 엄청 많다.

안드로이드 기본 소스에는 들어가있는 것처럼 나오던데, 이상하게 저 클래스를 쓸 수가 없더라.

그렇다고 17MB짜리 jar를 추가해서 쓰기도 좀 그렇고….

 

*LG G2에서는 MMS의 발신자 정보 들어있는 곳에 수신자 전화번호가 보여서 가져올 수가 없다.

출처 : http://susemi99.kr/664

apk 파일을 디컴파일을 해보고 싶은 마음이 생겼습니다.

개발할 어플이 있는데 좀 롤모델이 될만한 어플이 있어

한번 내부를 들여다 보고 싶었기 때문이죠

그래서 방법을 공유합니다.

일단 완전하게 빼낼수는 없는점 참고하세요

 

1. PC 상에서 디컴파일 하는 방법

pc 상에서 apk 파일을 가지고 디컴파일을 할수 있습니다.

우선 apk 파일을 빼내야 합니다.

apk 파일을 빼내는건 아주 쉽습니다.

구글 플레이에서 많은 어플들이 있는데요

그중에서도 난 아래 어플을 많이 사용하고 있습니다.

'앱 추출' 이라는 어플입니다.

사용방법은 너무 간단해서 따로 설명하지는 않겠습니다.

뺴낸 apk 파일을 컴퓨터로 옮깁니다.

이제 작업할 프로그램을 다운 받습니다.

경로가서 다운받아도 되고 첨부로 받아도 됩니다.

 

dex2jar : https://code.google.com/p/dex2jar/ => dex 파일을 jar 파일로 변환해주는 툴입니다

dex2jar.zip

 

 

JD-GUI : http://jd.benow.ca/ =>  java 디컴파일 툴입니다.

jd-gui-0.3.6.windows.zip

 

이제 준비는 완료

dex2jar 파일압축을 풀고 cmd 창을 열어 해당 경로로 이동합니다.

그리고 아래 처럼 명령어를 날립니다. remote 는 apk 파일 명칭입니다.

dex2jar.bat remote.apk

아 위명령어는 옛날버전이군요.. 다시 날립니다.

d2j-dex2jar remote.apk

작업이 끝나면 폴더에 아래처럼 jar 파일이 생성됩니다.

이제 jar 파일을 JD-GUI 툴로 보면됩니다.

jd-gui.exe 를 실행하고

jar 파일을 엽니다.

흠.. 역시 어플 만드는 사람들이 바보가 아니라서 디컴파일해도 알수없도록 되어있네요

하지만 내부 소스는 그대로 보이긴합니다. 참고용으로는 사용이 가능합니다. ^^

 

2. 핸드폰으로 어플을 통해 디컴파일 하는 방법

전 이 방법이 제일 편한것 같습니다.

apk 파일을 뺴낼필요가 없습니다. 그냥 어플만 다운받고 설치된 어플 중에 하나를 선택하면됩니다.

Show Java 라는 어플을 다운받습니다.

실행합니다.

전 지금 디컴파일 한게 많아서 그런데 최초에는 아무것도 없습니다.

오른쪽 하단의 + 버튼을 눌러  Pick from Installed 를 선택합니다.

그러면 설치된 어플을 쭉 찾아서 보여줍니다.

디컴파일 하고자 하는 어플을 선택합니다.

아래처럼 어떤 디컴파일러를 쓸꺼냐고 물어봅니다.

전 JeDX 0.6.1 을 자주 사용합니다.

선택하고 나면 아래처럼 나타나면서 디컴파일을 진행합니다.

디컴파일이 완료되었습니다.

위 PC 로 진행한것 보다 더 보기가 편한것 같습니다.

그런데 이걸 핸드폰으로 보면 의미가 없겠죠

상단의 공유 모양 버튼을 클릭합니다.

그러면 아래 그림처럼 열심히 공유하기 위한 준비를 합니다.

아마도 압축을 하는것 같습니다.

아래처럼 이제 공유하고자 하는 곳을 선택하게되면 디컴파일한 결과가 압축되어 전달됩니다.

그럼 PC 에서 해당 파일을 압축을 풀어서 보면 됩니다.

정말... 쉽습니다...

 

// 웹페이지 띄우기

Uri uri = Uri.parse("http://www.google.com");

Intent it  = new Intent(Intent.ACTION_VIEW,uri);

startActivity(it);

 

 

// 구글맵 띄우기

Uri uri = Uri.parse("geo:38.899533,-77.036476");

Intent it = new Intent(Intent.Action_VIEW,uri);

startActivity(it);

 

 

// 구글 길찾기 띄우기

Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=출발지주소&daddr=도착지주소&hl=ko");

Intent it = new Intent(Intent.ACTION_VIEW,URI);

startActivity(it);

 

 

// 전화 걸기

Uri uri = Uri.parse("tel:xxxxxx");

Intent it = new Intent(Intent.ACTION_DIAL, uri); 

startActivity(it); 

 

 

Uri uri = Uri.parse("tel.xxxxxx");

Intent it = new Intent(Intent.ACTION_CALL,uri);

// 퍼미션을 잊지 마세요. <uses-permission id="android.permission.CALL_PHONE" />

 

 

// SMS/MMS 발송

Intent it = new Intent(Intent.ACTION_VIEW);  

it.putExtra("sms_body", "The SMS text");  

it.setType("vnd.android-dir/mms-sms");  

startActivity(it); 

 

 

// SMS 발송

Uri uri = Uri.parse("smsto:0800000123");  

Intent it = new Intent(Intent.ACTION_SENDTO, uri);  

it.putExtra("sms_body", "The SMS text");  

startActivity(it); 

 

 

// MMS 발송

Uri uri = Uri.parse("content://media/external/images/media/23");  

Intent it = new Intent(Intent.ACTION_SEND);  

it.putExtra("sms_body", "some text");  

it.putExtra(Intent.EXTRA_STREAM, uri);  

it.setType("image/png");  

startActivity(it);

 

 

// 이메일 발송

Uri uri = Uri.parse("mailto:xxx@abc.com");

Intent it = new Intent(Intent.ACTION_SENDTO, uri);

startActivity(it);

 

 

Intent it = new Intent(Intent.ACTION_SEND);  

it.putExtra(Intent.EXTRA_EMAIL, "me@abc.com");  

it.putExtra(Intent.EXTRA_TEXT, "The email body text");  

it.setType("text/plain");  

startActivity(Intent.createChooser(it, "Choose Email Client")); 

 

 

Intent it = new Intent(Intent.ACTION_SEND);    

String[] tos = {"me@abc.com"};    

String[] ccs = {"you@abc.com"};    

it.putExtra(Intent.EXTRA_EMAIL, tos);    

it.putExtra(Intent.EXTRA_CC, ccs);    

it.putExtra(Intent.EXTRA_TEXT, "The email body text");    

it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");    

it.setType("message/rfc822");    

startActivity(Intent.createChooser(it, "Choose Email Client"));  

 

 

// extra 추가하기

Intent it = new Intent(Intent.ACTION_SEND);  

it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");  

it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/mysong.mp3");  

sendIntent.setType("audio/mp3");  

startActivity(Intent.createChooser(it, "Choose Email Client"));

 

 

// 미디어파일 플레이 하기

Intent it = new Intent(Intent.ACTION_VIEW);

Uri uri = Uri.parse("file:///sdcard/song.mp3");

it.setDataAndType(uri, "audio/mp3");

startActivity(it);

 

 

Uri uri = Uri.withAppendedPath(

  MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");  

Intent it = new Intent(Intent.ACTION_VIEW, uri);  

startActivity(it); 

 

 

// 설치 어플 제거

Uri uri = Uri.fromParts("package", strPackageName, null);  

Intent it = new Intent(Intent.ACTION_DELETE, uri);  

startActivity(it);

 

 

// APK파일을 통해 제거하기

Uri uninstallUri = Uri.fromParts("package", "xxx", null);

returnIt = new Intent(Intent.ACTION_DELETE, uninstallUri);

 

 

// APK파일 설치

Uri installUri = Uri.fromParts("package", "xxx", null);

returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);

 

 

// 음악 파일 재생

Uri playUri = Uri.parse("file:///sdcard/download/everything.mp3");

returnIt = new Intent(Intent.ACTION_VIEW, playUri);

 

 

// 첨부파일을 추가하여 메일 보내기

Intent it = new Intent(Intent.ACTION_SEND); 

it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); 

it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/eoe.mp3"); 

sendIntent.setType("audio/mp3"); 

startActivity(Intent.createChooser(it, "Choose Email Client"));

 

 

// 마켓에서 어플리케이션 검색

Uri uri = Uri.parse("market://search?q=pname:pkg_name"); 

Intent it = new Intent(Intent.ACTION_VIEW, uri); 

startActivity(it); 

// 패키지명은 어플리케이션의 전체 패키지명을 입력해야 합니다.

 

 

// 마켓 어플리케이션 상세 화면

Uri uri = Uri.parse("market://details?id=어플리케이션아이디"); 

Intent it = new Intent(Intent.ACTION_VIEW, uri); 

startActivity(it);

// 아이디의 경우 마켓 퍼블리싱사이트의 어플을 선택후에 URL을 확인해보면 알 수 있습니다.

 

 

// 구글 검색

Intent intent = new Intent();

intent.setAction(Intent.ACTION_WEB_SEARCH);

intent.putExtra(SearchManager.QUERY,"searchString")

startActivity(intent);

sendSMS(PhoneNum, message); // 번호와 메시지로 호출 하시면 됩니다.

 

    //---SMS 전송---
    private void sendSMS(String phoneNumber, String message)
    {       
        String SENT = "SMS_SENT";
        String DELIVERED = "SMS_DELIVERED";
                
        PendingIntent sentPI = PendingIntent.getBroadcast(this, 0,
            new Intent(SENT), 0);
 
        PendingIntent deliveredPI = PendingIntent.getBroadcast(this, 0,
            new Intent(DELIVERED), 0);
 
        //---when the SMS has been sent---
        registerReceiver(new BroadcastReceiver(){
         
            @Override
            public void onReceive(Context arg0, Intent arg1) {
                switch (getResultCode())
                {
                    case Activity.RESULT_OK:
                        Toast.makeText(getBaseContext(), "SMS sent",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                        Toast.makeText(getBaseContext(), "Generic failure",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case SmsManager.RESULT_ERROR_NO_SERVICE:
                        Toast.makeText(getBaseContext(), "No service",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case SmsManager.RESULT_ERROR_NULL_PDU:
                        Toast.makeText(getBaseContext(), "Null PDU",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case SmsManager.RESULT_ERROR_RADIO_OFF:
                        Toast.makeText(getBaseContext(), "Radio off",
                                Toast.LENGTH_SHORT).show();
                        break;
                }
            }
        }, new IntentFilter(SENT));
                
        //---when the SMS has been delivered---
        registerReceiver(new BroadcastReceiver(){
            @Override
            public void onReceive(Context arg0, Intent arg1) {
                switch (getResultCode())
                {
                    case Activity.RESULT_OK:
                        Toast.makeText(getBaseContext(), "SMS delivered",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case Activity.RESULT_CANCELED:
                        Toast.makeText(getBaseContext(), "SMS not delivered",
                                Toast.LENGTH_SHORT).show();
                        break;                       
                }
            }
        }, new IntentFilter(DELIVERED));
                
        SmsManager sms = SmsManager.getDefault();
        sms.sendTextMessage(phoneNumber, null, message, sentPI, deliveredPI);        
    }       

 

+ Recent posts