Logo Spiria

Masquage des notifications de processus de premier plan dans Android

11 avril 2019.

Dans cet article, Mathieu nous explique comment avoir un processus de premier plan sans affichage de notification permanente pour l’utilisateur.

Dans Android, les services de premier plan (Foreground Services) sont des processus qui s’exécutent en tâche de fond lorsque l’application n’est pas visible et le système ne les tue jamais. Ce sont généralement des processus qui interagissent directement avec l’utilisateur, comme les lecteurs de musique. Toutefois, la création d’un processus de premier plan nécessite l’affichage permanent d’une notification. Ce n’est pas un problème pour les lecteurs de musique, puisque la notification est utilisée pour proposer une interface de commandes et des informations sur le morceau en cours. Mais dans certains cas, la notification n’est d’aucune utilité et ne fait que déranger l’utilisateur. Nous verrons donc comment la faire disparaître.

Pour les besoins de cet article, nous supposerons que vous savez tout du développement sur Android, de celui des services de premier plan, des récepteurs de diffusion (Broadcast Receivers) et des canaux de notification. Nous vous montrerons comment masquer les notifications de service de premier plan sur différentes versions d’Android. Il existe trois scénarios :

  • Masquer les notifications en deux clics dans Android 8 et au-delà.
  • Masquage automatique des notifications dans Android 4 à 7.0.1.
  • Minimiser la visibilité des notifications dans Android 7.1.

Comme il n’y a aucun moyen effectif de cacher les notifications dans Android 7.1, nous verrons ce qu’il est possible de faire à la place.

Android 8 et supérieur

Sous Android 8, il n’est pas possible de masquer automatiquement une notification de service de premier plan ; cependant, nous pouvons permettre aux utilisateurs de la masquer manuellement en seulement deux clics.

Android 8 a introduit le concept de canaux de notification, permettant de configurer les notifications d’un canal depuis l’interface d’information de l’application. Vous pouvez configurer la vibration, la sonnerie et la visibilité des notifications. Dans notre cas, nous voulons accéder à la possibilité d’afficher ou de masquer les notifications d’un canal.

L’idée est de créer un canal spécifique pour la notification de service de premier plan et d’envoyer l’utilisateur directement à l’interface de réglages du canal de notification lorsqu’il clique sur la notification.

Pour ouvrir l’interface de réglages du canal de notification, utilisez un Intent standard, tel que celui-ci :

Intent i = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
i.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
i.putExtra(Settings.EXTRA_CHANNEL_ID, "serviceNotificationChannelId");
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

Cependant, nous ne pouvons pas lancer l’Intent directement lorsque la notification est cliquée, car cela fait planter l’application de préférences d’Android. Pour éviter ça, nous pouvons utiliser l’Intent depuis notre processus d’application. Nous allons créer un récepteur de diffusion qui lancera les réglages du canal de notification, en appelant ce récepteur à partir de notre notification.

Voici le code du récepteur de diffusion :

public class NotificationBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
                .putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName())
                .putExtra(Settings.EXTRA_CHANNEL_ID, HiddenService.SERVICE_NOTIFICATION_CHANNEL)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(i);
    }
}

Notez que le paramètre FLAG_ACTIVITY_NEW_TASK est requis lors du lancement d’une activité à partir d’un récepteur de diffusion.

N’oubliez pas de mettre à jour votre manifeste avec :

<receiver android:name=".NotificationBroadcastReceiver"/>

Maintenant, nous devons mettre à jour la notification utilisée pour le service de premier plan afin d’appeler ce récepteur quand on clique dessus. Voici le code complet pour créer la notification et mettre le service au premier plan :

NotificationManager notifManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel serviceChannel = new NotificationChannel(
        "serviceNotificationChannelId",
        "Hidden Notification Service",
        NotificationManager.IMPORTANCE_DEFAULT);
notifManager.createNotificationChannel(serviceChannel);

Intent intent = new Intent(this, NotificationBroadcastReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
        this,
        1,
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT
);

