Accueil / Articles PiApplications. / La plate-forme Android

Liste des SMS/MMS reçus.

Certaines applications peuvent nécessiter de retrouver les SMS et MMS reçus. Confronté à ce besoin, nous nous sommes rendu compte qu'il y avait assez peu d'information sur le sujet sur Internet. Souvent ces informations datent d'avant la publication des spécifications et nombre d'exemples ne fonctionnent tout simplement pas.

Principes.

Android utilise conjointement une base de données et le système de fichier pour stocker les SMS et les MMS reçus. Les récupérer nécessite l'emploi de "curseurs" et la création de ces curseurs reposent sur l'emploi d'URI. La structure des colonnes du curseur diffère selon qu'il s'agit d'un SMS ou d'un MMS. Ces colonnes sont en grande partie décrites par les classes imbriquée de la classe android.provider.Telephony.

Identification des colonnes d'un curseur sur les SMS reçus.

L'URI de ce curseur est content://sms/inbox. La documentation est fournie par la classe Telephony.TextBasedSmsColumns. Voici un exemple de code source permettant de lister les SMS reçus avec le détail de données associées à chacun d'eux :

private void readSMS()
{
  Uri uri = Uri.parse("content://sms/inbox");
  ContentResolver crl = getContentResolver();
  Cursor crs = crl.query(uri, null, null, null, null);
  while (crs.moveToNext())
  {
    Log.d(TAG, "===== NOUVEAU SMS =====");
    int iMaxCols = crs.getColumnCount();
    for (int k = 0; k < iMaxCols; k++)
    {
      String sKey = crs.getColumnName(k);
      String sValue = crs.getString(k);
      Log.d(TAG, String.format("%s=%s", sKey, sValue));
    }
  }
}

Les traces produisent quelque chose du type :

===== NOUVEAU MMS =====
_id=2
thread_id=82
address=+336xxxxxxxx  // Numéro de l'émetteur
m_size=167
person=0
date=1453490099875
date_sent=1453490113000
protocol=0
read=1
status=-1
type=1
reply_path_present=0
subject=null
body=Contenu du SMS.
service_center=+336xxxxxxxx
locked=0
sim_id=3
error_code=0
seen=1

La lecture des SMS est donc simple et toutes les informations se trouvent dans les colonnes du curseur pour chaque ligne lue.

Identification des colonnes d'un curseur sur les MMS reçus.

Un code source quasi aussi simple permet de faire la même chose avec les MMS. Toutefois l'URI est ici content://mms/inbox.

private void readMMS()
{
  Uri uri = Uri.parse("content://mms/inbox");
  ContentResolver crl = getContentResolver();
  Cursor crs = crl.query(uri, null, null, null, null);
  while (crs.moveToNext())
  {
    Log.d(TAG, "===== NOUVEAU MMS =====");
    int iMaxCols = crs.getColumnCount();
    for (int k = 0; k < iMaxCols; k++)
    {
      String sKey = crs.getColumnName(k);
      String sValue = crs.getString(k);
      Log.d(TAG, String.format("%s=%s", sKey, sValue));
    }
  }
}

Les traces produisent quelque chose du type :

===== NOUVEAU MMS =====
_id=2                                            // Identifiant de la ligne du curseur lue
thread_id=82                                     // Numéro de conversation
date=1453768485                                  // Date d'envoi
date_sent=0
msg_box=1                                        // Dossier auquel appartient le MMS (1 = inbox)
read=1                                           // Indicateur de lecture du message
m_id=msVkCsPEEeWOa4SPaWggdg                      // Identifiant de message (ici égal à l'identifiant de transaction)
sub=mms_img780911373                             // Objet du message
sub_cs=106
ct_t=application/vnd.wap.multipart.related       // ConTent Type (ici un MMS multi partie)
ct_l=null
exp=null
m_cls=personal                                   // Classe du message
m_type=132
v=18                                             // Version de la spécification MMS
m_size=528068                                    // Taille du message en octets
pri=129                                          // Priorité du message
rr=129                                           // Rapport de lecture
rpt_a=null                                       // Envoi d'un CR de lecture autorisé ?
resp_st=null
st=null
tr_id=msVkCsPEEeWOa4SPaWggdg                     // Identifiant de transaction
retr_st=null
retr_txt=null
retr_txt_cs=null
read_status=null
ct_cls=null
resp_txt=null
d_tm=null
d_rpt=128                                        // N° du rapport de livraison
locked=0                                         // Message verrouillé si 1
sim_id=3
service_center=null
seen=1                                           // Indicateur de lecture du message

On ne peut pas dire que l'on sache grand-chose sur le MMS à cette lecture. En particulier, on note que contrairement aux SMS, le numéro de l'appelant ne figure pas dans les colonnes du curseur.

Cela est lié au fait que le curseur ne remonte ici que l'en-tête principale du MMS et non son contenu. La spécification des MMS est probablement inspirée de celle des courriels et introduit la notion de multi-parties (multipart. Un MMS est donc constitué d'une ou plusieurs parties, chacune d'elle pouvant transporter un type particulier de données (texte, image, vidéo, etc.). Pour lire un MMS, il faut donc disposer de son identifiant (cf colonne "_id ci-dessus) puis effectuer une boucle de lecture des différentes parties qui le constitue. Voici un exemple de code qui énumère les parties composant un MMS en donnant pour chacune d'elle les colonnes constituant l'information livrées par chacune de ces parties

