web-dev-qa-db-fra.com

Comment lire les données MMS dans Android?

Je veux lire les données MMS J'ai vu la table des pièces dans le mmssms.db où les entrées mms sont stockées; J'utilise un curseur et je veux connaître le URI approprié; J'utilise "content: // mms-sms/conversations" et le Noms des colonnes de "Adresse" (Envoyé à), "Texte" ou "Sujet" et le nom de la colonne "Données" de l'image.

J'ai vu le schéma de mmssms.db et leur colonne de la table des pièces.

71
user321373

C'est un peu difficile de trouver de la documentation à ce sujet, donc je vais rassembler ici toutes les informations que j'ai trouvées. Si vous êtes pressé ou que vous n'aimez pas lire, passez à la section Comment obtenir des données à partir d'un SMS .

contenu: // mms-sms/conversations

Il s'agit de l'URI du Mms et SMS ... qui nous permet d'interroger le MMS et SMS bases de données en même temps, et mélangez-les dans un seul thread (qui s'appellent conversations).

Pourquoi l'URI est-il important? Eh bien, c'est la façon standard d'obtenir MMS et SMS messages; par exemple, lorsque vous recevez un SMS et cliquez sur sur la barre de notification, il enverra une intention de diffusion comme celle-ci: content://mms-sms/conversations/XXX, où XXX est l'ID de la conversation.

Obtenez une liste de toutes les conversations

La seule chose que vous avez à faire est d'interroger le 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);

Remarque: généralement, lorsque vous appelez query et que vous souhaitez renvoyer toutes les colonnes, vous pouvez passer null comme projection paramètre. Cependant, vous ne pouvez pas faire cela avec ce fournisseur, c'est pourquoi j'utilise *.

Vous pouvez maintenant parcourir le Cursor comme d'habitude. Ce sont les colonnes les plus importantes que vous voudriez utiliser:

  • _id est l'ID du message. Capitaine évident à la rescousse? Pas vraiment. Cet ID peut être utilisé pour récupérer des informations détaillées à l'aide de content://sms ou content://mms.
  • date aucune explication nécessaire.
  • thread_id est l'ID de la conversation
  • body Le contenu du dernier SMS sur cette conversation. Si c'est un MMS, même s'il a une partie texte, ce sera null.

Remarque: si vous interrogez content://mms-sms/conversations il renverra une liste de différentes conversations dont _id est le dernier SMS ou MMS dans chaque conversation. Si vous interrogez content://mms-sms/conversations/xxx il renverra chacun SMS et/ou MMS sur la conversation dont l'ID est xxx.

Comment faire la différence entre SMS et MMS

Généralement, vous voudrez savoir quel type de message vous traitez. La documentation dit:

Une colonne virtuelle, MmsSms.TYPE_DISCRIMINATOR_COLUMN, peut être demandé dans la projection pour une requête. Sa valeur est "mms" ou "sms", selon que le message représenté par la ligne est un message MMS ou SMS, respectivement).

Je pense que cela fait référence à cette variable ... mais je n'ai pas pu le faire fonctionner. Si vous avez, dites-moi comment ou modifiez ce message.

Jusqu'à présent, c'est ce que j'ai fait et cela semble fonctionner, mais il doit y avoir de meilleures façons:

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

Comment obtenir des données à partir d'un SMS

Vous avez donc l'ID du SMS, alors la seule chose que vous avez à faire est:

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

Comment obtenir des données à partir d'une donnée MMS?

Les MMS sont un peu différents. Ils peuvent être construits avec différentes parties (texte, audio, images, etc.); ici, nous verrons comment récupérer chaque type de données séparément.

Supposons donc que nous ayons le MMS id dans la variable mmsId. Nous pouvons obtenir des informations détaillées à ce sujet MMS en utilisant le content://mms/ fournisseur:

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

Cependant, la seule colonne intéressante est read qui est 1 si le message a déjà été lu.

Comment obtenir du contenu texte à partir de MMS

Ici, nous devons utiliser content://mms/part... par exemple:

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

Il pourrait contenir différentes parties de texte ... mais généralement ce ne serait qu'une seule. Donc, si vous souhaitez supprimer la boucle, cela fonctionnera la plupart du temps. Voici à quoi ressemble la méthode getMmsText:

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

Comment obtenir une image à partir d'un MMS

C'est la même chose que d'obtenir la partie texte ... la seule différence est que vous rechercherez un type mime différent:

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

Voici à quoi ressemble la méthode getMmsImage:

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

Comment obtenir l'adresse de l'expéditeur

Vous devrez utiliser le content://mms/xxx/addr provider, où xxx est l'ID du 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;
}

Dernières pensées

  • Je ne comprends pas pourquoi Google, avec ces milliers de millions de dollars, ne paie pas un étudiant ou quelqu'un d'autre pour documenter cette API. Vous devez vérifier le code source pour savoir comment cela fonctionne et, ce qui est pire, ils ne rendent pas publiques les constantes utilisées dans les colonnes de la base de données, nous devons donc les écrire manuellement.
  • Pour d'autres types de données à l'intérieur d'un MMS vous pouvez appliquer la même idée que celle apprise ci-dessus ... c'est juste une question de connaître le type mime.
267
Cristian

