How to read MMS data on Android?

I want to read MMS data. I saw the parts table in mmssms.db where the mms records are stored; I am using a cursor and I want to know the corresponding URI ; I use “content: // mms-sms / conversations” and the column names of the column names are “Address” (sent to), “Text” or “Subject” and “Data”.

I saw the mmssms.db and their detail table column.

+63
android mms
Jun 10 '10 at 7:00
source share
5 answers

It is hard to find documentation about this, so I am collecting here all the information I found. If you are in a hurry or just do not like to read, go to How to get data from the SMS section .

content: // MMS-SMS / conversations

This is the URI of the Mms and SMS provider ... which allows us to simultaneously query the MMS and SMS databases, and mix them in one stream (called chains).

Why is it important? Well, this is the standard way to receive MMS and SMS messages; for example, when you receive an SMS message and click on the notification panel, it sends the broadcast intent as follows: content://mms-sms/conversations/XXX , where XXX is the identifier of the conversation.

Get a list of all conversations

The only thing you need to do is request content://mms-sms/conversations Uri:

 ContentResolver contentResolver = getContentResolver(); final String[] projection = new String[]{"*"}; Uri uri = Uri.parse("content://mms-sms/conversations/"); Cursor query = contentResolver.query(uri, projection, null, null, null); 

Note: usually when you call query and want to return all columns, you can pass null as a parameter to projection . However, you cannot do this with this provider, so I use * .

Now you can scroll through the Cursor as usual. These are the most important columns you would like to use:

  • _id is the message identifier. Is the captain explicit for salvation? Not really. This identifier can be used to get detailed information using either content://sms or content://mms .
  • date no explanation required.
  • thread_id - conversation id
  • body The content of the last SMS in this conversation. If it is MMS, even if it has a text part, it will be null .

Note: if you request content://mms-sms/conversations , it will return a list of different conversations, whose _id is the last SMS or MMS in each conversation. If you request content://mms-sms/conversations/XXX , it will return each SMS and / or MMS to the conversation whose identifier is XXX .

How to distinguish between SMS and MMS

Usually you need to know what type of message you are processing. The documentation reads:

The virtual column, MmsSms.TYPE_DISCRIMINATOR_COLUMN , can be queried in the projection for the query. Its value is either "mms" or "sms", depending on whether the message presented next is an MMS message or an SMS message, respectively.

I think this refers to this variable ... however, I could not get it to work. If you tell me how to change this or edit this post.

