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.
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.
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.
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);
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.
Rédaction par Jean-Marie Piatte (1983-2021)