MediaSessionService: allow apps to opt-out from notifications
Issue: androidx/media#50 PiperOrigin-RevId: 447435259
This commit is contained in:
parent
2e544224c2
commit
1b15d5c370
@ -73,7 +73,7 @@ import java.util.concurrent.TimeoutException;
|
||||
if (controllerMap.containsKey(session)) {
|
||||
return;
|
||||
}
|
||||
MediaControllerListener listener = new MediaControllerListener(session);
|
||||
MediaControllerListener listener = new MediaControllerListener(mediaSessionService, session);
|
||||
ListenableFuture<MediaController> controllerFuture =
|
||||
new MediaController.Builder(mediaSessionService, session.getToken())
|
||||
.setListener(listener)
|
||||
@ -118,7 +118,7 @@ import java.util.concurrent.TimeoutException;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNotification(MediaSession session) {
|
||||
public void updateNotification(MediaSession session) {
|
||||
@Nullable ListenableFuture<MediaController> controllerFuture = controllerMap.get(session);
|
||||
if (controllerFuture == null) {
|
||||
return;
|
||||
@ -132,6 +132,11 @@ import java.util.concurrent.TimeoutException;
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
if (!mediaSessionService.isSessionAdded(session) || !canStartPlayback(session.getPlayer())) {
|
||||
maybeStopForegroundService(/* removeNotifications= */ true);
|
||||
return;
|
||||
}
|
||||
|
||||
int notificationSequence = ++totalNotificationCount;
|
||||
MediaNotification.Provider.Callback callback =
|
||||
notification ->
|
||||
@ -140,17 +145,18 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
MediaNotification mediaNotification =
|
||||
this.mediaNotificationProvider.createNotification(mediaController, actionFactory, callback);
|
||||
updateNotification(session, mediaNotification);
|
||||
updateNotificationInternal(session, mediaNotification);
|
||||
}
|
||||
|
||||
private void onNotificationUpdated(
|
||||
int notificationSequence, MediaSession session, MediaNotification mediaNotification) {
|
||||
if (notificationSequence == totalNotificationCount) {
|
||||
updateNotification(session, mediaNotification);
|
||||
updateNotificationInternal(session, mediaNotification);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNotification(MediaSession session, MediaNotification mediaNotification) {
|
||||
private void updateNotificationInternal(
|
||||
MediaSession session, MediaNotification mediaNotification) {
|
||||
if (Util.SDK_INT >= 21) {
|
||||
// Call Notification.MediaStyle#setMediaSession() indirectly.
|
||||
android.media.session.MediaSession.Token fwkToken =
|
||||
@ -188,7 +194,7 @@ import java.util.concurrent.TimeoutException;
|
||||
}
|
||||
}
|
||||
// To hide the notification on all API levels, we need to call both Service.stopForeground(true)
|
||||
// and notificationManagerCompat.cancelAll(). For pre-L devices, we must also call
|
||||
// and notificationManagerCompat.cancel(notificationId). For pre-L devices, we must also call
|
||||
// Service.stopForeground(true) anyway as a workaround that prevents the media notification from
|
||||
// being undismissable.
|
||||
mediaSessionService.stopForeground(removeNotifications || Util.SDK_INT < 21);
|
||||
@ -209,40 +215,40 @@ import java.util.concurrent.TimeoutException;
|
||||
return player.getPlaybackState() != Player.STATE_IDLE && !player.getCurrentTimeline().isEmpty();
|
||||
}
|
||||
|
||||
private final class MediaControllerListener implements MediaController.Listener, Player.Listener {
|
||||
private static final class MediaControllerListener
|
||||
implements MediaController.Listener, Player.Listener {
|
||||
private final MediaSessionService mediaSessionService;
|
||||
private final MediaSession session;
|
||||
|
||||
public MediaControllerListener(MediaSession session) {
|
||||
public MediaControllerListener(MediaSessionService mediaSessionService, MediaSession session) {
|
||||
this.mediaSessionService = mediaSessionService;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public void onConnected() {
|
||||
if (canStartPlayback(session.getPlayer())) {
|
||||
updateNotification(session);
|
||||
// We need to present a notification.
|
||||
mediaSessionService.onUpdateNotification(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
if (!canStartPlayback(player)) {
|
||||
maybeStopForegroundService(/* removeNotifications= */ true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit the events on which we may update the notification to ensure we don't update the
|
||||
// notification too frequently, otherwise the system may suppress notifications.
|
||||
// We must limit the frequency of notification updates, otherwise the system may suppress
|
||||
// them.
|
||||
if (events.containsAny(
|
||||
Player.EVENT_PLAYBACK_STATE_CHANGED,
|
||||
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
Player.EVENT_MEDIA_METADATA_CHANGED)) {
|
||||
updateNotification(session);
|
||||
mediaSessionService.onUpdateNotification(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(MediaController controller) {
|
||||
mediaSessionService.removeSession(session);
|
||||
maybeStopForegroundService(/* removeNotifications= */ true);
|
||||
// We may need to hide the notification.
|
||||
mediaSessionService.onUpdateNotification(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,14 +94,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
* controller. If it's accepted, the controller will be available and keep the binding. If it's
|
||||
* rejected, the controller will unbind.
|
||||
*
|
||||
* <p>When a playback is started on the service, the service will obtain a {@link MediaNotification}
|
||||
* from the {@link MediaNotification.Provider} that's set with {@link #setMediaNotificationProvider}
|
||||
* (or {@link DefaultMediaNotificationProvider}, if no provider is set), and the service will become
|
||||
* a <a href="https://developer.android.com/guide/components/foreground-services">foreground
|
||||
* service</a>. It's required to keep the playback after the controller is destroyed. The service
|
||||
* will become a background service when all playbacks are stopped. Apps targeting {@code SDK_INT >=
|
||||
* 28} must request the permission, {@link android.Manifest.permission#FOREGROUND_SERVICE}, in order
|
||||
* to make the service foreground.
|
||||
* <p>{@link #onUpdateNotification(MediaSession)} will be called whenever a notification needs to be
|
||||
* shown, updated or cancelled. The default implementation will display notifications using a
|
||||
* default UI or using a {@link MediaNotification.Provider} that's set with {@link
|
||||
* #setMediaNotificationProvider}. In addition, when playback starts, the service will become a <a
|
||||
* href="https://developer.android.com/guide/components/foreground-services">foreground service</a>.
|
||||
* It's required to keep the playback after the controller is destroyed. The service will become a
|
||||
* background service when all playbacks are stopped. Apps targeting {@code SDK_INT >= 28} must
|
||||
* request the permission, {@link android.Manifest.permission#FOREGROUND_SERVICE}, in order to make
|
||||
* the service foreground. You can control when to show or hide notifications by overriding {@link
|
||||
* #onUpdateNotification(MediaSession)}. In this case, you must also start or stop the service from
|
||||
* the foreground, when playback starts or stops respectively.
|
||||
*
|
||||
* <p>The service will be destroyed when all sessions are closed, or no controller is binding to the
|
||||
* service while the service is in the background.
|
||||
@ -263,6 +266,16 @@ public abstract class MediaSessionService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@code session} has been added to this service via {@link #addSession} or
|
||||
* {@link #onGetSession(ControllerInfo)}.
|
||||
*/
|
||||
public final boolean isSessionAdded(MediaSession session) {
|
||||
synchronized (lock) {
|
||||
return sessions.containsKey(session.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a component is about to bind to the service.
|
||||
*
|
||||
@ -373,12 +386,37 @@ public abstract class MediaSessionService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a notification needs to be updated. Override this method to show or cancel your own
|
||||
* notifications.
|
||||
*
|
||||
* <p>This method is called whenever the service has detected a change that requires to show,
|
||||
* update or cancel a notification. The method will be called on the application thread of the app
|
||||
* that the service belongs to.
|
||||
*
|
||||
* <p>Override this method to create your own notification and customize the foreground handling
|
||||
* of your service.
|
||||
*
|
||||
* <p>The default implementation will present a default notification or the notification provided
|
||||
* by the {@link MediaNotification.Provider} that is {@link
|
||||
* #setMediaNotificationProvider(MediaNotification.Provider) set} by the app. Further, the service
|
||||
* is started in the <a
|
||||
* href="https://developer.android.com/guide/components/foreground-services">foreground</a> when
|
||||
* playback is ongoing and put back into background otherwise.
|
||||
*
|
||||
* <p>Apps targeting {@code SDK_INT >= 28} must request the permission, {@link
|
||||
* android.Manifest.permission#FOREGROUND_SERVICE}.
|
||||
*
|
||||
* @param session A session that needs notification update.
|
||||
*/
|
||||
public void onUpdateNotification(MediaSession session) {
|
||||
getMediaNotificationManager().updateNotification(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link MediaNotification.Provider} to customize notifications.
|
||||
*
|
||||
* <p>This should be called before any session is attached to this service via {@link
|
||||
* #onGetSession(ControllerInfo)} or {@link #addSession(MediaSession)}. Otherwise a default UX
|
||||
* will be shown with {@link DefaultMediaNotificationProvider}.
|
||||
* <p>This should be called before {@link #onCreate()} returns.
|
||||
*/
|
||||
@UnstableApi
|
||||
protected final void setMediaNotificationProvider(
|
||||
|
Loading…
x
Reference in New Issue
Block a user