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:
NotifcationManager.cancel(int) with notificationID
NotificationManager.cancel(String, int) with notificationID
and notificationTag
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.
unbindService
is called with getApplicationContext()
on the owning context like unbindService(ServiceConnection) using the same ServiceConnection as orignally bound with.Having done all of this, the notification remains.
What have I tried
notificationManger.cancel(int)
notificationManger.cancel(String, int)
notificationManger.cancelAll
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);
}
clearing the setContentIntent() by calling setContentIntent(null)
and re-notifying to update
I have also tried creating a new notification being cancelable with setAutoCancel(true)
, and setting the ongoing state with setOngoing(false)
and re-notifying to update
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();
}
}
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
.