ex0ns

about
A Computer Science Student's blog, about programming, algorithms, hack and a lot more.

[Android] Liste noire SMS

15 Jun 2014

Après avoir retrouvé au fin fond d'un dossier une vielle application Android, j'ai décidé d'écrire un article à son sujet. A l'heure ou cet article est publié Android KitKat est déjà disponible et toutes les techniques présentées ici ne fonctionnent plus sous cette version (une seule et unique application doit être choisie pour recevoir des SMS). Je n'ai pas encore trouvé d'alternative pour faire fonctionner ces fonctions sur cette version, mais je suis certain que ceci est possible. Pour en revenir à l'application, son but (simple) était de pouvoir bloquer la réception de messages (SMS) en fonction du numéro, ou bien d'un inficatif de pays.

Mise en place de l'application

D'un point de vue des permissions, cette application nécessite android.permission.RECEIVE_SMS , afin de pouvoir récuperer les SMS entrants. Il faut également ajouter un receiver (BroadcastReceiver à notre manifest, afin de définir quelle classe à le droit de procéder à la récupération du message.

Le receveur

Cette classe est définie dans com.example.smslistener.SmsReceived, elle hérite de BroadcastReceiver. La méthode : public void onReceive(Context context, Intent arg1), est appelée lors de l'émission d'un signal par le système (ou une application), correspondant au signal declaré dans le manifest (SMSRECEIVED_). Toutes les informations contenues dans le signal se trouvent dans les "extras" de notre Intent (cf documentation android), et en particulier la clé pdus (protocol data unit) contenant toutes les informations relatives au message.

La méthode createFromPdu de la classe android.telephony.SmsMessage permet de créer un objet du type SmsMessage à partir de ces informations, ce dernier contient, entre-autre le numéro de l'expéditeur : getOriginatingAddress. Une fois ce numéro en notre possession, nous avons achevé la plus grosse partie du travail, il ne nous reste plus qu'a vérifier qu'il n'est lié ni à un pays bloqué ni d'un numéro bloqué. Afin de déterminer le pays d'origine, la méthode la plus rapide est de comparer le MCC (Mobile Country Code), il n'y a pas de méthode fournie par android pour effectuer cette comparaison, cependant, nous savons que leur taille est comprise entre 1 et 4 chiffres. Ainsi, il nous suffit d'essayer toutes les longueurs possibles de code dans l'ordre décroissant et de regarder s'ils existent, c'est ce que fait la méthode String getPrefix(String number):

private String getPrefix(String number){
    for(int i=4; i > 1; i--){
        String prefix = number.substring(1, i);
        if(this.db.findByPrefix(prefix)){
            return prefix;
        }
    }
    return null;
}

Dans notre exemple, nous utilisons une base de donnée afin de stocker tous les indicatifs des pays, ceci sera detaillé par la suite, mais l'important est de comprendre la détéction d'un MCC. Il faut maintenant comparer le numéro de téléphone ceux bloqués, contenus en base de donnée, dans le cas ou il s'y trouve, on arrête le broadcast à l'aide de la fonction abortBroadcast, ainsi, aucun autre application ayant un récepteur du type SMSRECEIVED_ ne l'obtiendra (sous hypothèse d'avoir la prioritée la plus grande possible sur cet évenement).

La base de donnée

Le modèle

Je voulais écrire cet article en grande partie afin de présenter le fonctionnement d'une base de donnée sous Android, qui, même si bien illustré dans la documentation, manque un peu d'exemples pratiques. Tout d'abord, il nous faut un modèle (un objet qui modélisé par les différentes colonnes de notre base de donnée), ici, le modèle est Country, modélisant un pays, par son nom, son MCC (indicatif téléphonique) ainsi que par un champs "blocked", indiquant si le messages provenant de ce pays doivent ou non, être bloqués.

Création de la table

Afin de créer notre table modélisant nos pays, il faut créer une classe héritant de SQliteOpenHelper, donc l'objectif est de créer et mettre à jour la base de donnée, la méthode onCreate ne sera appelée qu'une unique fois si la base de donnée n'est pas présente (au premier lancement de l'application par exemple), la méthode onUpgrade, elle sera appelée dans le cas d'un changement de version de la base de donnée (par exemple l'ajout ou la suppression d'un champs lors de la mise à jour de l'application), nous n'utiliserons pas cette méthode. La création de la base de donnée se déroule à l'aide de commandes SQL déjà bien connues (rien de spécifique pour Android):

db.execSQL("CREATE TABLE blockednumbers (_id INTEGER PRIMARY KEY AUTOINCREMENT, number TEXT unique);");
db.execSQL("CREATE TABLE blockedmcc (_id INTEGER PRIMARY KEY AUTOINCREMENT, country TEXT, mcc INTEGER unique, blocked INTEGER DEFAULT 0);");

Deux tables sont crées, la première contenenant les numéros de la liste noire, et l'autre contenant la liste des pays (nous devons extraire la liste des pays existants ainsi que leur code MCC correspondant d'un fichier XML récupéré sur Internet, c'est ce que fait la classe XMLCountries, cette classe sera detaillée par la suite, mais son unique méthode retourne une liste de pays qui peuvent par la suite être inserés en base de donnée (le XML est donc à usage unique, tout comme la classe DatabaseHelper).

Gestionnaire de base de donnée

Afin de faciliter l'intéraction avec la base de donnée, la documentation Android recommande la création d'une classe "DatabaseManager", qui sera chargée d'effectuer les requêtes. Par exemple, la fonction:

public void triggerMCC(Country country){
    ContentValues values = new ContentValues();
    values.put("blocked", country.triggerBlocked());
    this.database.update("blockedmcc", values, "mcc=?", new String[]{Integer.toString(country.getMcc())});
}

Se charge d'ajouter/retirer un pays de la liste noire, ContentValues est une classe ressemblant fortement à une Map, permettant d'associer une valeur à une clé. La fonction update prend en paramètre le nom de la table à modifier, une table associative colonnes/valeurs des colonnes à mettre à jour ainsi qu'une chaine de caractère représentant le selecteur "WHERE" d'une requête SQL (notez que la requête est "préparée", la rendant ainsi invulnérable aux attaques d'injection de code SQL), ainsi qu'un dernier argument, un tableau contenant la liste des paramètres requis par le "WHERE" (peut être null); Les méthodes query, insert et delete fonctionnent d'une façon très similaire.

Extraction des données XML

La classe XMLCountries sert à extraire les données relatives aux pays, c'est à dire nom et MCC. Pour cela, il faut se diriger du coté de XmlPullParser, et d'itérer sur tous les "tags" disponibles, en regardant s'il s'agit du nom ou d'un identifiant de pays, à partir de ces informations, nous pouvons créer une liste de Country, qui sera utilisée pour l'insertion en base de donnée. Une fois de plus, cette classe est à usage unique, et n'est instanciée que dans la méthode onCreate de DatabaseHelper.

Tous les éléments sont maintenant en place, l'application se contente de lister les pays disponible qui peuvent être bloqués/débloqués par un simple clique, ainsi qu'une entrée de texte afin d'ajouter à la liste noire un numéro spécifique. L'application sera par la suite "reveillée" lors de la réception d'un SMS et vérifiera si oui ou non ce message doit être transmis aux autres applications.

Quelques idées d'amélioration

  • Compteur du nombre de messages bloqués
  • Liste des numéros bloqués
  • Une option pour exporter les messages bloqués (pour gérer les exceptions).
Bibliographie