androidnotificationsandroid-serviceandroid-notificationsnotificationmanager

Cancel (and hide) Android notification programmatically


I have a service running in conjunction with a Notification (System). When the service stops (and/or the app stops), the notification should be cancelled (i.e. not showing on the status bar anymore).

Going through the provide Android Dev guide of Notifications, I did not find any info on how to close a notification.

Going to SO, I found a number of questions.

1.

To summarise @nipun.birla's answer to how one should cancel Android notifications:

To Cancel a Notification, try in order:

  1. NotifcationManager.cancel(int) with notificationID

  2. NotificationManager.cancel(String, int) with notificationID and notificationTag

  3. NotificationManager.cancelAll() as final attempt

What was not mentioned though, is what should one do if none of these work.

2.

One should use cancelAll suggestion

3.

This SO thread contains a good example of a Started Service example with an associate notification implemented into the service lifecycle (where the notification is also killed) - See here for Started Service details

4.

Here a suggestion was made to delete the PendingIntent associated with the notification

5.

A couple more questions and solutions reflecting the same info above: See this, and this, and many more...

6.

An very interesting question and solution on programmatically hiding a notification icon in the status bar


My Problem

By now it should be fairly obvious, my notification does not cancel itself asked to do so.

Implementation:

See below for full implementation, although I will post the general usage of the helper class and core functions

- Create Notification

private Context context;
private NotificationManager notificationManager;
private NotificationChannel notificationChannel;
private NotificationCompat.Builder notificationBuilder;

public NotificationHelper(Context context) {
    this.context = context;

    // Init notification

    // onNotificationCreate()
    {
        // get notification manager system service
        notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Create notification channel
            createNotificationChannel();

            //Register notification channel with notification manager
            notificationManager.createNotificationChannel(notificationChannel);
        }

    }

    // Init Notification Builder

    // createNotificationChannel() 
    {
        Log.d(TAG, "createNotificationChannel: Creating notification channel");

        // Define notification channel ID, Channel Name and description
        String channelName = BuildConfig.FLAVOR.concat(" Notifier");
        String channelDescription = context.getString(R.string.notification_description);

        // Create notification channel
        notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
        // Set description of notification channel
        notificationChannel.setDescription(channelDescription);
    }
}

This is called by new NotificationHelper(getApplicationContext) where the context is the Application context used (which is also used as the context for many other functions)

The methodology behind my helper class is to simply use method chaining allowing for a more eye-candy'ish approach to creating, modifying and cancelling a notification.

- NotificationHelper usage:

Setting notification contentText by calling setTextContent(String):

public NotificationHelper setTextContent(String text){
    notificationBuilder.setContentText(text);
    return this;
}

