79

I want to read MMS data I have seen the part table in the mmssms.db where the mms entries are stored; I am using a cursor and I want to know the appropriate URI; I am using "content://mms-sms/conversations" and the Column names of "Address"(Sent to), "Text" or "Subject" and "Data" column name of image.

I have seen the schema of mmssms.db and Their Column of part Table.

Cristian
  • 198,401
  • 62
  • 356
  • 264
user321373
  • 1,061
  • 3
  • 10
  • 11
  • The `mmssms.db` database is part of the firmware and is not accessible by Android applications. The `content://mms-sms/conversations` content provider is not part of the SDK and should not be accessed by Android applications. – CommonsWare Jun 10 '10 at 11:12
  • I am doing something similar HERE! http://stackoverflow.com/questions/11556633/parse-application-smil-mms-mime-type-on-android – Etienne Lawlor Jul 30 '12 at 07:48

5 Answers5

290

It's kind of difficult to find documentation about this, so I will collect here all information I have found. If you are in a rush or just don't like to read, jump to the How to get data from a SMS section.

content://mms-sms/conversations

This is the URI of the Mms and SMS provider... which allows us to query the MMS and SMS databases at the same time, and mix them in a single thread (which are called conversations).

Why is the URI important? Well, that's the standard way of getting MMS and SMS messages; for instance, when you receive a SMS and click on the notification bar, it will send a broadcast intent like this: content://mms-sms/conversations/XXX, where XXX is the id of the conversation.

Get a list of all conversations

The only thing you have to do is to query the 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 the projection parameter. However, you cannot do that with this provider, so that's why I'm using *.

Now you can loop through the Cursor as usual. These are the more important columns you would want to use:

  • _id is the ID of the message. Captain obvious to the rescue? Not really. This ID can be used to retrieve detailed information using either content://sms or content://mms.
  • date no explanation needed.
  • thread_id is the ID of the conversation
  • body The content of the last SMS on this conversation. If it's an MMS, even if it has a text part, this will be null.

Note: if you query 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 query content://mms-sms/conversations/xxx it will return each SMS and/or MMS on the conversation whose ID is xxx.

How to differentiate between SMS and MMS

Usually, you will want to know which type of message you are handling. Documentation says:

A virtual column, MmsSms.TYPE_DISCRIMINATOR_COLUMN, may be requested in the projection for a query. Its value is either "mms" or "sms", depending on whether the message represented by the row is an MMS message or an SMS message, respectively.

I think it's referring to this variable... however I have not been able to make it work. If you have please tell me how or edit this post.

So far this is what I have done and it seems to work but there must 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's MMS
        } else {
            // it's SMS
        }
    } while (query.moveToNext());
}

How to get data from a SMS

So you have the ID of the SMS, then the only thing you have to do is:

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 a MMS data?

MMSs are a little bit different. They can be built with different parts (text, audio, images, etc.); so here will see how to retrieve each kind of data separately.

So let's guess we have the MMS id in the mmsId variable. We can get detailed information about this MMS by using the content://mms/ provider:

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

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

How to get text content from MMS

Here we have to use content://mms/part... for instance:

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 could contain different parts of text... but usually it'd be only one. So if you want to remove the loop it will work most of the times. This is how 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 image from MMS

It's the same than getting the text part... the only difference is that you will be looking for a different mime-type:

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

This is how 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, where xxx is the id of the MMS:

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

  • Can't understand why Google, with those thousands of millions of dollars, don't pay a student or someone else to document this API. You have to check the source code to know how it works and, which is worse, they don't make public those constants used in the columns of the database, so we have to write them manually.
  • For other kind of data inside an MMS you can apply the same idea learned above... it's just a matter of knowing the mime-type.
Bink
  • 1,934
  • 1
  • 25
  • 41