private void readMMS(String sID)
{
  String sMSS = String.format("mid=%s", sID);
  Uri uriPart = Uri.parse("content://mms/part/" + sID);
  ContentResolver crl = getContentResolver();
  Cursor crs = crl.query(uriPart, null, sMSS, null, null);
  while (crs.moveToNext())
  {
    Log.d(TAG, String.format("===== NOUVELLE PARTIE DU MMS N°%s =====", sID));
    int iMaxCols = crs.getColumnCount();
    for (int k = 0; k < iMaxCols; k++)
    {
      String sKey = crs.getColumnName(k);
      String sValue = crs.getString(k);
      Log.d(TAG, String.format("%s=%s", sKey, sValue));
    }
  }
}

Les traces produisent quelque chose du type :

===== NOUVELLE PARTIE DU MMS N°2 =====
_id=2                              // N° de ligne du curseur
mid=2                              // Identifiant du MMS auquel appartient cette partie
seq=0                              // Numéro d'ordre de la partie (lorsqu'il y en a plusieurs)
ct=image/jpeg                      // Type MIME du contenu de la partie
name=mms_img780911373.jpg          // Nom de la partie
chset=null
cd=null
fn=null
cid=<mms_img780911373>        // Identifiant du contenu
cl=mms_img780911373.jpg             // Nom du fichier contenant la partie
ctt_s=null
ctt_t=null
// Répertoire qui supporte le fichier contenant la partie
_data=/data/data/com.android.providers.telephony/app_parts/PART_1453768487837
text=null                          // Texte associé à la partie (en principe jamais défini pour un MMS)

La description des champs est documentée par la classe Telephony.Mms.Part.

Notez qu'un type MIME peut être distribué dans plusieurs parties (plusieurs images par exemple). Cela est également vrai pour le type MIME text/plain bien que généralement, un seule partie soit consacrée au texte transporté par un MMS. Comme pour les images, le texte qui accompagne le type text/plain est contenu dans le champ "{_data}/{cl}".

Grâce aux informations livrées par chaque partie, il est possible d'écrire une méthode générale de récupération de leurs données : La lecture de ces données suppose simplement que l'identifiant de la partie est connu :

private byte[] getMmsPartData(String sPartID)                   // Ici sPartID = "2"
{
  Uri uri = Uri.parse("content://mms/part/" + sPartID);
  InputStream ist = null;
  try
  {
    ist = getContentResolver().openInputStream(uri);
    byte[] abBuffer = null;
    byte[] ab = new byte[1024];
    boolean fEnd = false;
    while(! fEnd)
    {
      int i = ist.read(ab);
      if (i > 0)
      {
        if (abBuffer == null)
        {
          abBuffer = new byte[i];
          System.arraycopy(ab, 0, abBuffer, 0, i);
        }
        else
        {
          byte[] abTmp = new byte[abBuffer.length + i];
          System.arraycopy(abBuffer, 0, abTmp, 0, abBuffer.length);
          System.arraycopy(ab, 0, abTmp, abBuffer.length, i);
          abBuffer = abTmp;
        }
      }
      else if (i < 0)
        fEnd = true;
    }
    return abBuffer;
  }
  catch(IOException ioe)
  {
    // Traitement de l'exception
    Log.e(TAG, "Erreur rencontrée lors de la lecture des données d'une partie du MMS", ioe);
    return null;
  }
  finally
  {
    if (ist != null)
    {
      try
      {
        ist.close();
      }
      catch(IOException ioe)
      {
        Log.e(TAG, "Erreur rencontrée lors de la fermeture du fux de lecture des données d'un MMS.", ioe);
      }
    }
  }
}

Bien entendu, la connaissance du type MIME peut permettre de traiter directement les données lues dans le type attendu :

Par exemple pour du texte :

StringBuilder sbl = new StringBuilder();
InputStreamReader isr = new InputStreamReader(ist, "UTF-8");
BufferedReader brd = new BufferedReader(isr);
String s = brd.readLine();
while (s != null)
{
  sbl.append(sTmp);
  s = brd.readLine();
}

Par exemple pour une image (formats courants) :

Bitmap bmp = BitmapFactory.decodeStream(ist);

Lecture du numéro de l'expéditeur.

Il reste toutefois une information importante que n'a pas livrée notre MMS : l'adresse de l'expéditeur. Pour cela, nous devons créer un curseur sur l'adresse.

private String ReadMmsAddress(String sMmsID)
{
  String sAddress = "?";
  Uri uri  = Uri.parse(String.format("content://mms/%s/addr", sMmsID));
  String sFields = String.format("msg_id=%s", sMmsID);
  Cursor crs = getContentResolver().query(uri, null, sFields, null, null);
  Log.e(TAG, String.format("===== ADRESSE DU MMS N° %s =====", sMmsID));
  while (crs.moveToNext())
  {
    int iMaxCols = crs.getColumnCount();
    for (int k = 0; k < iMaxCols; k++)
    {
      String sKey = crs.getColumnName(k);
      String sValue = crs.getString(k);
      Log.d(TAG, String.format("%s=%s", sKey, sValue));
      if (sAddress.equals("?") && sKey.equals("address"))
        sAddress = sValue;
    }
  }
  return sAddress;
}

Les traces produisent quelque chose du type :

===== ADRESSE DU MMS N° 2 =====
_id=2
msg_id=2
contact_id=null
address=+336xxxxxxxx                   // Adresse émetteur
type=137
charset=106
_id=3
msg_id=2
contact_id=null
address=+336xxxxxxxx                   // Adresse destinatire
type=151
charset=106

La documentation de la classe Telephony.Mms.Addr livre la description des différentes colonnes.

Tous les exemples de code donnés ici ont été testés sur un téléphone de version Android 4.0.4.

(c) PiApplications 2016