Ludovic ROLAND

Blog technique sur mes expériences de développeur.

Accéder aux SMS depuis une application Android

8 mars 2015

Il y a plusieurs mois, je vous proposais un article expliquant comment accéder aux contacts du téléphone depuis une application Android tierce.

Je vous propose aujourd’hui un article, qui sera assez court, complétaire dans lequel nous verrons ensemble comment accéder aux SMS du téléphone depuis une application tierce.

Avant de commencer…

Comme pour lé récupération des contacts, nous allons nous appuyer sur la classe ContentResolver pour accéder aux SMS. Cette classe permet, notamment, via des requêtes de demander des informations aux systèmes comment par exemple la liste des SMS ou la liste des contacts.

Les permissions

Puisque nous allons récupérer des SMS, nous allons devons demander l’autorisation à l’utilisateur de le faire en déclarant une petite permission au niveau du fichier AndroidManifest.xml du projet :

<uses-permission android:name="android.permissions.READ_SMS" />

La requête

La méthode query

Afin de récupérer la liste des contacts, nous allons donc devoir utiliser la classe ContentResolver et plus précisément la méthode query. Je vous propose de faire un petit tour sur la documentation officielle pour voir les paramètres que prend cette méthode.

Comme vous pouvez le constater, il existe deux signatures pour cette méthode. Intéressons-nous à celle qui accepte le moins de paramètres :

public final Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

Cette méthode accepte cinq paramètres. Dans le cadre de ce tutoriel, nous allons nous attarder uniquement sur les deux premiers :

  • uri : il s’agit de l’URI à interoger pour récupérer les résultats que nous souhaitons. Via cette URI, nous allons pouvoir préciser la nature de ce que nous souhaitons récupérer comme par exemple des contacts, des SMS envoyés ou des SMS reçus.
  • projection : il s’agit d’un tableau permettant de filtrer les informations que l’on souhaite recevoir. Par filtre, je n’entends pas quelque chose du style les SMS envoyés par le Gérard, mais plutôt je ne veux que le nom et l’identifiant des contacts. Filtrer les informations à recevoir permet d’optimiser légèrement la requête qui est faite et gagner en performance.

Première version…

Je vous propose de débuter en douceur en créant une méthode privée dans un Fragment ou une Activity qui accepte pour paramètre un objet de type ContentResolver :

private void retrieveMessages(ContentResolver contentResolver)
{
}

Dans mon cas, cette méthode sera appelée depuis la méthode onCreateView d’un Fragment, aussi, elle sera appelée de la façon suivante : retrieveMessages(getActivity().getContentResolver());. Je vous laisse bien évidemment faire les vérifications d’usage quant à l’appel de la méthode getActivity() ;) .

Nous allons nous contenter d’appeler la méthode query avec le paramètre minimum, à savoir l’URI. Suivant les messages auxquels nous souhaitons accéder, l’URI à utiliser sera légèrement différente :

  • Tous les messages : Uri.parse("content://sms/") ;
  • Les messages reçus : Uri.parse("content://sms/inbox") ;
  • Les messages envoyés : Uri.parse("content://sms/sent") ;

Nous pouvons donc créer trois constantes que vous pourrez utiliser en fonction de votre besoin :

private static final Uri SMS_URI_ALL = Uri.parse("content://sms/");

private static final Uri SMS_URI_INBOX = Uri.parse("content://sms/inbox");

private static final Uri SMS_URI_OUTBOX = Uri.parse("content://sms/sent");

Dans le cadre de ce tutoriel, nous allons travailler avec les message de la boite de réception.

Notre appel à la méthode query ressemble donc à ça :

contentResolver.query(SMS_URI_INBOX, null, null, null, null);

Complétons notre méthode retrieveMessages en exploitant le résultat de la méthode query qui est un cursor :

private void retrieveMessages(ContentResolver contentResolver)
{
  final Cursor cursor = contentResolver.query(SMS_URI_INBOX, null, null, null, null);
}

Avant d’exploiter le cursor, je vous propose de vérifier qu’il n’est pas null. Si c’est le cas, nous quittons la méthode :

if (cursor == null)
{
  Log.e("retrieveMessages", "Cannot retrieve the messages");
  return;
}

Dans le cas contraire, nous allons vérifier qu’il contient au moins un résultat en le déplaçant sur son premier élément :

if (cursor.moveToFirst() == true)
{

}

S’il contient au moins un élément, nous allons parcourir tous les éléments à l’aide d’une boucle do...while :

if (cursor.moveToFirst() == true)
{
  do
  {

  }
  while (cursor.moveToNext() == true);
}

Finalement, nous allons, avant de quitter notre méthode retrieveMessages, fermer le cursor :

if (cursor.isClosed() == false)
{
  cursor.close();
}

Voici ce à quoi doit ressembler la méthode retrieveMessages pour le moment :