So far this is what I have done and it seems to work, but there should be better ways:

 ContentResolver contentResolver = getContentResolver(); final String[] projection = new String[]{"_id", "ct_t"}; Uri uri = Uri.parse("content://mms-sms/conversations/"); Cursor query = contentResolver.query(uri, projection, null, null, null); if (query.moveToFirst()) { do { String string = query.getString(query.getColumnIndex("ct_t")); if ("application/vnd.wap.multipart.related".equals(string)) { // it MMS } else { // it SMS } } while (query.moveToNext()); } 

How to get data from SMS

So, you have an SMS identifier, then you only need:

 String selection = "_id = "+id; Uri uri = Uri.parse("content://sms"); Cursor cursor = contentResolver.query(uri, null, selection, null, null); String phone = cursor.getString(cursor.getColumnIndex("address")); int type = cursor.getInt(cursor.getColumnIndex("type"));// 2 = sent, etc. String date = cursor.getString(cursor.getColumnIndex("date")); String body = cursor.getString(cursor.getColumnIndex("body")); 

How to get data from MMS data?

MMS are a little different. They can be built with various parts (text, audio, images, etc.); therefore, it will show how to retrieve each kind of data separately.

So let's assume that I have an MMS identifier in the mmsId variable. We can get detailed information about this MMS using the provider content://mms/ :

 Uri uri = Uri.parse("content://mms/"); String selection = "_id = " + mmsId; Cursor cursor = getContentResolver().query(uri, null, selection, null, null); 

However, only an interesting read column, which is 1 if the message has already been read.

How to get text content from MMS

Here we should use content://mms/part ... for example:

 String selectionPart = "mid=" + mmsId; Uri uri = Uri.parse("content://mms/part"); Cursor cursor = getContentResolver().query(uri, null, selectionPart, null, null); if (cursor.moveToFirst()) { do { String partId = cursor.getString(cursor.getColumnIndex("_id")); String type = cursor.getString(cursor.getColumnIndex("ct")); if ("text/plain".equals(type)) { String data = cursor.getString(cursor.getColumnIndex("_data")); String body; if (data != null) { // implementation of this method below body = getMmsText(partId); } else { body = cursor.getString(cursor.getColumnIndex("text")); } } } while (cursor.moveToNext()); } 

It may contain different parts of the text ... but usually it will be only one. Therefore, if you want to delete a loop, it will work most of the time. Here's what the getMmsText method looks like:

 private String getMmsText(String id) { Uri partURI = Uri.parse("content://mms/part/" + id); InputStream is = null; StringBuilder sb = new StringBuilder(); try { is = getContentResolver().openInputStream(partURI); if (is != null) { InputStreamReader isr = new InputStreamReader(is, "UTF-8"); BufferedReader reader = new BufferedReader(isr); String temp = reader.readLine(); while (temp != null) { sb.append(temp); temp = reader.readLine(); } } } catch (IOException e) {} finally { if (is != null) { try { is.close(); } catch (IOException e) {} } } return sb.toString(); } 

How to get an image from MMS

This is the same as getting the text part ... The only difference is that you will look for another type of mime:

 String selectionPart = "mid=" + mmsId; Uri uri = Uri.parse("content://mms/part"); Cursor cPart = getContentResolver().query(uri, null, selectionPart, null, null); if (cPart.moveToFirst()) { do { String partId = cPart.getString(cPart.getColumnIndex("_id")); String type = cPart.getString(cPart.getColumnIndex("ct")); if ("image/jpeg".equals(type) || "image/bmp".equals(type) || "image/gif".equals(type) || "image/jpg".equals(type) || "image/png".equals(type)) { Bitmap bitmap = getMmsImage(partId); } } while (cPart.moveToNext()); } 

Here's what the getMmsImage method looks like:

 private Bitmap getMmsImage(String _id) { Uri partURI = Uri.parse("content://mms/part/" + _id); InputStream is = null; Bitmap bitmap = null; try { is = getContentResolver().openInputStream(partURI); bitmap = BitmapFactory.decodeStream(is); } catch (IOException e) {} finally { if (is != null) { try { is.close(); } catch (IOException e) {} } } return bitmap; } 

How to get the sender address

You will need to use the content://mms/xxx/addr provider content://mms/xxx/addr , where XXX is the MMS identifier:

 private String getAddressNumber(int id) { String selectionAdd = new String("msg_id=" + id); String uriStr = MessageFormat.format("content://mms/{0}/addr", id); Uri uriAddress = Uri.parse(uriStr); Cursor cAdd = getContentResolver().query(uriAddress, null, selectionAdd, null, null); String name = null; if (cAdd.moveToFirst()) { do { String number = cAdd.getString(cAdd.getColumnIndex("address")); if (number != null) { try { Long.parseLong(number.replace("-", "")); name = number; } catch (NumberFormatException nfe) { if (name == null) { name = number; } } } } while (cAdd.moveToNext()); } if (cAdd != null) { cAdd.close(); } return name; } 

Final thoughts

  • I can’t understand why Google, with thousands of millions of dollars, is not paying a student or anyone else to document this API. You must check the source code to find out how it works, and, even worse, they will not publicize the constants used in the database columns, so we must write them manually.
  • For other types of data inside MMS, you can apply the same thought as above ... it's just a matter of knowledge like mime.
+240
Jun 22 '11 at 21:32
source share

The answer from Christian is excellent. However, the method of obtaining the sender address did not work for me. The Long.parseLong statement does nothing except possibly throw an exception and a new String (...) ?.

On my device, the number of cursors is 2 or more. The first usually has a “type” of 137, and the rest have a “type” of 151. I cannot find where this is documented, but we can conclude 137 from, and 151 to. Thus, if I run the method as is, I do not get an exception, and it returns the last line, which is the receiver, and only one of several in many cases.

Also AFAICT selection is not required, since all lines have the same msg_id. However, this does not hurt.

This is what works for me to get the sender address:

 public static String getMMSAddress(Context context, String id) { String addrSelection = "type=137 AND msg_id=" + id; String uriStr = MessageFormat.format("content://mms/{0}/addr", id); Uri uriAddress = Uri.parse(uriStr); String[] columns = { "address" }; Cursor cursor = context.getContentResolver().query(uriAddress, columns, addrSelection, null, null); String address = ""; String val; if (cursor.moveToFirst()) { do { val = cursor.getString(cursor.getColumnIndex("address")); if (val != null) { address = val; // Use the first one found if more than one break; } } while (cursor.moveToNext()); } if (cursor != null) { cursor.close(); } // return address.replaceAll("[^0-9]", ""); return address; } 

I don't care if this is all a number, but I have included a way to exclude everything but numbers as a comment if necessary. It can be easily changed to return all recipients.

I guess it worked for him. It looks like this will give the correct answer if the exception occurred on the first line.

+6
Oct 12 '12 at 19:19
source share

I had to make some changes to make this work for me.

  • When I get the .getString cursor (cursor.getColumnIndex ("type")) from the contents of mms-sms / conversations ("content: // mms-sms / conversations /"), I check the value of the "type" field for null. If the variable is null - i.e.

     String otype = c.getString(c.getColumnIndex("type")); if(otype != null) { //this is an sms - handle it... 

    the message is SMS, otherwise it is MMS. For MMS, you need to check both types of mime as follows: -

     if (("application/vnd.wap.multipart.related".equalsIgnoreCase(msg_type) ||"application/vnd.wap.multipart.mixed".equalsIgnoreCase(msg_type)) && !id.equalsIgnoreCase(lastMMSID)) { //this is a MMS - handle it... 
  • When you use ContentObserver to track message content for changes, it triggers multiple notifications for the same message. I use a static variable - in my case lastMMSID - to track the message.
  • This code works well for retrieving the contents of both inbound and outbound messages. It is important to iterate over all records returned by the "content: // mms / part /" uri in order to get to the contents - text and / or attachments - from MMS.
  • The only way I could work well to distinguish between incoming and outgoing MMS is to check the zero status of the "m_id" field of the contents of mms-sms / conversations.

     String m_id = c.getString(c.getColumnIndex("m_id")); String mDirection = m_id == null? "OUT": "IN"; 

A final thought on how to get the address field. For some reason, addressable content does not like to request the {"*"} parameter, but it works: -

 final String[] projection = new String[] {"address", "contact_id", "charset", "type"}; 

If this is an outgoing message, the type for the search will be 151. For the incoming message, the type will be 137. A fully functional code fragment will look something like this: -

 private String getANumber(int id) { String add = ""; final String[] projection = new String[] {"address","contact_id","charset","type"}; final String selection = "type=137 or type=151"; // PduHeaders Uri.Builder builder = Uri.parse("content://mms").buildUpon(); builder.appendPath(String.valueOf(id)).appendPath("addr"); Cursor cursor = context.getContentResolver().query( builder.build(), projection, selection, null, null); if (cursor.moveToFirst()) { do { String add = cursor.getString(cursor.getColumnIndex("address")); String type: cursor.getString(cursor.getColumnIndex("type")); } while(cursor.moveToNext()); } // Outbound messages address type=137 and the value will be 'insert-address-token' // Outbound messages address type=151 and the value will be the address // Additional checking can be done here to return the correct address. return add; } 

For all the brave warriors who crossed before me in this post - I thank you with all my heart!

+3
Apr 18 '13 at 8:44
source share

The answer above to get getMMSAddress () should not contain a while loop (cursor.moveToNext ()) ;. It should retrieve only the address from the first element in the cursor. For some reason this is not known to me, this cursor has more than one record. The first contains the sender address. The remaining cursor elements outside the first contain the recipient address. Thus, the as code returns the address of the recipients, not the address of the sender.

This is very useful for hacking the contents of MMS content.

+1
Feb 13 '13 at 15:09
source share

I just struggled with this; however, I finally got it to work, and I thought this topic could benefit from my experience.

I could request content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI) and get the addresses and parts as described in the stream, but I found that this URI will not receive streams that have only MMS -Messages, for example, streams with more than two correspondents.

After some digging in the source of the AOSP MMS application, I found that he used the Telephony.Threads.CONTENT_URI option to generate his conversation list - he added the “simple” parameter with the value “true”. when I added this parameter, I found that the provider will request a completely different table, which really has all the SMS and MMS streams.

This table has a completely different scheme from the usual Telephony.Threads.CONTENT_URI one (???); this is the projection that the AOSP application uses -

 public static final String[] ALL_THREADS_PROJECTION = { Threads._ID, Threads.DATE, Threads.MESSAGE_COUNT, Threads.RECIPIENT_IDS, Threads.SNIPPET, Threads.SNIPPET_CHARSET, Threads.READ, Threads.ERROR, Threads.HAS_ATTACHMENT }; 

The _ID here is the stream identifier - therefore the identifier in Telephony.Sms.CONTENT_URI or Telephony.Mms.CONTENT_URI.

After I discovered this strange detail, everything began to work much better! Please note, however, that the DATE column in the "simple = true" variant is not reliable, I had to use the date from the most recent Sms or Mms message.

Another thing that I should probably mention is that in order to get the right list of messages for a particular thread, I had to query both the Mms and Sms providers, and then combine the results into a single list and then sort them by date.

I checked the behavior on Android 5.x and 7.x.

Hope this helps a bit more.

+1
Feb 27 '17 at 23:23
source share



All Articles