Cristian
  • 198,401
  • 62
  • 356
  • 264
  • 2
    Conserning content://mms-sms/conversations. This url contains list with all threads. But not separate messages (sms or mms). So there is no sense to get to know sms or mms it is while it is none of them. – Maxim Aug 08 '11 at 13:21
  • would there be a reason why all my MMS mime types come back as application/smil? – Justin Feb 28 '12 at 23:19
  • 2
    Justin, because MMS are stored in database as slideshow using SMIL. – Naba Mar 02 '12 at 05:59
  • You better create a question and share your code with us. How can we see what you are doing wrong if we do not know your code? – Cristian Apr 10 '12 at 14:50
  • Cristian, great post! Thanks. However, I was getting the wrong number (recipient's number) out of getAddressNumber(id). I removed the loop and it is working fine now. Any idea why? – Dawson Goodell Jun 10 '12 at 07:18
  • @Cristian: do you have any idea how to aproach "application/smil" type? i just made an mms with xperia x8.. and ct = "application/smil".. xml based with tags like – pulancheck1988 Jun 10 '12 at 12:47
  • No idea dude... have you tried looking for those files in the file system? If they exist you could just read the XML, parse it, etc. – Cristian Jun 10 '12 at 15:06
  • @Maxim if you use it in an adapter then there is. – vikki Jul 06 '12 at 04:57
  • I am also having trouble reading application/smil type MMS messages. What is the best approach to parsing this type of message since it seems that an application/smil type can contain text or images or even possibly video? – Etienne Lawlor Jul 06 '12 at 07:59
  • I am doing something similar HERE! http://stackoverflow.com/questions/11556633/parse-application-smil-mms-mime-type-on-android – Etienne Lawlor Jul 30 '12 at 07:48
  • Is there a way to tell a sent mms from a received one? – Edward Falk Nov 22 '12 at 20:09
  • content://mms-sms/conversations/36 is this query suppose to return all the content of mms ? – kamal_tech_view Apr 03 '13 at 10:45
  • How do you know whether the MMS was sent or received? – gonzobrains May 30 '13 at 00:34
  • any one come up with a good way to know whether it was sent or received? – user577732 Jul 15 '13 at 21:29
  • So much trouble into explaining this and you didnt do a very nice job. It is very confusing with all the variables with the same name and not providing a single class or piece of code making us go trough it. Which was what you said google was doing wrong. – JoaoFilipeClementeMartins Apr 26 '14 at 19:15
  • love the answer but the only thing now i can't figure out is how to delete single mms messages or even a whole thread. any pointers would be great! – Christopher Rivera Apr 28 '14 at 20:14
  • Is there a way to programmatically delete the mms message using _id or threadId? – ssk Aug 27 '14 at 19:20
  • code works fine but we are getting wrong mms. The scenario is if i send new mms while backuping the mms. – srihari Nov 24 '14 at 09:28
  • Actually date needs some explaination as sorting using this column doesn't work as it should. If there is normalized_date column, date for MMS is shorter than for SMS and MMS appears on end. Anyway thanks for such great explanation! – Eloar Jan 07 '15 at 13:17
  • how can i identify group sms? – syam vakkalanka Oct 23 '15 at 11:00
  • Oh my, if I could select an answer... This is an amazing post. – Chemistpp Mar 08 '16 at 12:47
  • 3
    `content://mms-sms/conversations` doesn't work on all phones (I have a Galaxy S6 and it doesn't). I had to use `content://mms/` for all of it. – KVISH Mar 26 '16 at 00:41
  • How do you get the id of the message? – Kristy Welsh Dec 01 '16 at 14:09
  • If you are looking for the last MMS message you may need to delay the DB read. If you read to quickly after a message is received then you may get the second to last message. At least, if you are reading MMS right after they are received. – Markymark Mar 17 '18 at 06:51
  • @EdwardFalk or anyone else - did you find a way to detect sent vs received MMS messages? – Casey Hancock Mar 20 '18 at 07:02
  • This doesn't seem to work on many Samsung Galaxy phones - see [here](https://stackoverflow.com/q/41154259/997940) and [here](https://stackoverflow.com/q/13553046/997940) for example. Couldn't find any workaround besides appending `?simple=true` to the URI, but then tc_t column (and probably other columns as well) is missing. – Yoav Feuerstein Jul 04 '18 at 13:00
  • 1
    Unless I'm mistaken, `MessageFormat.format("content://mms/{0}/addr", id);` will only work for IDs that are less than 1,000. Shouldn't it be `MessageFormat.format("content://mms/{0,number,#}/addr", id);`? – Nick Sep 14 '18 at 03:00
  • how to get videos and and other media like getting image? – Sagar Jan 10 '19 at 10:18
  • this requires SMS permissions, which most likely will not be approved within your app if trying to publish in the app store – d.moncada Jan 17 '20 at 03:22
  • If you're trying to read and parse incoming SMS and MMS data in Android 9.0 and 28+, I posted something here: https://stackoverflow.com/questions/63194135/how-to-notify-and-read-incoming-mms-and-sms-messages-on-android – C. Lightfoot Jul 31 '20 at 14:53
  • Any way to dump this data via ADB? I'm trying to research a potentially malicious text I received. – Austin Burk Dec 18 '20 at 18:04
  • Hello getting the bitmap is not working. It is not returning null, but it is just not the image when rendered in the image view. Nothiing rendered. Can you please tell me what might the potential issue ? – Wai Yan Hein Mar 14 '21 at 01:33
  • Hello, getting address is not working. Any idea? – Wai Yan Hein Mar 23 '21 at 12:51
  • Wow, best SMS / MMS documentation I have found so far! Thanks so much! – edgar_wideman Sep 30 '21 at 21:07
