Chemistpp Chemistpp - 1 month ago 11
Android Question

Find and interate all SMS/MMS messages in android

First and foremost, I found this answer particularly helpful. However, it made me wonder how one goes about finding such information.

I can't seem to figure out how to iterate all the messages in my inbox. My current solution uses

Uri.parse("content://mms-sms/conversations")
in which I give use "_id" and "ct_t". However, it seems I only find the three conversations in my phone despite having 30 msges (20 of them in the save conversation thread and the others divided between two other conversations). Would make sense for such a statement
content://mms-sms/conversations
. However, the other providers seem to deal only with SMS OR MMS. Isn't there a way to just iterate the entire list of messages in this fashion where I replace
"content://mms-sms/conversations"
with something else?

public boolean refresh() {
final String[] proj = new String[]{"_id","ct_t"};
cursor = cr.query(Uri.parse("content://mms-sms/conversations"),proj,null,null,null);
if(!(cursor.moveToFirst())) {
empty = true;
cursor.close();
return false;
}
return true;
}


I iterate the messages with a next function

public boolean next() {

if(empty) {
cursor.close();
return false;
}
msgCnt = msgCnt + 1;

Msg msg;
String msgData = cursor.getString(cursor.getColumnIndex("ct_t"));
if("application/cnd.wap.multipart.related".equals(msgData)) {
msg = ParseMMS(cursor.getString(cursor.getColumnIndex("_id")));
} else {
msg = ParseSMS(cursor.getString(cursor.getColumnIndex("_id")));
}


if(!(cursor.moveToNext())) {
empty = true;
cursor.close();
return false;
}

return true;
}

Answer

Well, what I am asking doesn't really seem possible.
For those just starting out on such tasks, it's advisable to learn about how content providers work in general. Each Uri value added to the query returns access to specific tables.

Spending some time looking at the different Telephony.Mmssms tables that one can access and it seems, from my testing, that the only table you can access is using "content://mms-sms/conversations as using "content://mms-sms" leads to a null cursor.

Such is life, and it doesn't really make sense to iterate the messages that way since the content and method of extracting the data differ greatly based on whether or not the msg is an SMS or MMS message. It makes sense to iterate and parse SMS and MMS messages separately and store the interesting data into the same class object type for one to manipulate how they would like at a later date.

Useful to such a topic would be the Telephony.Sms documentation. Which is where one can find a descriptions of the column index fields. You can find the same information for Telephony.Mms as well as the sub table Telephony.Mms.Part, with links to each of the base columns to describe the information.

With this being said, here is a solution to the question How can I iterate all the SMS/MMS messages in the phone? and here is the solution that worked for me.

public class Main extends AppCompatActivity {
    //Not shown, Overrides,  button to call IterateAll();
    //implementations to follow
    IterateAll();
    public void ScanMMS();
    public void ScanSMS();
    public void ParseMMS(Msg msg);
    public Bitmap getMmsImg(String id);
    public String getMmsAddr(String id);
}

IterateAll() just calls the two different functions

IterateAll() {
    ScanMMS();
    ScanSMS();
}

ScanMMS() will iterate through the content://mms table extracting the data from each MMS.

public void ScanMMS() {
        System.out.println("==============================ScanMMS()==============================");
        //Initialize Box
        Uri uri = Uri.parse("content://mms");
        String[] proj = {"*"};
        ContentResolver cr = getContentResolver();

        Cursor c = cr.query(uri, proj, null, null, null);

        if(c.moveToFirst()) {
            do {
                /*String[] col = c.getColumnNames();
                String str = "";
                for(int i = 0; i < col.length; i++) {
                    str = str + col[i] + ": " + c.getString(i) + ", ";
                }
                System.out.println(str);*/
                //System.out.println("--------------------MMS------------------");
                Msg msg = new Msg(c.getString(c.getColumnIndex("_id")));
                msg.setThread(c.getString(c.getColumnIndex("thread_id")));
                msg.setDate(c.getString(c.getColumnIndex("date")));
                msg.setAddr(getMmsAddr(msg.getID()));


                ParseMMS(msg);
                //System.out.println(msg);
            } while (c.moveToNext());
        }

        c.close();

    }

}