La réponse de Christian est excellente. Cependant, la méthode pour obtenir l'adresse de l'expéditeur n'a pas fonctionné pour moi. L'instruction Long.parseLong ne fait rien sauf peut-être lever une exception et un nouveau String (...)?.

Sur mon appareil, le nombre de curseurs est de 2 ou plus. Le premier a généralement un "type" de 137 et les autres ont un "type" de 151. Je ne peux pas trouver où cela est documenté, mais on peut en déduire 137 est "de" et 151 est "à". Ainsi, si j'exécute la méthode telle quelle, je n'obtiens pas d'exception et elle renvoie la dernière ligne, qui est un destinataire et seulement l'un des nombreux dans de nombreux cas.

Aussi AFAICT la sélection n'est pas nécessaire car toutes les lignes ont le même msg_id. Cependant, cela ne fait pas de mal.

Voici ce qui fonctionne pour moi pour obtenir l'adresse de l'expéditeur:

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

Je me moquais de savoir si tout était numérique, mais j'ai inclus un moyen d'éliminer tout sauf les chiffres en tant que commentaire si cela était souhaité. Il peut également être facilement modifié pour renvoyer tous les destinataires.

Je suppose que cela a fonctionné pour lui. Il semble que cela donnerait la bonne réponse si l'exception se produisait sur la première ligne.

7
Kenneth Evans

Je viens de me battre avec ça; cependant, je l'ai finalement fait fonctionner et j'ai pensé que ce fil pourrait bénéficier de mon expérience.

Je pouvais interroger sur content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI) et obtenir des adresses et des pièces comme décrit utilement dans le fil, mais j'ai trouvé que cet URI ne récupérerait pas les fils qui seulement avaient MMS messages en eux - par exemple, des discussions avec plus de deux correspondants.

Après avoir creusé dans l'AOSP MMS, j'ai constaté qu'il utilisait une variante sur Telephony.Threads.CONTENT_URI pour générer sa liste de conversations - il ajoutait le paramètre "simple" avec la valeur "true". lorsque j'ai ajouté ce paramètre, j'ai trouvé que le fournisseur interrogeait une table complètement différente, qui avait en effet tous les threads SMS et MMS).

Cette table a un schéma complètement différent de celui de Telephony.Threads.CONTENT_URI normal (???); c'est la projection que l'application AOSP utilise -

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

Le _ID ici est l'ID du thread - donc un ID dans Telephony.Sms.CONTENT_URI ou Telephony.Mms.CONTENT_URI.

Après avoir découvert ce détail bizarre, les choses ont commencé à bien mieux fonctionner! Notez cependant que la colonne DATE dans la variante "simple = true" n'est pas fiable, j'ai dû utiliser la date du message Sms ou Mms le plus récent à la place.

Une autre chose que je devrais probablement mentionner est que pour obtenir une liste appropriée de messages pour un thread particulier, j'ai dû interroger les fournisseurs Mms et Sms, puis combiner les résultats dans une liste, puis les trier par date.

J'ai vérifié le comportement sur Android 5.x et 7.x.

J'espère que cela aide un peu plus.

4
Jason Proctor

J'ai dû faire quelques modifications pour que cela fonctionne pour moi.

  1. Lorsque je récupère le cursor.getString (cursor.getColumnIndex ("type")) du contenu mms-sms/conversations, ("content: // mms-sms/conversations /"), je teste le valeur du champ "type" pour null. Si la variable est nulle - c'est-à-dire.

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

    le message est un SMS, sinon c'est un MMS. Pour les MMS, vous devez tester les deux types de mime comme suit: -

    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. Lorsque vous utilisez un ContentObserver pour surveiller le contenu du message pour les modifications, il déclenche plusieurs notifications pour le même message. J'utilise une variable statique - dans mon cas lastMMSID - pour garder une trace du message.
  3. Ce code fonctionne bien pour récupérer le contenu des messages entrants et sortants. Il est important de parcourir tous les enregistrements qui sont retournés par l'URI "content: // mms/part /" afin d'accéder au contenu - texte et/ou pièces jointes - du MMS.
  4. Le seul moyen que j'ai pu trouver qui fonctionne assez bien pour faire la différence entre les MMS entrants et sortants, est de tester l'état nul du champ "m_id" du contenu mms-sms/conversations.

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

Une dernière réflexion sur la façon d'obtenir le champ d'adresse. Pour une raison quelconque, le contenu de l'adresse n'aime pas être interrogé avec un paramètre {"*"}, mais cela fonctionne: -

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

S'il s'agit d'un message sortant, le "type" à rechercher sera 151. Pour un message entrant, le "type" sera 137. Un morceau de code entièrement fonctionnel ressemblera à ceci: -

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

À tous les braves guerriers qui m'ont précédé dans cet article - je te remercie du fond du cœur!

3
Gustav

La réponse donnée ci-dessus pour obtenir le getMMSAddress () ne doit pas contenir la boucle while (cursor.moveToNext ()) ;. Il ne doit extraire l'adresse qu'à partir du premier élément du curseur. Pour une raison qui m'est inconnue, ce curseur a plus d'un enregistrement. Le premier contient l'adresse de l'expéditeur. Les autres éléments du curseur au-delà du premier contiennent l'adresse du destinataire. Ainsi, le code tel quel renvoie l'adresse du destinataire et non l'adresse de l'expéditeur.

Cela a été très utile pour ouvrir le contenu d'un MMS.

2
Hassan Nabil