12

The answer by Christian is excellent. However, the method for getting the sender's address did not work for me. The Long.parseLong statement doesn't do anything except possibly throw an exception and new String(...) ?.

On my device the cursor count is 2 or more. The first typically has a "type" of 137 and the others have a "type" of 151. I cannot find where this is documented, but one can deduce 137 is "from" and 151 is "to". Thus, if I run the method as is, I do not get an exception, and it returns the last row, which is a recipient and only one of several in many cases.

Also AFAICT the selection is not necessary as all the rows have the same msg_id. However, it doesn't hurt.

This is what works for me to get the sender's 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 didn't care about whether it is all numeric, but I included a way to eliminate everything but numerals as a comment if that is desired. It can easily be modified to return all the recipients, as well.

I assume it worked for him. It looks like it would give the right answer if the exception occurred on the first row.

Kenneth Evans
  • 2,179
  • 19
  • 26
  • 1
    I wouldn't try to call parseLong on the contact_id field; treat it as a string. In fact, it's quite possible that it could be an email address or something, in the case of email-to-mms gateways. – Edward Falk Nov 22 '12 at 17:36
  • 5
    To clarify the `type` constants, they come from the [`PduHeaders`](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.0_r1/com/google/android/mms/pdu/PduHeaders.java) class: `0x97` / 151 is `PduHeaders.TO` and `0x89` / 137 is `PduHeaders.FROM`. Other valid values for reference are: `0x81` / 129 is `PduHeaders.BCC` and `0x82` / 130 is `PduHeaders.CC`. See also [Telephony.Mms.Addr](https://developer.android.com/reference/android/provider/Telephony.Mms.Addr.html). – zelanix Jun 18 '15 at 22:26
5

I've just been struggling with this; however, I finally got it to work and I thought this thread might benefit from my experience.

I could query on content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI) and get addresses and parts as helpfully described in the thread, but I found that this URI would not retrieve threads that only had MMS messages in them - for example, threads with more than two correspondents.

After doing some digging in the AOSP MMS app source, I found that it was using a variant on Telephony.Threads.CONTENT_URI to generate its conversation list - it was adding the parameter "simple" with the value "true". when I added this parameter, I found that the provider would query a completely different table, which did indeed have all the SMS and MMS threads in it.

This table has a completely different schema from the regular Telephony.Threads.CONTENT_URI one (???); this is the projection that the AOSP app is using --

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 ID of the thread - so an ID into Telephony.Sms.CONTENT_URI or Telephony.Mms.CONTENT_URI.

After I discovered this bizarre detail, things started to work a lot better! 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 instead.

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

I verified behaviour on Android 5.x and 7.x.

I hope this helps a bit more.

Jason Proctor
  • 61
  • 1
  • 5
3

I had to make some modifications in order to get this to work for me.

  1. When I retrieve the cursor.getString(cursor.getColumnIndex("type")) from the mms-sms/conversations content, ("content://mms-sms/conversations/") I test 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 an SMS, else it is an MMS. For MMS's you have to test for both mime types 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...
    
  2. When you use a ContentObserver to monitor the message content for changes, it fires several notifications for the same message. I use a static variable - in my case lastMMSID - to keep track of the message.
  3. This code works well to retrieve the content of both Inbound and Outbound messages. It is important to iterate through all the records that are returned by the "content://mms/part/" uri in order to get to the content - text and/or attachments - of the MMS.
  4. The only way that I could find that works pretty well to differentiate between inbound and outbound MMS's, is to test the null status of the "m_id" field of the mms-sms/conversations content.

    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 the Address Content does not like to be queried with a {" * "} parameter, but this works:-

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

If it is an outbound message, the "type" to look for will be 151. For an inbound message, the "type" will be 137. A fully functional piece of code 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;
}

To all the brave warriors who have gone before me in this post - I thank thee from the bottom of my heart!

Gustav
  • 139
  • 1
  • 5
1

The answer given above for getting the getMMSAddress() should not contain the loop while (cursor.moveToNext());. It should only extract the address from the first element in the cursor. For some reason that is unknown to me, this cursor has more than one record. The first one contains the Sender's address. The other elements of the cursor beyond the first one, contain the receiver's address. Thus the code as is return the receivers address and not the sender address.

This has been very helpful for cracking open the contents of an MMS.