As one can see, a lot of the important MMS data is in this table, such as the date of the message, the message id and the thread id. You need to use that message ID to pull more information from MMS.

The MMS message is divided into smaller parts of data. Each part contains something different, like an image, or a text portion. You have to iterate each part as I do below.

public void ParseMMS(Msg msg) {
        Uri uri = Uri.parse("content://mms/part");
        String mmsId = "mid = " + msg.getID();
        Cursor c = getContentResolver().query(uri, null, mmsId, null, null);
        while(c.moveToNext()) {
/*          String[] col = c.getColumnNames();
            String str = "";
            for(int i = 0; i < col.length; i++) {
                str = str + col[i] + ": " + c.getString(i) + ", ";
            }
            System.out.println(str);*/

            String pid = c.getString(c.getColumnIndex("_id"));
            String type = c.getString(c.getColumnIndex("ct"));
            if ("text/plain".equals(type)) {
                msg.setBody(msg.getBody() + c.getString(c.getColumnIndex("text")));
            } else if (type.contains("image")) {
                msg.setImg(getMmsImg(pid));
            }


        }
        c.close();
        return;
    }

Each part as the mid field which corresponds to the id of the message found earlier. We search the MMS part library only for that mms id and then iterate the different parts found. ct or content_type as described in the documentation described what the part is, i.e. text, image, etc. I scan the type to see what to do with that part. If it's plain text, I add that text to the current message body (apparently there can be multiple text parts, but I haven't seen it, but I believe it) and if it's an image, than load the image into a bitmap. I imagine Bitmaps will be easy to send with java to my computer, but who knows, maybe want to just load it as a byte array.

Anyway, here is how one will get the image data from the MMS part.

