Related
I have a service that performs several downloads simultaneously in different threads. For each download, a notification with progress is displayed. Progress is updated about once a second. If you run three or more downloads at the same time, when you update the notification, they randomly change their positions in the status bar, jumping one over the other.
I tried setting the priority setPriority (priority), I used setOnlyAlertOnce (true), but this does not help. At the same time, in some other applications I saw that notifications are updated in their places.
The question is, how can this be achieved?
At the moment, my notifications are created like this:
private LongSparseArray<Task> mTasksArray = new LongSparseArray<>();
private int notifyId = 0;
...
//setup notification id on task download start
private void initTask(long taskId) {
Task task = new Task();
...
task.setNotificationId(notifyId++);
mTasksArray.put(taskId, task);
}
...
//notification update, called about once a second for every running download
private void showNotify(Task task) {
int notificationId = task.getNotificationId();
Notification notification = getProgressNotification(task);
mNotifyManager.notify(notificationId, notification);
}
#NonNull
private Notification getProgressNotification(Task task) {
int max = task.getTotal();
int count = task.getCount();
/**
* using id of notification as priority, but actual priority values can be only from -2 to 2
* right now it can show three first downloads correctly (for id values 0,1,2), but if,
* for example, stop first task and start another one, next id and priority will be 3
* (actually priority will be 2, because max priority is 2) and 3th and 4th notifications
* will reorder on every update, because of same priority
*/
int priority = task.getNotificationId();
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID);
builder.setContentTitle(mContext.getString(R.string.notif_title_downloading))
.setOnlyAlertOnce(true)
.setOngoing(true)
.setPriority(priority)
.setSmallIcon(R.drawable.ic_file_download_white_24dp)
.setProgress(max, count, false)
.addAction(getPauseAction(task))
.addAction(getStopAction(task))
.setContentText(String.format(Locale.ENGLISH, "%d/%d", count, max));
return builder.build();
}
Updated.
You need to use the same notification builder for each subsequent update. For example:
//First time
NotificationCompat.Builder builder = new NotificationCompat.Builder(...)
...
notificationManager.notify(id, builder.build());
//Second time
builder.setProgress(max, count, false);
notificationManager.notify(id, builder.build());
I want to dismiss the notification action buttons (not the whole notification) when clicking on those action buttons. (Lets say: a download notification with stop action button. When click on stop, dismiss stop button and change contentText to 'Download canceled')
The only thing it comes to my mind is to cancel notification and notify another one with the same id, but this seems to be an ugly workaround...
So, is there any way to remove action buttons from notifications?
(i think there is no need to put any code, but I will if its necessary...)
If you are using the NotificationCompat.Builder from the v4 Support Library, you can simply access the builder's action collection directly (Unfortunately no public mutators are provided).
The following will do the trick (Of course you must update re-notify):
NotificationCompat.Builder notifBuilder = NotificationCompat.Builder(context);
...
notifBuilder.mActions.clear();
I am using following workaround:
NotificationCompat.Builder builder = //existing instance of builder
//...
try {
//Use reflection clean up old actions
Field f = builder.getClass().getDeclaredField("mActions");
f.setAccessible(true);
f.set(builder, new ArrayList<NotificationCompat.Action>());
} catch (NoSuchFieldException e) {
// no field
} catch (IllegalAccessException e) {
// wrong types
}
from here: https://code.google.com/p/android/issues/detail?id=68063
Note:
Proguard may break the button clearing in obfuscated build. Fix is to add the following two lines in proguard-rules.pro
-keep class androidx.core.app.NotificationCompat { *; }
-keep class androidx.core.app.NotificationCompat$* { *; }
I had the same problem and found a solution for this.
I created another builder and added two "empty" actions like this:
builder.addAction(0, null, null);
builder.addAction(0, null, null);
(one for each button I had, so if you have three, call it three times).
Then when calling Notify, it removes the buttons.
Even though the accepted answer works, as per documentation, the designed way to do this is by using NotificationCompat.Extender class. For example in Kotlin:
private val clearActionsNotificationExtender = NotificationCompat.Extender { builder ->
builder.mActions.clear()
builder
}
private val notificationBuilder by lazy {
NotificationCompat.Builder(context)
.addAction(R.drawable.ic_play_arrow, "Play", playPendingIntent)
}
private fun updateNotification(){
notificationBuilder
.extend(clearActionsNotificationExtender) // this will remove the play action
.addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
}
NotificationCompat.Builder notifBuilder = NotificationCompat.Builder(context);
remove Whole Action Button :
builder.mActions.clear();
for remove special action button :
builder.mActions.remove(index);
finally :
notificationManager.notify(notificationID, builder.build());
Android provides the notification area for alerting users about the events that have occurred. It also provides a notification drawer that user can pull down to see more detailed information about the notification.
Notification Drawer consists of
View (contains tittle,detail,small icon)
Action ( any action which may occur in case the user clicks the notification drawer view)
To set up a notification so it can be updated, issue it with a notification ID by calling NotificationManager.notify(ID, notification). To update this notification once you've issued it, update or create a NotificationCompat.Builder object, build a Notification object from it, and issue the Notification with the same ID you used previously.
mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Sets an ID for the notification, so it can be updated
int notifyID = 1;
mNotifyBuilder = new NotificationCompat.Builder(this)
.setContentTitle("New Message")
.setContentText("You are downloading some image.")
.setSmallIcon(R.drawable.ic_stop)
numMessages = 0;
// Start of a loop that processes data and then notifies the user
...
mNotifyBuilder.setContentText("Download canceled")
.setNumber(++numMessages);
// Because the ID remains unchanged, the existing notification is
// updated.
mNotificationManager.notify(
notifyID,
mNotifyBuilder.build());
...
I have a Notification, which supports play,pause forward and back.
private static Notification createNotification(String interpret, String title, boolean paused) {
// if (builder == null)
builder = new NotificationCompat.Builder(context);
builder.setPriority(Notification.PRIORITY_MAX);
builder.setAutoCancel(false);
builder.setContentTitle(title);
builder.setContentText(interpret);
builder.setOngoing(true);
builder.setOnlyAlertOnce(true);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentIntent(PendingIntent.getActivity(context, 9, new Intent(context, ApplicationActivity.class), Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT));
builder.addAction(R.drawable.av_previous, "", PendingIntent.getBroadcast(context.getApplicationContext(), 0, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PREVIOUS), PendingIntent.FLAG_CANCEL_CURRENT));
if (paused)
builder.addAction(R.drawable.av_play, "", PendingIntent.getBroadcast(context.getApplicationContext(), 2, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PLAY), PendingIntent.FLAG_CANCEL_CURRENT));
else
builder.addAction(R.drawable.av_pause, "", PendingIntent.getBroadcast(context.getApplicationContext(), 3, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PAUSE), PendingIntent.FLAG_CANCEL_CURRENT));
builder.addAction(R.drawable.av_next, "", PendingIntent.getBroadcast(context.getApplicationContext(), 1, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.NEXT), PendingIntent.FLAG_CANCEL_CURRENT));
Notification notification = builder.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
notification.tickerView = null;
return notification;
}
Updating the notification:
public static void update(String interpret, String title, boolean paused) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(0, createNotification(interpret, title, paused));
}
To avoid blinking on update, I´ve set the builder to a global variable and I reuse it on every update, which works great. but reusing it, means that also all buttons I´ve added are reused and there is no possibility to remove Actions I´ve added before.
The button change only works, if I reinitialize the NotificationCompat.Builder on every update, which means I get the blinking again.
How do I avoid blinking, but letting the button change?
EDIT:
Just checked out Rocket Player, they didn´t solve the problem too, but Google Play Music did
Like Boris said, the problem is that a new notification will be build every update.
My solution covers the same logic, but I use the NotificationBuilder...
here is the code:
if (mNotificationBuilder == null) {
mNotificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(iconId)
.setContentTitle(title)
.setContentText(message)
.setLargeIcon(largeIcon)
.setOngoing(true)
.setAutoCancel(false);
} else {
mNotificationBuilder.setContentTitle(title)
.setContentText(message);
}
keep in mind that mNotificationBuilder is a private field in the class.
The issue is that you create new notification every time you want to update. I had the same issue and it fixed when I did the following:
retain the instance of the notification inbetween different calls of createNotification.
set this instance to null every time it is removed from the notification bar.
do the following code:
Code:
private static Notification createNotification(String interpret, String title, boolean paused) {
if (mNotification == null) {
// do the normal stuff you do with the notification builder
} else {
// set the notification fields in the class member directly
... set other fields.
// The below method is deprecated, but is the only way I have found to set the content title and text
mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
}
return mNotification;
}
And now when you call notify no blinking will appear:
manager.notify(0, createNotification(interpret, title, paused));
PS: I also faced a problem that if I executed setLatestEventInfo the large and small icons got scrwed up. That's why I did:
int tmpIconResourceIdStore = mNotification.icon;
// this is needed to make the line below not change the large icon of the notification
mNotification.icon = 0;
// The below method is deprecated, but is the only way I have found to set the content title and text
mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
mNotification.icon = tmpIconResourceIdStore;
Looking into Adnroid ccode this line mNotification.icon = 0; disables the icon screw up.
I know that this is a rather old question, but since I didn't found a solution anywhere else, I thought answering this now might help others with the same problem.
This problem is kind of tricky to begin with. I encountered it today as well, and being my stubborn self, I found a solution after searching and trying for a while.
How to solve this problem:
In order to be compatible with API-Levels lower than 19, my solution is to use the NotificationCompat classes from the support-library.
As suggested by others, I keep the reference to the NotificationCompat.Builder for as long as the notification is required. The actions I use in my Notification are only added upon initial creation of the Builder, and those actions that will change depending on the situation, I also store in a private member of the service. Upon change, I re-use the Builder object and adjust the NotificationCompat.Action object according to my needs. Then I call the Builder.getNotification() or Builder.build() method, depending on API-level (probably not necessary due to the support-libs, but I didn't check that. If I can omit that, please write a comment, so I can improve my code ;)
Here's an example code of what I just described above:
public Notification createForegroundNotification(TaskProgressBean taskProgressBean, boolean indeterminate) {
Context context = RewardCalculatorApplication.getInstance();
long maxTime = TaskUtils.getMaxTime(taskEntry);
long taskElapsedTime = TaskUtils.calculateActualElapsedTime(taskProgressBean);
long pauseElapsedTime = taskProgressBean.getPauseElapsedTime();
int pauseToggleActionIcon;
int pauseToggleActionText;
PendingIntent pauseToggleActionPI;
boolean pauseButton = pauseElapsedTime == 0;
if(pauseButton) {
pauseToggleActionIcon = R.drawable.ic_stat_av_pause;
pauseToggleActionText = R.string.btnTaskPause;
pauseToggleActionPI = getPendingIntentServicePause(context);
} else {
pauseToggleActionIcon = R.drawable.ic_stat_av_play_arrow;
pauseToggleActionText = R.string.btnTaskContinue;
pauseToggleActionPI = getPendingIntentServiceUnpause(context);
}
String contentText = context.getString(R.string.taskForegroundNotificationText,
TaskUtils.formatTimeForDisplay(taskElapsedTime),
TaskUtils.formatTimeForDisplay(pauseElapsedTime),
TaskUtils.formatTimeForDisplay(taskProgressBean.getPauseTotal()));
// check if we have a builder or not...
boolean createNotification = foregroundNotificationBuilder == null;
if(createNotification) { // create one
foregroundNotificationBuilder = new NotificationCompat.Builder(context);
// set the data that never changes...plus the pauseAction, because we don't change the
// pauseAction-object, only it's data...
pauseAction = new NotificationCompat.Action(pauseToggleActionIcon, getString(pauseToggleActionText), pauseToggleActionPI);
foregroundNotificationBuilder
.setContentTitle(taskEntry.getName())
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(getPendingIntentActivity(context))
.setOngoing(true)
.addAction(R.drawable.ic_stat_action_done, getString(R.string.btnTaskFinish), getPendingIntentServiceFinish(context))
.addAction(pauseAction);
}
// this changes with every update
foregroundNotificationBuilder.setContentText(contentText);
if(indeterminate) {
foregroundNotificationBuilder.setProgress(0, 0, true);
} else {
foregroundNotificationBuilder.setProgress((int) maxTime, (int) taskElapsedTime, false);
}
// if this is not the creation but the button has changed, change the pauseAction's data...
if(!createNotification && (pauseButton != foregroundNotificationPauseButton)) {
foregroundNotificationPauseButton = pauseButton;
pauseAction.icon = pauseToggleActionIcon;
pauseAction.title = getString(pauseToggleActionText);
pauseAction.actionIntent = pauseToggleActionPI;
}
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
? foregroundNotificationBuilder.getNotification() // before jelly bean...
: foregroundNotificationBuilder.build(); // since jelly bean...
}
The variables foregroundNotificationBuilder, pauseAction and foregroundNotificationPauseButton are private members of the service class.
The getPendingIntent...() methods are convenience methods that simply create the PendingIntent objects.
This method is then called when I need to update the notification using the NotificationManager, as well as handed over to the service's startForeground() method. This solves the flickering and the problems with the not updatable actions in the notification.
Android: I am trying to cancel a notification from the notification bar after a package being installed.
What I am doing is the following:
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
Uri data = intent.getData();
//some code goes here
//get the id of the notification to cancel in some way
notificationhelper._completeNotificationManager.cancel(id);
}
}
}
where
public class notificationhelper {
public static NotificationManager _completeNotificationManager = null;
public void complete() {
if (_completeNotificationManager == null)
_completeNotificationManager = (NotificationManager) _context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(
R.drawable.notification,
_context.getString(R.string.notification),
System.currentTimeMillis());
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.flags |= Notification.FLAG_NO_CLEAR;
_completeNotificationManager.notify(TEXT, id, notification);
}
}
But the notificationhelper._completeNotificationManager.cancel(id) does not work. I tried to use notificationhelper._completeNotificationManager.cancelAll(); and it works. What I am doing wrong?
In my experience, you can't cancel all notifications with a particular ID, regardless of tag.
That is, if you create two notifications like so:
notificationManager.notify(TAG_ONE, SAME_ID, notification_one);
notificationManager.notify(TAG_TWO, SAME_ID, notification_two);
Then, notificationManager.cancel(SAME_ID) won't cancel either of them! I suspect that this is because the "tag" field, if unspecified in notify() and cancel(), defaults to null, which you have to cancel explicitly.
So, to cancel these two notifications, you have to call:
notificationManager.cancel(TAG_ONE, SAME_ID);
notificationManager.cancel(TAG_TWO, SAME_ID);
In your case, you're supplying "TEXT" as the tag but cancelling just using the id, which defaults to using tag=null.
So, either don't provide TEXT as your tag:
_completeNotificationManager.notify(id, notification);
Or, if you need separate notifications and don't want them to clobber each other, keep track of the active tags:
_completeNotificationManager.notify(TEXT, id, notification);
collectionOfActiveTags.add(TEXT);
...
for (String activeTag : collectionOfActiveTags)
notificationhelper._completeNotificationManager.cancel(activeTag, id);
I wish that what you're trying to do was supported, as it seems that it should be.
Well this is probably irrelevant at this point, but it should be posted here so that people like me dealing with the same problem might find the solution.
If NotificationManager.cancel() isn't working, try changing the ID for the notification.
notificationManager.notify(NOTIFICATION_ID, notification);
When I changed NOTIFICATION_ID from 1 to [RANDOM_NUMBER], it magically started working. I assume that 1 is somehow reserved, although there is no note in any documentation...
An of course make sure you use the same NOTIFICATION_ID to cancel:
notificationManager.cancel(NOTIFICATION_ID);
My notifications were not getting removed because my service was Foreground Service and NOT a regular service started by StartService.
If your service is foreground, call stopForeground(true) instead of stopself(). So now my code looks like this:
NotificationManagerCompat.from(this).cancel(NotificationHelper.PLAYER_NOTIFICATION_ID);
stopForeground(true);
and it worked, notification was removed.
I was facing the same issue recently. I have managed to solve it.
So from what i understood.
use the id which is basically a random number to notify and send this same id to the piece of code (receiver/activity...) where you want to cancel it.
When using tags, it seems to not work for me as I was giving one tag to all notifications but with unique id. It worked only on the first tag so I completely avoided using tags. If you want to use tags, issue unique tags along with unique id and use them both while cancelling.
So final answer... what I used and what works for me:
STEP 1:
int notif_id = (int)(System.currentTimeMillis()%10000);
STEP2: add this id inside the action intent (I am launching an activity where the notification gets cancelled on the action click):
Intent notificationSettingsIntent = new Intent(context.getApplicationContext(), NotificationSettingsActivity.class);
notificationSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
notificationSettingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
notificationSettingsIntent.putExtra("fromNotification",true);
notificationSettingsIntent.putExtra("notif_id",notif_id);
PendingIntent notificationSettingsActivityPendingIntent = PendingIntent.getActivity(context,notif_id,notificationSettingsIntent,PendingIntent.FLAG_ONE_SHOT);
STEP 3: notify using the id in the step 1 but with no tags
NotificationManagerCompat notificationCompat = NotificationManagerCompat.from(context.getApplicationContext());
notificationCompat.notify(notif_id,notificationBuilder.build());
Now in the Activity which gets opened by my action click, I cancel the notification as:
NotificationManagerCompat notificationCompat = NotificationManagerCompat.from(context.getApplicationContext());
notificationCompat.cancel(getIntent().getIntExtra("notif_id"));
Works every time now.
Sorry for late joining!
But following worked fine for me.
NotificationManagerCompat mNotificationManager = NotificationManagerCompat.from(context.getApplicationContext());
mNotificationManager.cancel("<TAG>",<Notificatoin-id>);
Following worked for me:
final NotificationManagerCompat mNotificationManager = NotificationManagerCompat.from(context.getApplicationContext());
mNotificationManager.cancel(<Notificatoin-id>);
Since there is no accepted answer, I am posting another one with same scenario I faced
private fun stopForegroundService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_DETACH)
}else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N){
stopForeground(true)
}
notificationManager.cancel(NOTIFICATION_ID)
}
Point to note is first you need to set stopForeground(false) then call notificationManager.cancel(NOTIFICATION_ID)
If you change the order, it won't work
I have an application with two buttons. One button that "closes" the application and one that begins the algorithm. When I click "begin" it "hides" the application and displays a notification in the notification bar. I need to be able to execute/call a method when the notification is clicked/pressed. There are a few answers for this sort of question, but they are incredibly vague and one only points to a link to the doc on BroadcastReceiver.
If you are going to leave a url to the BroadcastReceiver doc and say "read this page," please don't reply to this question. If you are going to explain how I could use BroadcastReceiver to execute a method (from within the same class that displayed the notification), please show me some code for how this could be done.
My algorithm: press a button, display notification, click notification, call a method (don't display activity). That's it.
If it's not possible, just let me know. If it is, please show me what you would do to make it possible. Something this simple shouldn't have been overlooked by the developers of the android sdk.
After several iterations of trial and error, I finally found a fairly straightforward and clean way to run an arbitrary method when a notification's action is clicked. In my solution, there is one class (I'll call it NotificationUtils) that creates the notification and also contains an IntentService static inner class that will run when actions on the notification are clicked. Here is my NotificationUtils class, followed by the necessary changes to AndroidManifest.xml:
public class NotificationUtils {
public static final int NOTIFICATION_ID = 1;
public static final String ACTION_1 = "action_1";
public static void displayNotification(Context context) {
Intent action1Intent = new Intent(context, NotificationActionService.class)
.setAction(ACTION_1);
PendingIntent action1PendingIntent = PendingIntent.getService(context, 0,
action1Intent, PendingIntent.FLAG_ONE_SHOT);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("Sample Notification")
.setContentText("Notification text goes here")
.addAction(new NotificationCompat.Action(R.drawable.ic_launcher,
"Action 1", action1PendingIntent));
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
public static class NotificationActionService extends IntentService {
public NotificationActionService() {
super(NotificationActionService.class.getSimpleName());
}
#Override
protected void onHandleIntent(Intent intent) {
String action = intent.getAction();
DebugUtils.log("Received notification action: " + action);
if (ACTION_1.equals(action)) {
// TODO: handle action 1.
// If you want to cancel the notification: NotificationManagerCompat.from(this).cancel(NOTIFICATION_ID);
}
}
}
Now just implement your actions in onHandleIntent and add the NotificationActionService to your manifest within the <application> tags:
<service android:name=".NotificationUtils$NotificationActionService" />
Summary:
Create a class that will create the notification.
Inside that class, add a IntentService inner classes (make sure it is static or you will get a cryptic error!) that can run any method based on the action that was clicked.
Declare the IntentService class in your manifest.
On Notification click we can't get any fire event or any click listener. When we add notification in notification bar, we can set a pending intent, which fires an intent (activity/service/broadcast) upon notification click.
I have a workound solution for you, if you really don't want to display your activity then the activity which is going to start with pending intent send a broad cast from there to your parent activity and just finish the pending activity and then once broadcast receiver receives in parent activity call whatever method you want inside the receiver. For your reference..
// This is what you are going to set a pending intent which will start once
// notification is clicked. Hopes you know how to add notification bar.
Intent notificationIntent = new Intent(this, dummy_activity.class);
notificationIntent.setAction("android.intent.action.MAIN");
notificationIntent.addCategory("android.intent.category.LAUNCHER");
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT |
Notification.FLAG_AUTO_CANCEL);
// Now, once this dummy activity starts send a broad cast to your parent activity and finish the pending activity
//(remember you need to register your broadcast action here to receive).
BroadcastReceiver call_method = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action_name = intent.getAction();
if (action_name.equals("call_method")) {
// call your method here and do what ever you want.
}
};
};
registerReceiver(call_method, new IntentFilter("call_method"));
}
}