Will Eddins
Will Eddins

Reputation: 13907

Allow notification to be cancelled after calling stopForeground(false)

I have a Media service that uses startForeground() to show a notification when playback starts. It has pause/stop buttons when playing, play/stop buttons while paused.

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
// setup...

Notification n = mBuilder.build();

if (state == State.Playing) {
    startForeground(mId, n);
}
else {
    stopForeground(false);
    mNotificationManager.notify(mId, n);        
}

The problem here is when I show/update the notification in it's paused state, you should be allowed to remove it. mBuilder.setOngoing(false) appears to have no effect as the previous startForeground overrides it.

Calling stopForeground(true); with the same code works as expected, but the Notification flashes as it is destroyed and recreated. Is there a way to "update" the notification created from startForeground to allow it to be removed after calling stop?

Edit: As requested, here's the full code creating the notification. createNotification is called whenever the service is played or paused.

private void createNotification() {
    NotificationCompat.Builder mBuilder = 
            new NotificationCompat.Builder(this)
    .setSmallIcon(R.drawable.ic_launcher)
    .setContentTitle("No Agenda")
    .setContentText("Live stream");

    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
    {
        if (state == State.Playing) {
            Intent pauseIntent = new Intent(this, MusicService.class);
            pauseIntent.setAction(ACTION_PAUSE);
            PendingIntent pausePendingIntent =     PendingIntent.getService(MusicService.this, 0, pauseIntent, 0);              

            mBuilder.addAction(R.drawable.pause, "Pause", pausePendingIntent);
            //mBuilder.setOngoing(true);
        }
        else if (state == State.Paused) {
            Intent pauseIntent = new Intent(this, MusicService.class);
            pauseIntent.setAction(ACTION_PAUSE);
            PendingIntent pausePendingIntent =  PendingIntent.getService(MusicService.this, 0, pauseIntent, 0);

            mBuilder.addAction(R.drawable.play, "Play", pausePendingIntent);
            mBuilder.setOngoing(false);
        }

        Intent stopIntent = new Intent(this, MusicService.class);
        stopIntent.setAction(ACTION_STOP);
        PendingIntent stopPendingIntent = PendingIntent.getService(MusicService.this, 0, stopIntent, 0);

        setNotificationPendingIntent(mBuilder);
        mBuilder.addAction(R.drawable.stop, "Stop", stopPendingIntent);
    }
    else
    {
        Intent resultIntent = new Intent(this, MainActivity.class);
        PendingIntent intent = PendingIntent.getActivity(this, 0, resultIntent, 0);

        mBuilder.setContentIntent(intent);
    }

    Notification n = mBuilder.build();

    if (state == State.Playing) {
        startForeground(mId, n);
    }
    else {
        stopForeground(true);
        mNotificationManager.notify(mId, n);
    }
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setNotificationPendingIntent(NotificationCompat.Builder mBuilder) {
    Intent resultIntent = new Intent(this, MainActivity.class);

    TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
    stackBuilder.addParentStack(MainActivity.class);
    stackBuilder.addNextIntent(resultIntent);

    PendingIntent resultPendingIntent =
            stackBuilder.getPendingIntent(
                0,
                PendingIntent.FLAG_UPDATE_CURRENT
            );

    mBuilder.setContentIntent(resultPendingIntent);
}

Follow-up Edit:

One of the comments below mentioned that the answer may be "fragile", and as of the release of Android 4.3, the behavior behind startForeground has changed. startForeground will force your application to show a notification while in the foreground, and the method should just be called with the notification to show. I haven't tested, but the accepted answer may no longer work as intended.

In terms of stopping the flashing when calling stopForeground, I don't think it's worth fighting the framework for.

There's some additional information on the Android 4.3 notification change here.

Upvotes: 21

Views: 12908

Answers (6)

Mahendra Liya
Mahendra Liya

Reputation: 13218

I landed to this question when I was trying to find a solution for why are the ongoing notifications not getting cancelled inspite of calling the cancel and cancelAll methods.

What helped me from reading the answers here was to stop the Foreground service.

So, before cancelling the notification, I made sure to broadcast an intent to stop the foreground service and the notification got cancelled!

Upvotes: 0

Kamleshwer Purohit
Kamleshwer Purohit

Reputation: 356

Id can not be 0 so I use stopForeground(false). now i am able to remove notification

Upvotes: 0

Xing Liu
Xing Liu

Reputation: 36

Before N, the notification has to reappear when stopForeground and startForeground called in a sequence. You need to show the notification once more and don't make it a foreground notification after you paused. I recommend you not to add work around or hack to fix this.(There are work arounds, but may introduce other bugs).

On N+, there is a flag added, ServiceCompat.STOP_FOREGROUND_DETACH. This will be passed to stopForeground and let you keep the current notification in the notification tray.

Also don't forget to kill the service(stopSelf() or whatever). Since if the user swipes away the app, you app main process may stay.

Upvotes: 1

Inon Stelman
Inon Stelman

Reputation: 965

You may consider using a different approach.
Since you should use foreground service for such a task (media playing) I suggest you keep on doing start foreground(), but instead of passing a notification to it just set id 0 and notification null like this startForeground(0, null);.

This way the foreground service will not display any notification.

Now, for your purposes, you can use regular notifications and update their states (ongoing, layout, text, etc...), this way you are not dependant on the foreground service's notification behaviour.

Hope this helps.

Upvotes: 6

akshay7692
akshay7692

Reputation: 601

Instead of stopService(true), calling stopService(false) will retain the notification as it is (without ongoing state) unless it is dismissed by user/removed programmatically or if the service stops. Hence just call stopService(false) and update notification to show paused state and now notification can be dismissed by user. This also prevents flashing since we are not recreating the notification.

Upvotes: 6

k4dima
k4dima

Reputation: 6251

According to docs this behaviour is by design before Lollipop

Applications targeting this or a later release will get these new changes in behavior:

...

  • Calling Service.stopForeground with removeNotification false will modify the still posted notification so that it is no longer forced to be ongoing.

Upvotes: 3

Related Questions