Add session extras to the state of the controller

This change adds `MediaController.getSessionExtras()` through
which a controller can access the session extras.

The session extras can be set for the entire session when
building the session. This can be overridden for specific
controllers in `MediaSession.Callback.onConnect`.

PiperOrigin-RevId: 584430419
This commit is contained in:
bachinger 2023-11-21 14:29:56 -08:00 committed by Copybara-Service
parent 1d61c48266
commit a063d137b4
16 changed files with 383 additions and 57 deletions

View File

@ -53,6 +53,8 @@ import java.util.List;
public final Bundle tokenExtras;
public final Bundle sessionExtras;
public final PlayerInfo playerInfo;
public final ImmutableList<CommandButton> customLayout;
@ -67,6 +69,7 @@ import java.util.List;
Player.Commands playerCommandsFromSession,
Player.Commands playerCommandsFromPlayer,
Bundle tokenExtras,
Bundle sessionExtras,
PlayerInfo playerInfo) {
this.libraryVersion = libraryVersion;
this.sessionInterfaceVersion = sessionInterfaceVersion;
@ -77,6 +80,7 @@ import java.util.List;
this.playerCommandsFromSession = playerCommandsFromSession;
this.playerCommandsFromPlayer = playerCommandsFromPlayer;
this.tokenExtras = tokenExtras;
this.sessionExtras = sessionExtras;
this.playerInfo = playerInfo;
}
@ -90,11 +94,12 @@ import java.util.List;
private static final String FIELD_PLAYER_COMMANDS_FROM_SESSION = Util.intToStringMaxRadix(4);
private static final String FIELD_PLAYER_COMMANDS_FROM_PLAYER = Util.intToStringMaxRadix(5);
private static final String FIELD_TOKEN_EXTRAS = Util.intToStringMaxRadix(6);
private static final String FIELD_SESSION_EXTRAS = Util.intToStringMaxRadix(11);
private static final String FIELD_PLAYER_INFO = Util.intToStringMaxRadix(7);
private static final String FIELD_SESSION_INTERFACE_VERSION = Util.intToStringMaxRadix(8);
private static final String FIELD_IN_PROCESS_BINDER = Util.intToStringMaxRadix(10);
// Next field key = 11
// Next field key = 12
@Override
public Bundle toBundle() {
@ -115,6 +120,7 @@ import java.util.List;
bundle.putBundle(FIELD_PLAYER_COMMANDS_FROM_SESSION, playerCommandsFromSession.toBundle());
bundle.putBundle(FIELD_PLAYER_COMMANDS_FROM_PLAYER, playerCommandsFromPlayer.toBundle());
bundle.putBundle(FIELD_TOKEN_EXTRAS, tokenExtras);
bundle.putBundle(FIELD_SESSION_EXTRAS, sessionExtras);
Player.Commands intersectedCommands =
MediaUtils.intersect(playerCommandsFromSession, playerCommandsFromPlayer);
bundle.putBundle(
@ -179,6 +185,7 @@ import java.util.List;
? Player.Commands.EMPTY
: Player.Commands.fromBundle(playerCommandsFromSessionBundle);
@Nullable Bundle tokenExtras = bundle.getBundle(FIELD_TOKEN_EXTRAS);
@Nullable Bundle sessionExtras = bundle.getBundle(FIELD_SESSION_EXTRAS);
@Nullable Bundle playerInfoBundle = bundle.getBundle(FIELD_PLAYER_INFO);
PlayerInfo playerInfo =
playerInfoBundle == null ? PlayerInfo.DEFAULT : PlayerInfo.fromBundle(playerInfoBundle);
@ -192,6 +199,7 @@ import java.util.List;
playerCommandsFromSession,
playerCommandsFromPlayer,
tokenExtras == null ? Bundle.EMPTY : tokenExtras,
sessionExtras == null ? Bundle.EMPTY : sessionExtras,
playerInfo);
}

View File

