Wie lese ich MMS-Daten in Android?

74

Ich möchte MMS-Daten lesen. Ich habe mmssms.dbdie Teiletabelle gesehen, in der die MMS- Einträge gespeichert sind. Ich benutze einen Cursor und möchte das entsprechende wissen URI; Ich verwende "Inhalt: // mms-sms / Konversationen" und die Spaltennamen "Adresse" (gesendet an), "Text" oder "Betreff" und "Daten" Spaltenname des Bildes.

Ich habe das Schema mmssms.dbund die Spalte der Teiletabelle gesehen.

user321373
quelle
Die mmssms.dbDatenbank ist Teil der Firmware und für Android-Anwendungen nicht zugänglich. Der content://mms-sms/conversationsInhaltsanbieter ist nicht Teil des SDK und sollte nicht von Android-Anwendungen aufgerufen werden.
CommonsWare
Ich mache HIER etwas Ähnliches! stackoverflow.com/questions/11556633/…
toobsco42

Antworten:

277

Es ist schwierig, eine Dokumentation darüber zu finden, deshalb werde ich hier alle Informationen sammeln, die ich gefunden habe. Wenn Sie in Eile sind oder einfach nicht lesen möchten, springen Sie zum Abschnitt So erhalten Sie Daten aus einer SMS .

Inhalt: // mms-sms / gespräche

Dies ist die URI des MMS- und SMS-Anbieters ... mit der wir die MMS- und SMS-Datenbanken gleichzeitig abfragen und in einem einzigen Thread (sogenannte Konversationen ) mischen können .

Warum ist die URI wichtig? Nun, das ist die Standardmethode, um MMS- und SMS-Nachrichten zu erhalten. Wenn Sie beispielsweise eine SMS empfangen und auf die Benachrichtigungsleiste klicken, wird eine Broadcast-Absicht wie folgt gesendet: content://mms-sms/conversations/XXXWo XXXist die ID der Konversation?

Holen Sie sich eine Liste aller Gespräche

Das einzige, was Sie tun müssen, ist die content://mms-sms/conversationsUri abzufragen :

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

Hinweis: Wenn Sie queryalle Spalten aufrufen und zurückgeben möchten, können Sie sie normalerweise nullals projectionParameter übergeben. Bei diesem Anbieter ist dies jedoch nicht möglich. Deshalb verwende ich *.

Jetzt können Sie das Cursorwie gewohnt durchlaufen . Dies sind die wichtigeren Spalten, die Sie verwenden möchten:

  • _idist die ID der Nachricht. Kapitän offensichtlich zur Rettung? Nicht wirklich. Diese ID kann verwendet werden, um detaillierte Informationen mit entweder content://smsoder abzurufen content://mms.
  • date Keine Erklärung erforderlich.
  • thread_id ist die ID des Gesprächs
  • bodyDer Inhalt der letzten SMS zu diesem Gespräch. Wenn es sich um eine MMS handelt, auch wenn sie einen Textteil enthält, ist dies der Fall null.

Hinweis: Wenn Sie eine Abfrage durchführen content://mms-sms/conversations, wird eine Liste verschiedener Konversationen zurückgegeben, bei denen _ides sich um die letzte SMS oder MMS in jeder Konversation handelt. Wenn Sie abfragen content://mms-sms/conversations/xxx, wird jede SMS und / oder MMS in der Konversation zurückgegeben, deren ID lautet xxx.

So unterscheiden Sie zwischen SMS und MMS

Normalerweise möchten Sie wissen, welche Art von Nachricht Sie bearbeiten. Die Dokumentation sagt:

Eine virtuelle Spalte MmsSms.TYPE_DISCRIMINATOR_COLUMNkann in der Projektion für eine Abfrage angefordert werden. Sein Wert ist entweder "mms" oder "sms", abhängig davon, ob die durch die Zeile dargestellte Nachricht eine MMS-Nachricht oder eine SMS-Nachricht ist.

Ich denke, es bezieht sich auf diese Variable ... aber ich konnte es nicht zum Laufen bringen. Wenn Sie haben, sagen Sie mir bitte, wie oder bearbeiten Sie diesen Beitrag.

Bisher habe ich das getan und es scheint zu funktionieren, aber es muss bessere Wege geben:

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

So erhalten Sie Daten aus einer SMS

Sie haben also die ID der SMS und müssen dann nur noch Folgendes tun:

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"));

Wie erhalte ich Daten aus MMS-Daten?

MMS sind etwas anders. Sie können aus verschiedenen Teilen (Text, Audio, Bilder usw.) bestehen. Hier erfahren Sie, wie Sie jede Art von Daten separat abrufen.

Nehmen wir also an, wir haben die MMS-ID in der mmsIdVariablen. Detaillierte Informationen zu dieser MMS erhalten Sie über den content://mms/Anbieter:

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