Notification notification = new Notification.Builder(this, "serviceNotificationChannelId")
        .setContentTitle("Hide Notification Example")
        .setContentText("To hide me, click and uncheck \"Hidden Notification Service\"")
        .setContentIntent(pendingIntent)
        .setSmallIcon(R.mipmap.ic_launcher)
        .getNotification();
startForeground(SERVICE_NOTIFICATION_ID, notification);

Voici la notification telle qu’affichée sur le téléphone de l'utilisateur ; comme il y est indiqué, l’utilisateur n’a qu’à cliquer sur la notification.

decorative

L’interface de réglages apparaît et l’utilisateur n’a plus qu’à désactiver « Afficher les notifications » (“Show notifications”).

decorative

Ceci n’est pas un processus automatique, mais il est très efficace, prend 2 secondes et ne demande aucune connaissance particulière de la part de l’utilisateur.

Android 7.0 et inférieur

Sous Android 7.0 et inférieur, le code est un peu plus délicat, mais il ne nécessite aucune action de l’utilisateur. La solution consiste à exploiter la gestion déficiente des services de premier plan et de leurs notifications par Android.

Ici, l’astuce consiste à créer deux services qui débutent au premier plan avec le même identifiant de premier plan, puis à arrêter l’un des services en demandant la suppression de la notification. La notification disparaîtra, mais le service restant continuera de fonctionner et restera marqué comme un processus de premier plan.

Pour y parvenir, nous aurons besoin d’un service factice dont le seul but est de débuter au premier plan et de s’arrêter immédiatement.

Voici le code pour le service factice :

public class DummyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        Notification.Builder builder = new Notification.Builder(this);
        this.startForeground(-1, builder.getNotification());
        stopSelf();
    }

    @Override
    public void onDestroy() {
        stopForeground(true);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

Notez le « -1 » qui est l’identifiant de premier plan de notre service. Dans votre implémentation, utilisez le même identifiant que celui que vous utilisez pour votre service principal.

Le paramètre (true) dans l’appel de la méthode stopForeground indique que la notification doit être supprimée.

N’oubliez pas d’ajouter cette ligne dans votre manifeste :

<service android:name=".DummyService"/>

Maintenant, il ne nous reste plus qu’à lancer ce service factice dans le service que nous voulons exécuter au premier plan sans notification. La meilleure solution est d’ajouter la ligne ci-dessous dans onCreate :

startService(new Intent(this, DummyService.class));

N’oubliez pas de mettre votre service principal au premier plan avant de lancer le service factice.

Pour vous assurer que votre service fonctionne bien en mode premier-plan, exécutez la commande adb suivante :

adb shell dumpsys activity services <service>

Où <service> est le nom de classse (exemple : com.example.myapp.MyForegroundService).

Une fois exécuté, vous devriez voir une ligne avec isForeground=true, suivie de l’identifiant de premier plan et de l’information sur la notification.

Android 7.1

Comme indiqué au début de cet article, il n’y a pas de bonne solution pour Android 7.1. Les deux stratégies décrites ci-dessus ne fonctionnent pas.

Sous Android 7.1, vous pouvez désactiver les notifications pour votre application comme sous Android 8 et supérieur. Cependant, vous ne pouvez pas choisir quelles notifications cacher ou afficher ; c’est tout ou rien. Si cette solution correspond à vos besoins, vous pouvez utiliser la même solution que celle décrite pour Android 8 (moins les canaux de notification), et appeler les interfaces de réglages à partir du récepteur de diffusion en utilisant le code suivant :

Intent i = new Intent()
        .setAction("android.settings.APP_NOTIFICATION_SETTINGS")
        .putExtra("app_package", getPackageName())
        .putExtra("app_uid", getApplicationInfo().uid)
        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);

Il ouvrira l'interface suivante :

decorative

Si masquer toutes les notifications n’est pas une option pour vous, la seule solution est de définir la priorité de la notification à un minimum, l’envoyant ainsi au bas de la liste des notifications et réduisant sa visibilité.