setting the contentTitle by `setTitle(String):

public NotificationHelper setTitle(String format) {
    notificationBuilder.setContentTitle(format);
    return this;
}

and setting the smallIcon (status icon) by calling setStatusIcon(int):

public NotificationHelper setStatusIcon(int res_id) {
    notificationBuilder.setSmallIcon(res_id);
    return this;
}

Finally, updating the notification to display the resulting settings is done by:

public void update() {
    Log.d(TAG, "update: Updating notification");
    Notification notification = notificationBuilder.build();

    // Set notification flags
    notification.flags |= Notification.FLAG_NO_CLEAR;
    notification.flags |= Notification.FLAG_ONGOING_EVENT;
    notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;

    // Notify update
    notificationManager.notify(TAG, notificationId, notification);
}

- Cancel Notification

And as expected, cancelling the notification is as simple as calling cancelNotification():

public void cancelNotification() {
    Log.d(TAG, "cancelNotification: Cancelling notification");
    notificationManager.cancel(TAG, notificationId);
}

This however has no effect on cancelling the notification.

When the notification is cancelled, the following has occurred just prior to the cancellation.

Having done all of this, the notification remains.

What have I tried

This did not work, so I got creative:

Furthermore: Creating a separate NotificationManager method to post these updates (i.e. no flags are set here, but the same notificationmanager is used)

public void updateCancelable() {
    Log.d(TAG, "update: Updating notification to cancel");
    Notification notification = notificationBuilder
            .setContentIntent(null)
            .setOngoing(false)
            .setAutoCancel(true)
            .build();
    // Notify update
    notificationManager.notify(TAG, notificationId, notification);
}

This did not help either. Is there something I may be missing?

I should also mention this: while debugging my app, I noticed that when I exited the app (having the bound service stopped and calling cancelNotification(), the app should in no way be running anymore although Android Studio does still keep an active debugging session open like one would expect when the app is still running.Not sure if this has something to do with it


NotificationHelper class (full implementation)

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.connectedover.BuildConfig;
import com.connectedover.R;
import com.connectedover.listeners.NotificationUpdateListener;

/**
 * Class aimed at providing helper method for creating, maintaining and destroying notifications in conjunction with {@link de.blinkt.openvpn.core.OpenVPNService}
 *
 * @author cybex
 * @since 1.5.1
 */
public class NotificationHelper implements NotificationUpdateListener {

    private static final String TAG = NotificationManager.class.getSimpleName();
    private static final String channelId = BuildConfig.APPLICATION_ID.concat(".").concat(TAG);
    private static final int notificationId = 42;

    private Context context;
    private NotificationManager notificationManager;
    private NotificationChannel notificationChannel;

    private NotificationCompat.Builder notificationBuilder;

    public NotificationHelper(Context context) {
        this.context = context;

        // Init notification
        onNotificationCreate();

        // Init Notification Builder
        createBasicNotification();
    }

    /**
     * Initialize {@link NotificationChannel} and register channel with {@link NotificationManager} service if API is Android Orea (API 26 or higher), else initializes the notification manager
     */
    private void onNotificationCreate() {
        Log.d(TAG, "onNotificationCreate: Initializing notification helper");

        // get notification manager system service
        notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Create notification channel
            createNotificationChannel();

            //Register notification channel with notification manager
            notificationManager.createNotificationChannel(notificationChannel);
        }
    }

    /**
     * Creates a notification channel required by devices running Android SDK 26 and higher.
     * The notification  channel is set to {@link NotificationManager#IMPORTANCE_LOW} which should have no sound and appear right at the top of the status bar
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    private void createNotificationChannel() {
        Log.d(TAG, "createNotificationChannel: Creating notification channel");

        // Define notification channel ID, Channel Name and description
        String channelName = BuildConfig.FLAVOR.concat(" Notifier");
        String channelDescription = context.getString(R.string.notification_description);

        // Create notification channel
        notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
        // Set description of notification channel
        notificationChannel.setDescription(channelDescription);
    }

    /**
     * Creates a basic notification using {@link android.support.v4.app.NotificationCompatBuilder} for use throughout the application
     */
    private void createBasicNotification() {
        // Instantiate Notification Builder
        notificationBuilder = new NotificationCompat
                .Builder(context, channelId)
                .setContentTitle(context.getString(R.string.app_name))
                .setSmallIcon(R.drawable.ic_logo_disconnected)
                .setWhen(System.currentTimeMillis())
                .setAutoCancel(false)
                .setOngoing(true);
    }

    /**
     * Set the pending intent of a clickable {@link android.app.Notification} held by {@link NotificationHelper#notificationBuilder}
     * @param pendingIntent Pending intent to connect to activity
     * @return returns an instance of {@link NotificationHelper}
     */
    public NotificationHelper setPendingIntent(PendingIntent pendingIntent){
        Log.d(TAG, "setPendingIntent: Setting notification Pending intent");
        notificationBuilder.setContentIntent(pendingIntent);
        return this;
    }

    /**
     * Updates the notification which is displayed for the user.
     */
    public void update() {
        Log.d(TAG, "update: Updating notification");
        Notification notification = notificationBuilder.build();

        // Set notification flags
        notification.flags |= Notification.FLAG_NO_CLEAR;
        notification.flags |= Notification.FLAG_ONGOING_EVENT;
        notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;

        // Notify update
        notificationManager.notify(TAG, notificationId, notification);
    }

    /**
     * Updates the notification {@link NotificationHelper#notificationBuilder} with new text and displays it to the user
     *
     * @param text new text to display
     * @return returns current {@link NotificationHelper} instance for method chaining.
     */
    public NotificationHelper setTextContent(String text){
        notificationBuilder.setContentText(text);
        return this;
    }

    @Override
    public void onUpdate(String update) {
        Log.d(TAG, "onUpdate: updating notification via callback");
        this.setTextContent(update)
                .update();
    }

    /**
     * Sets a new icon for the notification displayed to the user
     * @param res_id icon resource
     * @return current instance
     */
    public NotificationHelper setLargeIcon(int res_id) {
        notificationBuilder.setLargeIcon(ImageUtils.toBitmap(context, res_id));
        return this;
    }

    /**
     * Sets a new icon for the notification displayed to the user show in the status bar (i.e. the small icon)
     * @param res_id icon resource
     * @return current instance
     */
    public NotificationHelper setStatusIcon(int res_id) {
        notificationBuilder.setSmallIcon(res_id);
        return this;
    }

    public NotificationHelper setTitle(String format) {
        notificationBuilder.setContentTitle(format);
        return this;
    }

    /**
     * Cancels the application notification
     */
    public void cancelNotification() {
        Log.d(TAG, "cancelNotification: Cancelling notification");
        notificationManager.cancelAll();
    }
}

Solution

  • If you want to use your Notification as part of a foreground service, rather than manipulate flags directly, use startForeground() and stopForeground() on your Service.