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.

Will Eddins
  • 13,628
  • 5
  • 51
  • 85
  • 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. – dsandler Mar 25 '13 at 15:14
  • @dsandler I've added the full code for createNotification(). – Will Eddins Mar 26 '13 at 00:24
  • 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). – Will Eddins Mar 26 '13 at 00:44

5 Answers5

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.

Keyur Lakhani
  • 4,321
  • 1
  • 24
  • 35
Inon Stelman
  • 955
  • 7
  • 12
  • 1
    This sounds like the correct path, however passing a null notification throws an IllegalArgumentException on startForeground(). The following question seems to indicate the same: http://stackoverflow.com/questions/10962418/startforeground-without-showing-notification – Will Eddins Apr 09 '13 at 08:39
  • 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)` – Inon Stelman Apr 09 '13 at 10:10
  • Using 0 as the id worked, and everything works as expected. Any id other than 0 seems to cause the IllegalArgumentException. Perfect! – Will Eddins Apr 09 '13 at 20:28
  • 2
    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. – jonasb Apr 09 '13 at 22:42
  • @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. – Sanghati Mukherjee Jul 18 '13 at 13:35
  • @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. – Will Eddins Jul 18 '13 at 13:43
  • 1
    @jonasb @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. – Will Eddins Jul 29 '13 at 21:15
  • This no longer works since 4.3 (or at least the service will still display a notification) as stated here: http://commonsware.com/blog/2013/07/30/notifications-foreground-services-android-4p3.html – Richard Fung Jun 23 '14 at 22:52
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.

akshay7692
  • 601
  • 1
  • 8
  • 19
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.
Community
  • 1
  • 1
k4dima
  • 6,070
  • 5
  • 41
  • 39
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.

Xing Liu
  • 36
  • 2
0

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