Allow notification to be cancelled after calling stopForeground(false)
Asked Answered
G

6

21

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.

Garwood answered 25/3, 2013 at 8:21 Comment(3)
Can you show your code when you attempt to call setOngoing(false)? I suspect the notification isn't being rebuilt to pick up the changes but it's hard to tell from this snippet.Bellwort
@Bellwort I've added the full code for createNotification().Garwood
I'd also like to point out that I'm only testing on Jellybean at the moment, currently 4.2.2 (emulator and phone), which is where i'm setting setOngoing(false). Also, the setOngoing(false) does work when the notification is not started through startForeground (and setOngoing(true) is uncommented).Garwood
H
6

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.

Hectic answered 2/4, 2013 at 13:41 Comment(8)
This sounds like the correct path, however passing a null notification throws an IllegalArgumentException on startForeground(). The following question seems to indicate the same: #10962918Garwood
That's interesting, because I have used this method for a couple of times, and just retested it with a blank project. I also made sure that the service is indeed running foreground by querying the PackageManager. You may also try passing 0 as the id in startForeground(id, notification)Hectic
Using 0 as the id worked, and everything works as expected. Any id other than 0 seems to cause the IllegalArgumentException. Perfect!Garwood
Seems quite fragile. It seems clear to me that Google want there to be a notification for foreground services, so I wouldn't count on the current behavior to work in future Android versions... I suggest using stopForeground(true) and live with the notification flash.Hoey
@WillEddins can you please tell me how did you used toggle button in ongoing notification. Actually I am not able to figure it out. I am stuck from last 2 days. It will be a great help.Sevenfold
@SanghatiMukherjee You're really just creating a brand new notification each time, with a different action. It just looks like a toggle to the end user.Garwood
@Hoey @inistel As jonasb originally stated this answer may be 'fragile', it may no longer work as of Android 4.3, due to startForeground now creating it's own notification if one isn't passed in to try to stop rogue apps from abusing foreground status. See the edit at the bottom of my question for more info.Garwood
This no longer works since 4.3 (or at least the service will still display a notification) as stated here: commonsware.com/blog/2013/07/30/…Trevelyan
K
6

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.

Kaifeng answered 5/3, 2016 at 17:17 Comment(0)
F
3

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.
Fieldstone answered 20/1, 2015 at 0:51 Comment(0)
B
1

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.

Bazil answered 24/9, 2020 at 21:20 Comment(0)
N
0

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

Nahshu answered 12/4, 2022 at 6:59 Comment(0)
C
0

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!

Cynar answered 31/10, 2023 at 7:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.