Avoid usage of MediaController in MediaNotification.Provider
PiperOrigin-RevId: 451155897
This commit is contained in:
parent
3d2b335825
commit
821615cea0
@ -51,7 +51,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -126,16 +125,17 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
|
||||
@Override
|
||||
public final MediaNotification createNotification(
|
||||
MediaController mediaController,
|
||||
MediaSession mediaSession,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
MediaNotification.ActionFactory actionFactory,
|
||||
Callback onNotificationChangedCallback) {
|
||||
ensureNotificationChannel();
|
||||
|
||||
Player player = mediaSession.getPlayer();
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||
// Set metadata info in the notification.
|
||||
MediaMetadata metadata = mediaController.getMediaMetadata();
|
||||
MediaMetadata metadata = player.getMediaMetadata();
|
||||
builder.setContentTitle(metadata.title).setContentText(metadata.artist);
|
||||
|
||||
@Nullable ListenableFuture<Bitmap> bitmapFuture = loadArtworkBitmap(metadata);
|
||||
@ -164,19 +164,16 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
MediaStyle mediaStyle = new MediaStyle();
|
||||
int[] compactViewIndices =
|
||||
addNotificationActions(
|
||||
getMediaButtons(
|
||||
mediaController.getAvailableCommands(),
|
||||
customLayout,
|
||||
mediaController.getPlayWhenReady()),
|
||||
getMediaButtons(player.getAvailableCommands(), customLayout, player.getPlayWhenReady()),
|
||||
builder,
|
||||
actionFactory);
|
||||
mediaStyle.setShowActionsInCompactView(compactViewIndices);
|
||||
if (mediaController.isCommandAvailable(COMMAND_STOP) || Util.SDK_INT < 21) {
|
||||
if (player.isCommandAvailable(COMMAND_STOP) || Util.SDK_INT < 21) {
|
||||
// We must include a cancel intent for pre-L devices.
|
||||
mediaStyle.setCancelButtonIntent(actionFactory.createMediaActionPendingIntent(COMMAND_STOP));
|
||||
}
|
||||
|
||||
long playbackStartTimeMs = getPlaybackStartTimeEpochMs(mediaController);
|
||||
long playbackStartTimeMs = getPlaybackStartTimeEpochMs(player);
|
||||
boolean displayElapsedTimeWithChronometer = playbackStartTimeMs != C.TIME_UNSET;
|
||||
builder
|
||||
.setWhen(playbackStartTimeMs)
|
||||
@ -185,7 +182,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
|
||||
Notification notification =
|
||||
builder
|
||||
.setContentIntent(mediaController.getSessionActivity())
|
||||
.setContentIntent(mediaSession.getSessionActivity())
|
||||
.setDeleteIntent(actionFactory.createMediaActionPendingIntent(COMMAND_STOP))
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSmallIcon(getSmallIconResId(context))
|
||||
@ -197,34 +194,9 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void handleCustomCommand(
|
||||
MediaController mediaController, String action, Bundle extras) {
|
||||
@Nullable SessionCommand customCommand = null;
|
||||
for (SessionCommand command : mediaController.getAvailableSessionCommands().commands) {
|
||||
if (command.commandCode == SessionCommand.COMMAND_CODE_CUSTOM
|
||||
&& command.customAction.equals(action)) {
|
||||
customCommand = command;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (customCommand != null) {
|
||||
ListenableFuture<SessionResult> future =
|
||||
mediaController.sendCustomCommand(customCommand, extras);
|
||||
Futures.addCallback(
|
||||
future,
|
||||
new FutureCallback<SessionResult>() {
|
||||
@Override
|
||||
public void onSuccess(SessionResult result) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
Log.w(TAG, "custom command " + action + " produced an error: " + t.getMessage(), t);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
public final boolean handleCustomCommand(MediaSession session, String action, Bundle extras) {
|
||||
// Make the custom action being delegated to the session as a custom session command.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -422,14 +394,14 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
}
|
||||
}
|
||||
|
||||
private static long getPlaybackStartTimeEpochMs(MediaController controller) {
|
||||
private static long getPlaybackStartTimeEpochMs(Player player) {
|
||||
// Changing "showWhen" causes notification flicker if SDK_INT < 21.
|
||||
if (Util.SDK_INT >= 21
|
||||
&& controller.isPlaying()
|
||||
&& !controller.isPlayingAd()
|
||||
&& !controller.isCurrentMediaItemDynamic()
|
||||
&& controller.getPlaybackParameters().speed == 1f) {
|
||||
return System.currentTimeMillis() - controller.getContentPosition();
|
||||
&& player.isPlaying()
|
||||
&& !player.isPlayingAd()
|
||||
&& !player.isCurrentMediaItemDynamic()
|
||||
&& player.getPlaybackParameters().speed == 1f) {
|
||||
return System.currentTimeMillis() - player.getContentPosition();
|
||||
} else {
|
||||
return C.TIME_UNSET;
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ public final class MediaNotification {
|
||||
* <p>The returned {@link NotificationCompat.Action} will have a {@link PendingIntent} with the
|
||||
* extras from {@link SessionCommand#customExtras}. Accordingly the {@linkplain
|
||||
* SessionCommand#customExtras command's extras} will be passed to {@link
|
||||
* Provider#handleCustomCommand(MediaController, String, Bundle)} when the action is executed.
|
||||
* Provider#handleCustomCommand(MediaSession, String, Bundle)} when the action is executed.
|
||||
*
|
||||
* @param customCommandButton A {@linkplain CommandButton custom command button}.
|
||||
* @see MediaNotification.Provider#handleCustomCommand
|
||||
@ -116,8 +116,8 @@ public final class MediaNotification {
|
||||
/**
|
||||
* Creates a new {@link MediaNotification}.
|
||||
*
|
||||
* @param mediaController The controller of the session.
|
||||
* @param actionFactory The {@link ActionFactory} for creating notification {@linkplain
|
||||
* @param session The media session.
|
||||
* @param actionFactory The {@link ActionFactory} for creating notification {@link
|
||||
* NotificationCompat.Action actions}.
|
||||
* @param customLayout The custom layout {@linkplain MediaSession#setCustomLayout(List) set by
|
||||
* the session}.
|
||||
@ -126,7 +126,7 @@ public final class MediaNotification {
|
||||
* been loaded asynchronously.
|
||||
*/
|
||||
MediaNotification createNotification(
|
||||
MediaController mediaController,
|
||||
MediaSession session,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ActionFactory actionFactory,
|
||||
Callback onNotificationChangedCallback);
|
||||
@ -134,13 +134,15 @@ public final class MediaNotification {
|
||||
/**
|
||||
* Handles a notification's custom command.
|
||||
*
|
||||
* @param mediaController The controller of the session.
|
||||
* @param session The media session.
|
||||
* @param action The custom command action.
|
||||
* @param extras A bundle {@linkplain SessionCommand#customExtras set in the custom command},
|
||||
* otherwise {@link Bundle#EMPTY}.
|
||||
* @return {@code false} if the action should be delivered to the session as a custom command or
|
||||
* {@code true} if the action has been handled completely by the provider.
|
||||
* @see ActionFactory#createCustomAction
|
||||
*/
|
||||
void handleCustomCommand(MediaController mediaController, String action, Bundle extras);
|
||||
boolean handleCustomCommand(MediaSession session, String action, Bundle extras);
|
||||
}
|
||||
|
||||
/** The notification id. */
|
||||
|
@ -32,8 +32,10 @@ import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -122,28 +124,44 @@ import java.util.concurrent.TimeoutException;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
MediaController mediaController = controllerFuture.get(0, TimeUnit.MILLISECONDS);
|
||||
mediaNotificationProvider.handleCustomCommand(mediaController, action, extras);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
MediaController mediaController = checkStateNotNull(Futures.getDone(controllerFuture));
|
||||
if (!mediaNotificationProvider.handleCustomCommand(session, action, extras)) {
|
||||
@Nullable SessionCommand customCommand = null;
|
||||
for (SessionCommand command : mediaController.getAvailableSessionCommands().commands) {
|
||||
if (command.commandCode == SessionCommand.COMMAND_CODE_CUSTOM
|
||||
&& command.customAction.equals(action)) {
|
||||
customCommand = command;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (customCommand != null
|
||||
&& mediaController.getAvailableSessionCommands().contains(customCommand)) {
|
||||
ListenableFuture<SessionResult> future =
|
||||
mediaController.sendCustomCommand(customCommand, Bundle.EMPTY);
|
||||
Futures.addCallback(
|
||||
future,
|
||||
new FutureCallback<SessionResult>() {
|
||||
@Override
|
||||
public void onSuccess(SessionResult result) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
Log.w(
|
||||
TAG, "custom command " + action + " produced an error: " + t.getMessage(), t);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
// We should never reach this.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateNotification(MediaSession session) {
|
||||
@Nullable ListenableFuture<MediaController> controllerFuture = controllerMap.get(session);
|
||||
if (controllerFuture == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MediaController mediaController;
|
||||
try {
|
||||
mediaController = checkStateNotNull(Futures.getDone(controllerFuture));
|
||||
} catch (ExecutionException e) {
|
||||
// We should never reach this point.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
if (!mediaSessionService.isSessionAdded(session) || !canStartPlayback(session.getPlayer())) {
|
||||
maybeStopForegroundService(/* removeNotifications= */ true);
|
||||
return;
|
||||
@ -157,10 +175,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
MediaNotification mediaNotification =
|
||||
this.mediaNotificationProvider.createNotification(
|
||||
mediaController,
|
||||
checkStateNotNull(customLayoutMap.get(session)),
|
||||
actionFactory,
|
||||
callback);
|
||||
session, checkStateNotNull(customLayoutMap.get(session)), actionFactory, callback);
|
||||
updateNotificationInternal(session, mediaNotification);
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ import android.view.KeyEvent;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.media.MediaSessionManager.RemoteUserInfo;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.DeviceInfo;
|
||||
@ -247,6 +248,14 @@ public class MediaSession {
|
||||
* Sets a {@link PendingIntent} to launch an {@link android.app.Activity} for the {@link
|
||||
* MediaSession}. This can be used as a quick link to an ongoing media screen.
|
||||
*
|
||||
* <p>A client can use this pending intent to start an activity belonging to this session. When
|
||||
* this pending intent is for instance included in the notification {@linkplain
|
||||
* NotificationCompat.Builder#setContentIntent(PendingIntent) as the content intent}, tapping
|
||||
* the notification will open this activity.
|
||||
*
|
||||
* <p>See <a href="https://developer.android.com/training/notify-user/navigation">'Start an
|
||||
* Activity from a Notification'</a> also.
|
||||
*
|
||||
* @param pendingIntent The pending intent.
|
||||
* @return The builder to allow chaining.
|
||||
*/
|
||||
@ -514,6 +523,17 @@ public class MediaSession {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PendingIntent} to launch {@linkplain
|
||||
* Builder#setSessionActivity(PendingIntent) the session activity} or null if not set.
|
||||
*
|
||||
* @return The {@link PendingIntent} to launch an activity belonging to the session.
|
||||
*/
|
||||
@Nullable
|
||||
public PendingIntent getSessionActivity() {
|
||||
return impl.getSessionActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the underlying {@link Player} for this session to dispatch incoming events to.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user