Die einzige interessante Spalte ist readjedoch, 1ob die Nachricht bereits gelesen wurde.

So erhalten Sie Textinhalte von MMS

Hier müssen wir verwenden content://mms/part... zum Beispiel:

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

Es könnte verschiedene Textteile enthalten ... aber normalerweise ist es nur einer. Wenn Sie also die Schleife entfernen möchten, funktioniert dies meistens. So sieht die getMmsTextMethode aus:

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

So erhalten Sie ein Bild von MMS

Es ist dasselbe wie das Erhalten des Textteils ... der einzige Unterschied besteht darin, dass Sie nach einem anderen MIME-Typ suchen:

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

So sieht die getMmsImageMethode aus:

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

So erhalten Sie die Absenderadresse

Sie müssen den content://mms/xxx/addrAnbieter verwenden, wobei xxxdie ID der MMS lautet:

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

Abschließende Gedanken

  • Ich kann nicht verstehen, warum Google mit diesen Tausenden von Millionen Dollar keinen Studenten oder eine andere Person bezahlt, um diese API zu dokumentieren. Sie müssen den Quellcode überprüfen, um zu wissen, wie er funktioniert, und was noch schlimmer ist, sie machen die in den Spalten der Datenbank verwendeten Konstanten nicht öffentlich, daher müssen wir sie manuell schreiben.
  • Für andere Arten von Daten in einer MMS können Sie dieselbe Idee anwenden, die Sie oben gelernt haben. Es geht nur darum, den MIME-Typ zu kennen.
Cristian
quelle
2
Inhalt: // mms-sms / gespräche. Diese URL enthält eine Liste mit allen Threads. Aber keine separaten Nachrichten (SMS oder MMS). Es macht also keinen Sinn, SMS oder MMS kennenzulernen, solange es keiner von ihnen ist.
Maxim
Gibt es einen Grund, warum alle meine MMS-MIME-Typen als Anwendung / Lächeln zurückkommen?
Justin
2
Justin, weil MMS mit SMIL als Diashow in der Datenbank gespeichert werden.
Naba
3
content://mms-sms/conversationsfunktioniert nicht auf allen Handys (ich habe ein Galaxy S6 und es funktioniert nicht). Ich musste content://mms/für alles verwenden.
KVISH
1
Wenn ich mich nicht irre, MessageFormat.format("content://mms/{0}/addr", id);funktioniert dies nur für IDs mit weniger als 1.000. Sollte es nicht sein MessageFormat.format("content://mms/{0,number,#}/addr", id);?
Nick
9

Die Antwort von Christian ist ausgezeichnet. Die Methode zum Abrufen der Absenderadresse hat bei mir jedoch nicht funktioniert. Die Long.parseLong-Anweisung führt nur eine Ausnahme und einen neuen String (...) aus.

Auf meinem Gerät beträgt die Cursorzahl 2 oder mehr. Der erste hat typischerweise einen "Typ" von 137 und die anderen einen "Typ" von 151. Ich kann nicht finden, wo dies dokumentiert ist, aber man kann ableiten, dass 137 "von" und 151 "bis" ist. Wenn ich die Methode so ausführe, wie sie ist, erhalte ich keine Ausnahme und es wird die letzte Zeile zurückgegeben, die in vielen Fällen ein Empfänger und nur eine von mehreren ist.

Auch AFAICT die Auswahl ist nicht notwendig, da alle Zeilen die gleiche msg_id haben. Es tut jedoch nicht weh.

Dies funktioniert für mich, um die Absenderadresse zu erhalten:

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

Es war mir egal, ob alles numerisch ist, aber ich habe eine Möglichkeit eingefügt, alles außer Zahlen als Kommentar zu entfernen, wenn dies gewünscht wird. Es kann leicht geändert werden, um auch alle Empfänger zurückzugeben.

Ich nehme an, es hat bei ihm funktioniert. Es sieht so aus, als würde es die richtige Antwort geben, wenn die Ausnahme in der ersten Zeile auftritt.

Kenneth Evans
quelle
1
Ich würde nicht versuchen, parseLong im Feld contact_id aufzurufen. Behandle es als einen String. Tatsächlich ist es durchaus möglich, dass es sich bei E-Mail-zu-MMS-Gateways um eine E-Mail-Adresse oder etwas anderes handelt.
Edward Falk
4
Um die typeKonstanten zu verdeutlichen , stammen sie aus der PduHeadersKlasse: 0x97/ 151 ist PduHeaders.TOund 0x89/ 137 ist PduHeaders.FROM. Andere gültige Referenzwerte sind: 0x81/ 129 ist PduHeaders.BCCund 0x82/ 130 ist PduHeaders.CC. Siehe auch Telephony.Mms.Addr .
Zelanix
5