@ -408,10 +408,10 @@ public class MediaController implements Player {
}
/**
* Called when the session extras have changed.
* Called when the session extras are set on the session side.
*
* @param controller The controller.
* @param extras The session extras that have changed.
* @param extras The session extras that have been set on the session.
*/
default void onExtrasChanged(MediaController controller, Bundle extras) {}
@ -967,6 +967,20 @@ public class MediaController implements Player {
return isConnected() ? impl.getCustomLayout() : ImmutableList.of();
}
/**
* Returns the session extras.
*
* <p>After being connected, {@link Listener#onExtrasChanged(MediaController, Bundle)} is called
* when the extras on the session are set.
*
* @return The session extras.
*/
@UnstableApi
public final Bundle getSessionExtras() {
verifyApplicationThread();
return isConnected() ? impl.getSessionExtras() : Bundle.EMPTY;
}
/** Returns {@code null}. */
@UnstableApi
@Override
@ -2029,6 +2043,8 @@ public class MediaController implements Player {
ImmutableList<CommandButton> getCustomLayout();
Bundle getSessionExtras();
Timeline getCurrentTimeline();
void setMediaItem(MediaItem mediaItem);

View File

@ -134,6 +134,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
private long lastSetPlayWhenReadyCalledTimeMs;
@Nullable private PlayerInfo pendingPlayerInfo;
@Nullable private BundlingExclusions pendingBundlingExclusions;
private Bundle sessionExtras;
public MediaControllerImplBase(
Context context,
@ -173,6 +174,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
.getInstance()
.runOnApplicationLooper(MediaControllerImplBase.this.getInstance()::release);
surfaceCallback = new SurfaceCallback();
sessionExtras = Bundle.EMPTY;
serviceConnection =
(this.token.getType() == SessionToken.TYPE_SESSION)
@ -727,6 +729,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
return customLayout;
}
@Override
public Bundle getSessionExtras() {
return sessionExtras;
}
@Override
public Timeline getCurrentTimeline() {
return playerInfo.timeline;
@ -2545,6 +2552,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
token.getPackageName(),
result.sessionBinder,
result.tokenExtras);
sessionExtras = result.sessionExtras;
getInstance().notifyAccepted();
}
@ -2763,6 +2771,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
if (!isConnected()) {
return;
}
sessionExtras = extras;
getInstance()
.notifyControllerListener(listener -> listener.onExtrasChanged(getInstance(), extras));
}

View File

