28
votes

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

3
i don't understand how reusing the builder (which, btw, you don't in this code, probably a typo) can prevent the blinking, since the notification object is still a different one every time ?njzk2
I don´t know why, but it´s a fact. found it here stackoverflow.com/questions/6406730/…Marian Klühspies
my understanding from the question you posted is that what prevents blinking is the use of the same IDnjzk2
no, this doesn´t prevent it! As I said, using always the same builder prevents it, but it won´t let me change the existing lookMarian Klühspies
are you absolutely certain of that ? because in its present state, you code does create a new builder at each run.njzk2

3 Answers

17
votes

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.

9
votes

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.

6
votes

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.