public Bitmap getMmsImg(String id) {
    Uri uri = Uri.parse("content://mms/part/" + id);
    InputStream in = null;
    Bitmap bitmap = null;

    try {
        in = getContentResolver().openInputStream(uri);
        bitmap = BitmapFactory.decodeStream(in);
        if(in != null)
            in.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return bitmap;
}

You know, I'm not entirely sure how opening an input stream on the content resolver really works and how it is giving me just the image and not like all the other data, no clue, but it seems to work. I stole this one from some different sources while looking for solutions.

The MMS addresses aren't as straight forward to pull as they are for SMS, but here is how you can get them all. The only thing I haven't been able to do is figure out who the sender was. I'd love it if someone knew that.

public String getMmsAddr(String id) {
        String sel = new String("msg_id=" + id);
        String uriString = MessageFormat.format("content://mms/{0}/addr", id);
        Uri uri = Uri.parse(uriString);
        Cursor c = getContentResolver().query(uri, null, sel, null, null);
        String name = "";
        while (c.moveToNext()) {
/*          String[] col = c.getColumnNames();
            String str = "";
            for(int i = 0; i < col.length; i++) {
                str = str + col[i] + ": " + c.getString(i) + ", ";
            }
            System.out.println(str);*/
            String t = c.getString(c.getColumnIndex("address"));
            if(!(t.contains("insert")))
                name = name + t + " ";
        }
        c.close();
        return name;
}

This was all just for MMS. The good news is that SMS is much simpler.

public void ScanSMS() {
        System.out.println("==============================ScanSMS()==============================");
        //Initialize Box
        Uri uri = Uri.parse("content://sms");
        String[] proj = {"*"};
        ContentResolver cr = getContentResolver();

        Cursor c = cr.query(uri,proj,null,null,null);

        if(c.moveToFirst()) {
            do {
                String[] col = c.getColumnNames();
                String str = "";
                for(int i = 0; i < col.length; i++) {
                    str = str + col[i] + ": " + c.getString(i) + ", ";
                }
                //System.out.println(str);

                System.out.println("--------------------SMS------------------");

                Msg msg = new Msg(c.getString(c.getColumnIndex("_id")));
                msg.setDate(c.getString(c.getColumnIndex("date")));
                msg.setAddr(c.getString(c.getColumnIndex("Address")));
                msg.setBody(c.getString(c.getColumnIndex("body")));
                msg.setDirection(c.getString(c.getColumnIndex("type")));
                msg.setContact(c.getString(c.getColumnIndex("person")));
                System.out.println(msg);


            } while (c.moveToNext());
        }
        c.close();
}

Here is my simple message structure so anyone may compile the above code quickly if wanted.

import android.graphics.Bitmap;

/**
 * Created by rbenedict on 3/16/2016.
 */

//import java.util.Date;

public class Msg {
    private String id;
    private String t_id;
    private String date;
    private String dispDate;
    private String addr;
    private String contact;
    private String direction;
    private String body;
    private Bitmap img;
    private boolean bData;
    //Date vdat;

    public Msg(String ID) {
        id = ID;
        body = "";
    }

    public void setDate(String d) {
        date = d;
        dispDate = msToDate(date);
    }
    public void setThread(String d) { t_id = d; }

    public void setAddr(String a) {
        addr = a;
    }
    public void setContact(String c) {
        if (c==null) {
            contact = "Unknown";
        } else {
            contact = c;
        }
    }
    public void setDirection(String d) {
        if ("1".equals(d))
            direction = "FROM: ";
        else
            direction = "TO: ";

    }
    public void setBody(String b) {
        body = b;
    }
    public void setImg(Bitmap bm) {
        img = bm;
        if (bm != null)
            bData = true;
        else
            bData = false;
    }

    public String getDate() {
        return date;
    }
    public String getDispDate() {
        return dispDate;
    }
    public String getThread() { return t_id; }
    public String getID() { return id; }
    public String getBody() { return body; }
    public Bitmap getImg() { return img; }
    public boolean hasData() { return bData; }

    public String toString() {

        String s = id + ". " + dispDate + " - " + direction + " " + contact + " " + addr + ": "  + body;
        if (bData)
            s = s + "\nData: " + img;
        return s;
    }

    public String msToDate(String mss) {

        long time = Long.parseLong(mss,10);

        long sec = ( time / 1000 ) % 60;
        time = time / 60000;

        long min = time % 60;
        time = time / 60;

        long hour = time % 24 - 5;
        time = time / 24;

        long day = time % 365;
        time = time / 365;

        long yr = time + 1970;

        day = day - ( time / 4 );
        long mo = getMonth(day);
        day = getDay(day);

        mss = String.valueOf(yr) + "/" + String.valueOf(mo) + "/" + String.valueOf(day) + " " + String.valueOf(hour) + ":" + String.valueOf(min) + ":" + String.valueOf(sec);

        return mss;
    }
    public long getMonth(long day) {
        long[] calendar = {31,28,31,30,31,30,31,31,30,31,30,31};
        for(int i = 0; i < 12; i++) {
            if(day < calendar[i]) {
                return i + 1;
            } else {
                day = day - calendar[i];
            }
        }
        return 1;
    }
    public long getDay(long day) {
        long[] calendar = {31,28,31,30,31,30,31,31,30,31,30,31};
        for(int i = 0; i < 12; i++) {
            if(day < calendar[i]) {
                return day;
            } else {
                day = day - calendar[i];
            }
        }
        return day;
    }



}

Some final comments and notes on this solution.

The person field seems to always be NULL and later I plan to implement a contact look up. I also haven't been able to identify who sent the MMS message.

I am not super familiar with java and I am still learning it. I am positive there is a data container (ArrayList) (Vector?) that could hold a user defined object. And if sortable by a specific field in the object (date), one could iterate that list and have a chronological order of all the message: both MMS/SMS and both sent/received.