@ -191,7 +191,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
maskedPlayerInfo,
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -255,7 +256,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
/* playerError= */ null),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -376,7 +378,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
maskedPlayerInfo,
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo, discontinuityReason, mediaItemTransitionReason);
}
@ -414,6 +417,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
return controllerInfo.customLayout;
}
@Override
public Bundle getSessionExtras() {
return controllerInfo.sessionExtras;
}
@Override
@Nullable
public PlaybackException getPlayerError() {
@ -529,7 +537,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerInfo.playerInfo.copyWithPlaybackParameters(playbackParameters),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -548,7 +557,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerInfo.playerInfo.copyWithPlaybackParameters(new PlaybackParameters(speed)),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -640,7 +650,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
maskedPlayerInfo,
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -703,7 +714,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
maskedPlayerInfo,
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -755,7 +767,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
maskedPlayerInfo,
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -821,7 +834,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
maskedPlayerInfo,
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -933,7 +947,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerInfo.playerInfo.copyWithRepeatMode(repeatMode),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -959,7 +974,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerInfo.playerInfo.copyWithShuffleModeEnabled(shuffleModeEnabled),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -1081,7 +1097,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerInfo.playerInfo.copyWithDeviceVolume(volume, isDeviceMuted),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -1112,7 +1129,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerInfo.playerInfo.copyWithDeviceVolume(volume + 1, isDeviceMuted),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -1142,7 +1160,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerInfo.playerInfo.copyWithDeviceVolume(volume - 1, isDeviceMuted),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -1175,7 +1194,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerInfo.playerInfo.copyWithDeviceVolume(volume, muted),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -1212,7 +1232,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
Player.PLAYBACK_SUPPRESSION_REASON_NONE),
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout);
controllerInfo.customLayout,
controllerInfo.sessionExtras);
updateStateMaskedControllerInfo(
maskedControllerInfo,
/* discontinuityReason= */ null,
@ -1308,7 +1329,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
convertToNonNullQueueItemList(controllerCompat.getQueue()),
controllerCompat.getQueueTitle(),
controllerCompat.getRepeatMode(),
controllerCompat.getShuffleMode());
controllerCompat.getShuffleMode(),
controllerCompat.getExtras());
handleNewLegacyParameters(/* notifyConnected= */ true, newLegacyPlayerInfo);
}
@ -1823,6 +1845,13 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
@Override
public void onExtrasChanged(Bundle extras) {
controllerInfo =
new ControllerInfo(
controllerInfo.playerInfo,
controllerInfo.availableSessionCommands,
controllerInfo.availablePlayerCommands,
controllerInfo.customLayout,
extras);
getInstance()
.notifyControllerListener(listener -> listener.onExtrasChanged(getInstance(), extras));
}
@ -2068,6 +2097,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
availableSessionCommands,
availablePlayerCommands,
customLayout,
newLegacyPlayerInfo.sessionExtras,
playerError,
durationMs,
currentPositionMs,
@ -2235,6 +2265,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
SessionCommands availableSessionCommands,
Commands availablePlayerCommands,
ImmutableList<CommandButton> customLayout,
Bundle sessionExtras,
@Nullable PlaybackException playerError,
long durationMs,
long currentPositionMs,
@ -2305,7 +2336,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
/* parameters= */ TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT);
return new ControllerInfo(
playerInfo, availableSessionCommands, availablePlayerCommands, customLayout);
playerInfo, availableSessionCommands, availablePlayerCommands, customLayout, sessionExtras);
}
private static PositionInfo createPositionInfo(
@ -2355,6 +2386,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
@Nullable public final CharSequence queueTitle;
@PlaybackStateCompat.RepeatMode public final int repeatMode;
@PlaybackStateCompat.ShuffleMode public final int shuffleMode;
public final Bundle sessionExtras;
public LegacyPlayerInfo() {
playbackInfoCompat = null;
@ -2364,6 +2396,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queueTitle = null;
repeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
sessionExtras = Bundle.EMPTY;
}
public LegacyPlayerInfo(
@ -2373,7 +2406,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
List<QueueItem> queue,
@Nullable CharSequence queueTitle,
@PlaybackStateCompat.RepeatMode int repeatMode,
@PlaybackStateCompat.ShuffleMode int shuffleMode) {
@PlaybackStateCompat.ShuffleMode int shuffleMode,
Bundle sessionExtras) {
this.playbackInfoCompat = playbackInfoCompat;
this.playbackStateCompat = playbackStateCompat;
this.mediaMetadataCompat = mediaMetadataCompat;
@ -2381,6 +2415,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
this.queueTitle = queueTitle;
this.repeatMode = repeatMode;
this.shuffleMode = shuffleMode;
this.sessionExtras = sessionExtras;
}
public LegacyPlayerInfo(LegacyPlayerInfo other) {
@ -2391,6 +2426,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queueTitle = other.queueTitle;
repeatMode = other.repeatMode;
shuffleMode = other.shuffleMode;
sessionExtras = other.sessionExtras;
}
@CheckResult
@ -2405,7 +2441,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queue,
queueTitle,
repeatMode,
shuffleMode);
shuffleMode,
sessionExtras);
}
@CheckResult
@ -2418,7 +2455,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queue,
queueTitle,
repeatMode,
shuffleMode);
shuffleMode,
sessionExtras);
}
@CheckResult
@ -2431,7 +2469,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queue,
queueTitle,
repeatMode,
shuffleMode);
shuffleMode,
sessionExtras);
}
@CheckResult
@ -2443,7 +2482,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queue,
queueTitle,
repeatMode,
shuffleMode);
shuffleMode,
sessionExtras);
}
@CheckResult
@ -2455,7 +2495,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queue,
queueTitle,
repeatMode,
shuffleMode);
shuffleMode,
sessionExtras);
}
@CheckResult
@ -2468,7 +2509,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queue,
queueTitle,
repeatMode,
shuffleMode);
shuffleMode,
sessionExtras);
}
@CheckResult
@ -2480,7 +2522,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queue,
queueTitle,
repeatMode,
shuffleMode);
shuffleMode,
sessionExtras);
}
@CheckResult
@ -2492,7 +2535,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
queue,
queueTitle,
repeatMode,
shuffleMode);
shuffleMode,
sessionExtras);
}
}
@ -2502,23 +2546,27 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
public final SessionCommands availableSessionCommands;
public final Commands availablePlayerCommands;
public final ImmutableList<CommandButton> customLayout;
public final Bundle sessionExtras;
public ControllerInfo() {
playerInfo = PlayerInfo.DEFAULT.copyWithTimeline(QueueTimeline.DEFAULT);
availableSessionCommands = SessionCommands.EMPTY;
availablePlayerCommands = Commands.EMPTY;
customLayout = ImmutableList.of();
sessionExtras = Bundle.EMPTY;
}
public ControllerInfo(
PlayerInfo playerInfo,
SessionCommands availableSessionCommands,
Commands availablePlayerCommands,
ImmutableList<CommandButton> customLayout) {
ImmutableList<CommandButton> customLayout,
Bundle sessionExtras) {
this.playerInfo = playerInfo;
this.availableSessionCommands = availableSessionCommands;
this.availablePlayerCommands = availablePlayerCommands;
this.customLayout = customLayout;
this.sessionExtras = sessionExtras;
}
}
}