private void retrieveMessages(ContentResolver contentResolver)
{
  final Cursor cursor = contentResolver.query(SMS_URI_INBOX, null, null, null, null);

  if (cursor == null)
  {
    Log.e("retrieveMessages", "Cannot retrieve the messages");
    return;
  }

  if (cursor.moveToFirst() == true)
  {
    do
    {

    }
    while (cursor.moveToNext() == true);
  }

  if (cursor.isClosed() == false)
  {
    cursor.close();
  }
}

Il convient donc de compléter la boucle do...while afin d’exploiter réellement les éléments du cursor. Pour ça, nous allons devoir exploiter l’une des méthodes suivantes du cursor : getDouble(), getFloat(), getInt(), getLong(), getShort(), getString(). Chacune de ces méthodes prend un seul paramètre qui correspond à l’index de la colonne dont on souhaite récupérer l’information.

Par exemple, si je souhaite récupérer le contenu du message voici ce que l’on doit écrire :

cursor.getString(cursor.getColumnIndexOrThrow("body"));

Par exemple, si je souhaite récupérer le numéro de téléphone qui a envoyé le SMS, voici ce que l’on doit écrire :

cursor.getString(cursor.getColumnIndexOrThrow("address"));

Je vous propose alors de compléter notre méthode retrieveMessages en affichant dans le logcat le numéro de téléphone qui a envoyé le SMS ainsi que le contenu du message :

private void retrieveMessages(ContentResolver contentResolver)
{
  final Cursor cursor = contentResolver.query(SMS_URI_INBOX, null, null, null, null);

  if (cursor == null)
  {
    Log.e("retrieveMessages", "Cannot retrieve the messages");
    return;
  }

  if (cursor.moveToFirst() == true)
  {
    do
    {
      final String address = cursor.getString(cursor.getColumnIndexOrThrow("address"));
      final String body = cursor.getString(cursor.getColumnIndexOrThrow("body"));

      Log.d("retrieveContacts", "The message with from + '" + address + "' with the body '" + body + "' has been retrieved");
    }
    while (cursor.moveToNext() == true);
  }

  if (cursor.isClosed() == false)
  {
    cursor.close();
  }
}

Si vous exécutez l’application Android, vous allez vous rendre compte que tout fonctionne.

Un peu d’UI

Maintenant que nous sommes capable de récupérer notre liste de messages, je vous propose de les afficher dans une ListView. Pour celà, nous allons devoir modifier notre méthode pour qu’elle renvoie maintenant notre liste de message sous la forme d’un ArrayList.

La méthode retrieveMessages

La première étape consiste donc à modifier notre méthode retrieveMessages. Dorénavant, sa signature est la suivante :

private List<String> retrieveMessages(ContentResolver contentResolver)
{
}

Celà nous oblige à retourner un résultat à deux endroits de la fonction :

  • dans le if qui vérifie que le cursor n’est pas null ;
  • à la fin de la méthode.

Pour le if allons au plus simple et renvoyons null :

if (cursor == null)
{
  Log.e("retrieveMessages", "Cannot retrieve the messages");
  return null;
}

Pour le renvoie en fin de méthode, pas de difficulté, il convient simplement d’ajouter les message dans notre liste au fur et à mesure que nous les parcourons.

private List<String> retrieveMessages(ContentResolver contentResolver)
{
  final List<String> messages = new ArrayList<>();
  final Cursor cursor = contentResolver.query(SMS_URI_INBOX, null, null, null, null);

  if (cursor == null)
  {
    Log.e("retrieveMessages", "Cannot retrieve the messages");
    return null;
  }

  if (cursor.moveToFirst() == true)
  {
    do
    {
      final String address = cursor.getString(cursor.getColumnIndexOrThrow("address"));
      final String body = cursor.getString(cursor.getColumnIndexOrThrow("body"));

      messages.add(address + " - " + body);

      Log.d("retrieveContacts", "The message with from + '" + address + "' with the body '" + body + "' has been retrieved");
    }
    while (cursor.moveToNext() == true);
  }

  if (cursor.isClosed() == false)
  {
    cursor.close();
  }

  return messages;
}

L’affichage des messages

Pour afficher les messages, nous allons utiliser une ListView avec un ArrayAdapter. Voici ce à quoi ressemble le layout du Fragment :

<ListView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@android:id/list"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
/>

Finalement, voici ce à quoi ressemble la méthode onCreateView du Fragment qui affiche la liste des messages :

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
  View rootView = inflater.inflate(R.layout.fragment_main, container, false);

  final ListView list = (ListView) rootView.findViewById(android.R.id.list);
  final List<String> messages = retrieveMessages(getActivity().getContentResolver());

  if (messages != null)
  {
    list.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, messages));
  }

  return rootView;
}

Après exécution de l’application, vous devriez avoir quelque chose comme ça :

Comme vous pouvez le constater, les smileys sont bien conservés et affichés.

Télécharger le projet

Le projet Android créé pour la rédaction de cet article est disponible sur Github.

Commentaires