Ich habe gerade damit gekämpft; Ich habe es jedoch endlich zum Laufen gebracht und dachte, dieser Thread könnte von meiner Erfahrung profitieren.

Ich konnte content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI)Adressen und Teile abfragen und abrufen, wie im Thread hilfreich beschrieben, aber ich stellte fest, dass dieser URI keine Threads abruft, die nur MMS-Nachrichten enthalten - beispielsweise Threads mit mehr als zwei Korrespondenten.

Nachdem ich einige Male in der AOSP MMS-App-Quelle gesucht hatte, stellte ich fest, dass sie eine Variante Telephony.Threads.CONTENT_URIzum Generieren ihrer Konversationsliste verwendete - sie fügte den Parameter "simple" mit dem Wert "true" hinzu. Als ich diesen Parameter hinzufügte, stellte ich fest, dass der Anbieter eine völlig andere Tabelle abfragen würde, die tatsächlich alle SMS- und MMS-Threads enthielt.

Diese Tabelle hat ein völlig anderes Schema als die reguläre Telephony.Threads.CONTENT_URI one (???); Dies ist die Projektion, die die AOSP-App verwendet -

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

Die _ID hier ist die ID des Threads - also eine ID in Telephony.Sms.CONTENT_URI oder Telephony.Mms.CONTENT_URI.

Nachdem ich dieses bizarre Detail entdeckt hatte, begannen die Dinge viel besser zu funktionieren! Beachten Sie jedoch, dass die DATE-Spalte in der Variante "simple = true" nicht zuverlässig ist. Stattdessen musste ich das Datum aus der letzten SMS- oder Mms-Nachricht verwenden.

Eine andere Sache, die ich wahrscheinlich erwähnen sollte, ist, dass ich, um eine korrekte Liste von Nachrichten für einen bestimmten Thread zu erhalten, sowohl die Mms- als auch die SMS-Anbieter abfragen, die Ergebnisse in einer Liste zusammenfassen und sie dann nach Datum sortieren musste.

Ich habe das Verhalten unter Android 5.x und 7.x überprüft.

Ich hoffe das hilft ein bisschen mehr.

Jason Proctor
quelle
3

Ich musste einige Änderungen vornehmen, damit dies für mich funktioniert.

  1. Wenn ich den cursor.getString (cursor.getColumnIndex ("Typ")) aus dem Inhalt von mms-sms / gespräche ("content: // mms-sms / gespräche /") abrufe, teste ich den Wert des Feldes "Typ" für null. Wenn die Variable null ist - dh

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

    Die Nachricht ist eine SMS, sonst ist es eine MMS. Für MMS müssen Sie beide MIME-Typen wie folgt testen: -

    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. Wenn Sie einen ContentObserver verwenden, um den Nachrichteninhalt auf Änderungen zu überwachen, werden mehrere Benachrichtigungen für dieselbe Nachricht ausgelöst. Ich verwende eine statische Variable - in meinem Fall lastMMSID -, um die Nachricht zu verfolgen.
  3. Dieser Code eignet sich gut zum Abrufen des Inhalts von eingehenden und ausgehenden Nachrichten. Es ist wichtig, alle Datensätze zu durchlaufen, die von der Uri "content: // mms / part /" zurückgegeben werden, um zum Inhalt - Text und / oder Anhänge - der MMS zu gelangen.
  4. Die einzige Möglichkeit, die ich finden konnte, um zwischen eingehenden und ausgehenden MMS zu unterscheiden, besteht darin, den Nullstatus des Felds "m_id" des Inhalts von mms-sms / gespräche zu testen.

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

Ein letzter Gedanke, wie man das Adressfeld erhält. Aus irgendeinem Grund möchte der Adressinhalt nicht mit einem {"*"} Parameter abgefragt werden, aber dies funktioniert: -

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

Wenn es sich um eine ausgehende Nachricht handelt, ist der zu suchende "Typ" 151. Bei einer eingehenden Nachricht ist der "Typ" 137. Ein voll funktionsfähiger Code sieht ungefähr so ​​aus: -

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

An alle tapferen Krieger, die in diesem Beitrag vor mir gegangen sind - ich danke dir von ganzem Herzen!

Gustav
quelle
1

Die oben angegebene Antwort zum Abrufen von getMMSAddress () sollte die Schleife while (cursor.moveToNext ()) nicht enthalten. Es sollte nur die Adresse aus dem ersten Element im Cursor extrahieren. Aus einem mir unbekannten Grund hat dieser Cursor mehr als einen Datensatz. Die erste enthält die Adresse des Absenders. Die anderen Elemente des Cursors nach dem ersten enthalten die Adresse des Empfängers. Somit gibt der Code wie er ist die Empfängeradresse und nicht die Absenderadresse zurück.

Dies war sehr hilfreich, um den Inhalt einer MMS aufzubrechen.

Hassan Nabil
quelle