View File

@ -440,16 +440,34 @@ public abstract class MediaLibraryService extends MediaSessionService {
}
/**
* Sets an extra {@link Bundle} for the {@link MediaLibrarySession}. The {@link
* MediaLibrarySession#getToken()} session token} will have the {@link
* SessionToken#getExtras() extras}. If not set, an empty {@link Bundle} will be used.
* Sets an extras {@link Bundle} for the {@linkplain MediaLibrarySession#getToken() session
* token}. If not set, {@link Bundle#EMPTY} is used.
*
* @param extras The extra {@link Bundle}.
* <p>A controller has access to these extras through the {@linkplain
* MediaController#getConnectedToken() connected token}.
*
* @param tokenExtras The extras {@link Bundle}.
* @return The builder to allow chaining.
*/
@Override
public Builder setExtras(Bundle extras) {
return super.setExtras(extras);
public Builder setExtras(Bundle tokenExtras) {
return super.setExtras(tokenExtras);
}
/**
* Sets the {@linkplain MediaLibrarySession#getSessionExtras() session extras}. If not set,
* {@link Bundle#EMPTY} is used.
*
* <p>A controller has access to session extras through {@link
* MediaController#getSessionExtras()}.
*
* @param sessionExtras The session extras {@link Bundle}.
* @return The builder to allow chaining.
*/
@UnstableApi
@Override
public Builder setSessionExtras(Bundle sessionExtras) {
return super.setSessionExtras(sessionExtras);
}
/**
@ -551,7 +569,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
sessionActivity,
customLayout,
callback,
extras,
tokenExtras,
sessionExtras,
checkNotNull(bitmapLoader),
playIfSuppressed,
isPeriodicPositionUpdateEnabled);
@ -566,6 +585,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
ImmutableList<CommandButton> customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
Bundle sessionExtras,
BitmapLoader bitmapLoader,
boolean playIfSuppressed,
boolean isPeriodicPositionUpdateEnabled) {
@ -577,6 +597,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
customLayout,
callback,
tokenExtras,
sessionExtras,
bitmapLoader,
playIfSuppressed,
isPeriodicPositionUpdateEnabled);
@ -591,6 +612,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
ImmutableList<CommandButton> customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
Bundle sessionExtras,
BitmapLoader bitmapLoader,
boolean playIfSuppressed,
boolean isPeriodicPositionUpdateEnabled) {
@ -603,6 +625,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
customLayout,
(Callback) callback,
tokenExtras,
sessionExtras,
bitmapLoader,
playIfSuppressed,
isPeriodicPositionUpdateEnabled);

View File

@ -74,6 +74,7 @@ import java.util.concurrent.Future;
ImmutableList<CommandButton> customLayout,
MediaLibrarySession.Callback callback,
Bundle tokenExtras,
Bundle sessionExtras,
BitmapLoader bitmapLoader,
boolean playIfSuppressed,
boolean isPeriodicPositionUpdateEnabled) {
@ -86,6 +87,7 @@ import java.util.concurrent.Future;
customLayout,
callback,
tokenExtras,
sessionExtras,
bitmapLoader,
playIfSuppressed,
isPeriodicPositionUpdateEnabled);

View File

@ -318,16 +318,34 @@ public class MediaSession {
}
/**
* Sets an extra {@link Bundle} for the {@link MediaSession}. The {@link
* MediaSession#getToken()} session token} will have the {@link SessionToken#getExtras()
* extras}. If not set, an empty {@link Bundle} will be used.
* Sets an extras {@link Bundle} for the {@linkplain MediaSession#getToken() session token}. If
* not set, {@link Bundle#EMPTY} is used.
*
* @param extras The extra {@link Bundle}.
* <p>A controller has access to these extras through the {@linkplain
* MediaController#getConnectedToken() connected token}.
*
* @param tokenExtras The extras {@link Bundle}.
* @return The builder to allow chaining.
*/
@Override
public Builder setExtras(Bundle extras) {
return super.setExtras(extras);
public Builder setExtras(Bundle tokenExtras) {
return super.setExtras(tokenExtras);
}
/**
* Sets the {@linkplain MediaSession#getSessionExtras() session extras}. If not set, {@link
* Bundle#EMPTY} is used.
*
* <p>A controller has access to session extras through {@linkplain
* MediaController#getSessionExtras()}.
*
* @param sessionExtras The session extras {@link Bundle}.
* @return The builder to allow chaining.
*/
@UnstableApi
@Override
public Builder setSessionExtras(Bundle sessionExtras) {
return super.setSessionExtras(sessionExtras);
}
/**
@ -426,7 +444,8 @@ public class MediaSession {
sessionActivity,
customLayout,
callback,
extras,
tokenExtras,
sessionExtras,
checkNotNull(bitmapLoader),
playIfSuppressed,
isPeriodicPositionUpdateEnabled);
@ -621,6 +640,7 @@ public class MediaSession {
ImmutableList<CommandButton> customLayout,
Callback callback,
Bundle tokenExtras,
Bundle sessionExtras,
BitmapLoader bitmapLoader,
boolean playIfSuppressed,
boolean isPeriodicPositionUpdateEnabled) {
@ -639,6 +659,7 @@ public class MediaSession {
customLayout,
callback,
tokenExtras,
sessionExtras,
bitmapLoader,
playIfSuppressed,
isPeriodicPositionUpdateEnabled);
@ -652,6 +673,7 @@ public class MediaSession {
ImmutableList<CommandButton> customLayout,
Callback callback,
Bundle tokenExtras,
Bundle sessionExtras,
BitmapLoader bitmapLoader,
boolean playIfSuppressed,
boolean isPeriodicPositionUpdateEnabled) {
@ -664,6 +686,7 @@ public class MediaSession {
customLayout,
callback,
tokenExtras,
sessionExtras,
bitmapLoader,
playIfSuppressed,
isPeriodicPositionUpdateEnabled);
@ -982,7 +1005,23 @@ public class MediaSession {
}
/**
* Sends the session extras to connected controllers.
* Returns the session extras.
*
* <p>For informational purpose only. Mutations on the {@link Bundle} do not have immediate
* effect. To change the session extras use {@link #setSessionExtras(Bundle)} or {@link
* #setSessionExtras(ControllerInfo, Bundle)}.
*/
@UnstableApi
public Bundle getSessionExtras() {
return impl.getSessionExtras();
}
/**
* Sets the {@linkplain #getSessionExtras() session extras} and sends them to connected
* controllers.
*
* <p>The initial extras can be set {@linkplain Builder#setSessionExtras(Bundle) when building the
* session}.
*
* <p>This is a synchronous call and doesn't wait for results from the controllers.
*
@ -996,6 +1035,11 @@ public class MediaSession {
/**
* Sends the session extras to the connected controller.
*
* <p>The initial extras for a specific controller can be set in {@link
* Callback#onConnect(MediaSession, ControllerInfo)} when {@link
* ConnectionResult.AcceptedResultBuilder#setSessionExtras(Bundle) building the connection
* result}.
*
* <p>This is a synchronous call and doesn't wait for results from the controller.
*
* <p>Interoperability: This call has no effect when called for a {@linkplain
@ -1549,6 +1593,7 @@ public class MediaSession {
private SessionCommands availableSessionCommands;
private Player.Commands availablePlayerCommands = DEFAULT_PLAYER_COMMANDS;
@Nullable private ImmutableList<CommandButton> customLayout;
@Nullable private Bundle sessionExtras;
/**
* Creates an instance.
@ -1611,10 +1656,26 @@ public class MediaSession {
return this;
}
/**
* Sets the session extras, overriding the {@linkplain MediaSession#getSessionExtras() extras
* of the session}.
*
* <p>The default is null to indicate that the extras of the session should be used.
*/
@CanIgnoreReturnValue
public AcceptedResultBuilder setSessionExtras(Bundle sessionExtras) {
this.sessionExtras = sessionExtras;
return this;
}
/** Returns a new {@link ConnectionResult} instance for accepting a connection. */
public ConnectionResult build() {
return new ConnectionResult(
/* accepted= */ true, availableSessionCommands, availablePlayerCommands, customLayout);
/* accepted= */ true,
availableSessionCommands,
availablePlayerCommands,
customLayout,
sessionExtras);
}
}
@ -1642,16 +1703,21 @@ public class MediaSession {
/** The custom layout or null if the custom layout of the session should be used. */
@UnstableApi @Nullable public final ImmutableList<CommandButton> customLayout;
/** The session extras. */
@UnstableApi @Nullable public final Bundle sessionExtras;
/** Creates a new instance with the given available session and player commands. */
private ConnectionResult(
boolean accepted,
SessionCommands availableSessionCommands,
Player.Commands availablePlayerCommands,
@Nullable ImmutableList<CommandButton> customLayout) {
@Nullable ImmutableList<CommandButton> customLayout,
@Nullable Bundle sessionExtras) {
isAccepted = accepted;
this.availableSessionCommands = availableSessionCommands;
this.availablePlayerCommands = availablePlayerCommands;
this.customLayout = customLayout;
this.sessionExtras = sessionExtras;
}
/**
@ -1670,13 +1736,18 @@ public class MediaSession {
/* accepted= */ true,
availableSessionCommands,
availablePlayerCommands,
/* customLayout= */ null);
/* customLayout= */ null,
/* sessionExtras= */ null);
}
/** Creates a {@link ConnectionResult} to reject a connection. */
public static ConnectionResult reject() {
return new ConnectionResult(
/* accepted= */ false, SessionCommands.EMPTY, Player.Commands.EMPTY, ImmutableList.of());
/* accepted= */ false,
SessionCommands.EMPTY,
Player.Commands.EMPTY,
/* customLayout= */ ImmutableList.of(),
/* sessionExtras= */ Bundle.EMPTY);
}
}
@ -1850,7 +1921,8 @@ public class MediaSession {
/* package */ String id;
/* package */ CallbackT callback;
/* package */ @Nullable PendingIntent sessionActivity;
/* package */ Bundle extras;
/* package */ Bundle tokenExtras;
/* package */ Bundle sessionExtras;
/* package */ @MonotonicNonNull BitmapLoader bitmapLoader;
/* package */ boolean playIfSuppressed;
/* package */ ImmutableList<CommandButton> customLayout;
@ -1862,7 +1934,8 @@ public class MediaSession {
checkArgument(player.canAdvertiseSession());
id = "";
this.callback = callback;
extras = Bundle.EMPTY;
tokenExtras = Bundle.EMPTY;
sessionExtras = Bundle.EMPTY;
customLayout = ImmutableList.of();
playIfSuppressed = true;
isPeriodicPositionUpdateEnabled = true;
@ -1887,8 +1960,14 @@ public class MediaSession {
}
@SuppressWarnings("unchecked")
public BuilderT setExtras(Bundle extras) {
this.extras = new Bundle(checkNotNull(extras));
public BuilderT setExtras(Bundle tokenExtras) {
this.tokenExtras = new Bundle(checkNotNull(tokenExtras));
return (BuilderT) this;
}
@SuppressWarnings("unchecked")
public BuilderT setSessionExtras(Bundle sessionExtras) {
this.sessionExtras = new Bundle(checkNotNull(sessionExtras));
return (BuilderT) this;
}

View File

@ -149,6 +149,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private long sessionPositionUpdateDelayMs;
private boolean isMediaNotificationControllerConnected;
private ImmutableList<CommandButton> customLayout;
private Bundle sessionExtras;
public MediaSessionImpl(
MediaSession instance,
@ -159,6 +160,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ImmutableList<CommandButton> customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
Bundle sessionExtras,
BitmapLoader bitmapLoader,
boolean playIfSuppressed,
boolean isPeriodicPositionUpdateEnabled) {
@ -168,6 +170,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.sessionActivity = sessionActivity;
this.customLayout = customLayout;
this.callback = callback;
this.sessionExtras = sessionExtras;
this.bitmapLoader = bitmapLoader;
this.playIfSuppressed = playIfSuppressed;
this.isPeriodicPositionUpdateEnabled = isPeriodicPositionUpdateEnabled;
@ -481,6 +484,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
public void setSessionExtras(Bundle sessionExtras) {
this.sessionExtras = sessionExtras;
dispatchRemoteControllerTaskWithoutReturn(
(controller, seq) -> controller.onSessionExtrasChanged(seq, sessionExtras));
}
@ -489,8 +493,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (sessionStub.getConnectedControllersManager().isConnected(controller)) {
dispatchRemoteControllerTaskWithoutReturn(
controller, (callback, seq) -> callback.onSessionExtrasChanged(seq, sessionExtras));
if (isMediaNotificationController(controller)) {
dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onSessionExtrasChanged(seq, sessionExtras));
}
}
}
public Bundle getSessionExtras() {
return sessionExtras;
}
public BitmapLoader getBitmapLoader() {
return bitmapLoader;

View File

@ -528,6 +528,9 @@ import java.util.concurrent.ExecutionException;
connectionResult.availablePlayerCommands,
playerWrapper.getAvailableCommands(),
sessionImpl.getToken().getExtras(),
connectionResult.sessionExtras != null
? connectionResult.sessionExtras
: sessionImpl.getSessionExtras(),
playerInfo);
// Double check if session is still there, because release() can be called in

View File

@ -29,6 +29,7 @@ interface IRemoteMediaController {
// MediaController Methods
Bundle getConnectedSessionToken(String controllerId);
Bundle getSessionExtras(String controllerId);
void play(String controllerId);
void pause(String controllerId);
void setPlayWhenReady(String controllerId, boolean playWhenReady);

View File

@ -1895,7 +1895,7 @@ public class MediaControllerListenerTest {
.getMockPlayer()
.setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
threadTestRule.getHandler().postAndSync(() -> controller.setTimeDiffMs(/* timeDiff= */ 0L));
threadTestRule.getHandler().postAndSync(() -> controller.setTimeDiffMs(/* timeDiffMs= */ 0L));
CountDownLatch latch = new CountDownLatch(2);
AtomicBoolean isPlayingRef = new AtomicBoolean();
AtomicLong currentPositionMsRef = new AtomicLong();
@ -2136,7 +2136,7 @@ public class MediaControllerListenerTest {
remoteMockPlayer.setCurrentAdGroupIndex(testCurrentAdGroupIndex);
remoteMockPlayer.setCurrentAdIndexInAdGroup(testCurrentAdIndexInAdGroup);
remoteMockPlayer.notifyPositionDiscontinuity(
/* oldPositionInfo= */ SessionPositionInfo.DEFAULT_POSITION_INFO,
/* oldPosition= */ SessionPositionInfo.DEFAULT_POSITION_INFO,
newPositionInfo,
Player.DISCONTINUITY_REASON_INTERNAL);
@ -2470,11 +2470,13 @@ public class MediaControllerListenerTest {
sessionExtras.putString("key-0", "value-0");
CountDownLatch latch = new CountDownLatch(1);
List<Bundle> receivedSessionExtras = new ArrayList<>();
List<Bundle> getterSessionExtras = new ArrayList<>();
MediaController.Listener listener =
new MediaController.Listener() {
@Override
public void onExtrasChanged(MediaController controller, Bundle extras) {
receivedSessionExtras.add(extras);
getterSessionExtras.add(controller.getSessionExtras());
latch.countDown();
}
};
@ -2487,6 +2489,8 @@ public class MediaControllerListenerTest {
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedSessionExtras).hasSize(1);
assertThat(TestUtils.equals(receivedSessionExtras.get(0), sessionExtras)).isTrue();
assertThat(getterSessionExtras).hasSize(1);
assertThat(TestUtils.equals(getterSessionExtras.get(0), sessionExtras)).isTrue();
}
@Test
@ -2495,11 +2499,13 @@ public class MediaControllerListenerTest {
sessionExtras.putString("key-0", "value-0");
CountDownLatch latch = new CountDownLatch(1);
List<Bundle> receivedSessionExtras = new ArrayList<>();
List<Bundle> getterSessionExtras = new ArrayList<>();
MediaController.Listener listener =
new MediaController.Listener() {
@Override
public void onExtrasChanged(MediaController controller, Bundle extras) {
receivedSessionExtras.add(extras);
getterSessionExtras.add(controller.getSessionExtras());
latch.countDown();
}
};
@ -2513,6 +2519,7 @@ public class MediaControllerListenerTest {
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(receivedSessionExtras).hasSize(1);
assertThat(TestUtils.equals(receivedSessionExtras.get(0), sessionExtras)).isTrue();
assertThat(TestUtils.equals(getterSessionExtras.get(0), sessionExtras)).isTrue();
}
@Test

View File

@ -188,12 +188,14 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
sessionExtras.putString("key-1", "value-1");
CountDownLatch countDownLatch = new CountDownLatch(1);
List<Bundle> receivedSessionExtras = new ArrayList<>();
List<Bundle> getterSessionExtras = new ArrayList<>();
controllerTestRule.createController(
session.getSessionToken(),
new MediaController.Listener() {
@Override
public void onExtrasChanged(MediaController controller, Bundle extras) {
receivedSessionExtras.add(extras);
getterSessionExtras.add(controller.getSessionExtras());
countDownLatch.countDown();
}
});
@ -202,6 +204,22 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
assertThat(countDownLatch.await(1_000, MILLISECONDS)).isTrue();
assertThat(TestUtils.equals(receivedSessionExtras.get(0), sessionExtras)).isTrue();
assertThat(TestUtils.equals(getterSessionExtras.get(0), sessionExtras)).isTrue();
}
@Test
public void setSessionExtras_includedWhenConnecting() throws Exception {
Bundle sessionExtras = new Bundle();
sessionExtras.putString("key-1", "value-1");
session.setExtras(sessionExtras);
MediaController controller = controllerTestRule.createController(session.getSessionToken());
assertThat(
TestUtils.equals(
threadTestRule.getHandler().postAndSync(controller::getSessionExtras),
sessionExtras))
.isTrue();
}
@Test

View File

@ -422,6 +422,23 @@ public class MediaControllerTest {
session.cleanUp();
}
@Test
public void getSessionExtras_includedInConnectionStateWhenConnecting() throws Exception {
RemoteMediaSession session =
createRemoteMediaSession(TEST_GET_CUSTOM_LAYOUT, /* tokenExtras= */ null);
Bundle sessionExtras = new Bundle();
sessionExtras.putString("key1", "value1");
session.setSessionExtras(sessionExtras);
MediaController controller = controllerTestRule.createController(session.getToken());
assertThat(
threadTestRule.getHandler().postAndSync(controller::getSessionExtras).getString("key1"))
.isEqualTo("value1");
session.cleanUp();
}
@Test
public void getAvailableCommands_emptyPlayerCommands_commandReleaseStillAvailable()
throws Exception {
@ -772,7 +789,7 @@ public class MediaControllerTest {
session.setAvailableCommands(builder.build(), Player.Commands.EMPTY);
String testMediaId = "testMediaId";
Rating testRating = new HeartRating(/* hasHeart= */ true);
Rating testRating = new HeartRating(/* isHeart= */ true);
threadTestRule
.getHandler()
.postAndSync(

View File

@ -234,6 +234,80 @@ public class MediaSessionCallbackTest {
assertThat(remoteController.getAvailableCommands().contains(Player.COMMAND_RELEASE)).isTrue();
}
@Test
public void onConnect_connectionResultExtrasAreNull_usesSessionExtras() throws Exception {
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public MediaSession.ConnectionResult onConnect(
MediaSession session, ControllerInfo controller) {
return new AcceptedResultBuilder(session)
.setAvailablePlayerCommands(Player.Commands.EMPTY)
.build();
}
};
Bundle sessionExtras = new Bundle();
sessionExtras.putString("origin", "session");
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionExtras(sessionExtras)
.setCallback(callback)
.setId("onConnect_connectionResultExtrasAreNull_usesGlobalSessionExtras")
.build());
RemoteMediaController remoteController =
remoteControllerTestRule.createRemoteController(session.getToken());
assertThat(remoteController.getSessionExtras().getString("origin")).isEqualTo("session");
}
@Test
public void onConnect_connectionResultExtrasAreSet_usesControllerSpecificSessionExtras()
throws Exception {
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public MediaSession.ConnectionResult onConnect(
MediaSession session, ControllerInfo controller) {
Bundle sessionExtras = new Bundle();
sessionExtras.putString("origin", "controller");
return new AcceptedResultBuilder(session)
.setSessionExtras(sessionExtras)
.setAvailablePlayerCommands(Player.Commands.EMPTY)
.build();
}
};
Bundle sessionExtras = new Bundle();
sessionExtras.putString("origin", "session");
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionExtras(sessionExtras)
.setCallback(callback)
.setId("onConnect_connectionResultExtrasAreSet_usesControllerSpecificSessionExtras")
.build());
RemoteMediaController remoteController =
remoteControllerTestRule.createRemoteController(session.getToken());
assertThat(remoteController.getSessionExtras().getString("origin")).isEqualTo("controller");
}
@Test
public void onConnect_connectionResultDefault_emptySessionExtras() throws Exception {
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setId("onConnect_connectionResultDefault_emptySessionExtras")
.build());
RemoteMediaController remoteController =
remoteControllerTestRule.createRemoteController(session.getToken());
assertThat(remoteController.getSessionExtras().size()).isEqualTo(0);
}
@Test
public void onPostConnect_afterConnected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);

View File

@ -175,6 +175,11 @@ public class MediaControllerProviderService extends Service {
});
}
@Override
public Bundle getSessionExtras(String controllerId) throws RemoteException {
return runOnHandler(mediaControllerMap.get(controllerId)::getSessionExtras);
}
@Override
public void play(String controllerId) throws RemoteException {
runOnHandler(

View File

@ -97,6 +97,10 @@ public class RemoteMediaController {
return sessionTokenBundle == null ? null : SessionToken.fromBundle(sessionTokenBundle);
}
public Bundle getSessionExtras() throws RemoteException {
return binder.getSessionExtras(controllerId);
}
public void play() throws RemoteException {
binder.play(controllerId);
}