Add missing null and Bundle checks in MediaSession/ControllerStub

PiperOrigin-RevId: 599477547
This commit is contained in:
tonihei 2024-01-18 05:27:21 -08:00 committed by Copybara-Service
parent 616cb943f0
commit 59dc5b6692
10 changed files with 832 additions and 88 deletions

View File

@ -1466,7 +1466,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
* playback, in microseconds. * playback, in microseconds.
* *
* <p>The default position must be less or equal to the {@linkplain #setDurationUs duration}, * <p>The default position must be less or equal to the {@linkplain #setDurationUs duration},
* is set. * if set.
* *
* @param defaultPositionUs The default position relative to the start of the media item at * @param defaultPositionUs The default position relative to the start of the media item at
* which to begin playback, in microseconds. * which to begin playback, in microseconds.

View File

@ -1970,6 +1970,13 @@ public class MediaController implements Player {
connectionCallback.onAccepted(); connectionCallback.onAccepted();
} }
/** Returns the binder object used to connect to the session. */
@Nullable
@VisibleForTesting(otherwise = NONE)
/* package */ final IMediaController getBinder() {
return impl.getBinder();
}
private void verifyApplicationThread() { private void verifyApplicationThread() {
checkState(Looper.myLooper() == getApplicationLooper(), WRONG_THREAD_ERROR_MESSAGE); checkState(Looper.myLooper() == getApplicationLooper(), WRONG_THREAD_ERROR_MESSAGE);
} }
@ -2215,5 +2222,8 @@ public class MediaController implements Player {
@Nullable @Nullable
MediaBrowserCompat getBrowserCompat(); MediaBrowserCompat getBrowserCompat();
@Nullable
IMediaController getBinder();
} }
} }

View File

@ -1929,6 +1929,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
return null; return null;
} }
@Override
public IMediaController getBinder() {
return controllerStub;
}
private static Timeline createMaskingTimeline(List<Window> windows, List<Period> periods) { private static Timeline createMaskingTimeline(List<Window> windows, List<Period> periods) {
return new RemotableTimeline( return new RemotableTimeline(
new ImmutableList.Builder<Window>().addAll(windows).build(), new ImmutableList.Builder<Window>().addAll(windows).build(),

View File

@ -1316,6 +1316,12 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
return browserCompat; return browserCompat;
} }
@Nullable
@Override
public IMediaController getBinder() {
return null;
}
void onConnected() { void onConnected() {
if (released || connected) { if (released || connected) {
return; return;

View File

@ -47,7 +47,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@Override @Override
public void onSessionResult(int sequenceNum, Bundle sessionResultBundle) { public void onSessionResult(int sequenceNum, @Nullable Bundle sessionResultBundle) {
if (sessionResultBundle == null) {
return;
}
SessionResult result; SessionResult result;
try { try {
result = SessionResult.fromBundle(sessionResultBundle); result = SessionResult.fromBundle(sessionResultBundle);
@ -62,7 +65,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@Override @Override
public void onLibraryResult(int sequenceNum, Bundle libraryResultBundle) { public void onLibraryResult(int sequenceNum, @Nullable Bundle libraryResultBundle) {
if (libraryResultBundle == null) {
return;
}
LibraryResult<?> result; LibraryResult<?> result;
try { try {
result = LibraryResult.fromUnknownBundle(libraryResultBundle); result = LibraryResult.fromUnknownBundle(libraryResultBundle);
@ -77,7 +83,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@Override @Override
public void onConnected(int seq, Bundle connectionResultBundle) { public void onConnected(int seq, @Nullable Bundle connectionResultBundle) {
if (connectionResultBundle == null) {
return;
}
ConnectionState connectionState; ConnectionState connectionState;
try { try {
connectionState = ConnectionState.fromBundle(connectionResultBundle); connectionState = ConnectionState.fromBundle(connectionResultBundle);
@ -97,7 +106,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@Override @Override
public void onSetCustomLayout(int seq, List<Bundle> commandButtonBundleList) { public void onSetCustomLayout(int seq, @Nullable List<Bundle> commandButtonBundleList) {
if (commandButtonBundleList == null) {
return;
}
List<CommandButton> layout; List<CommandButton> layout;
try { try {
layout = layout =
@ -111,7 +123,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
@Override @Override
public void onAvailableCommandsChangedFromSession( public void onAvailableCommandsChangedFromSession(
int seq, Bundle sessionCommandsBundle, Bundle playerCommandsBundle) { int seq, @Nullable Bundle sessionCommandsBundle, @Nullable Bundle playerCommandsBundle) {
if (sessionCommandsBundle == null || playerCommandsBundle == null) {
return;
}
SessionCommands sessionCommands; SessionCommands sessionCommands;
try { try {
sessionCommands = SessionCommands.fromBundle(sessionCommandsBundle); sessionCommands = SessionCommands.fromBundle(sessionCommandsBundle);
@ -132,7 +147,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@Override @Override
public void onAvailableCommandsChangedFromPlayer(int seq, Bundle commandsBundle) { public void onAvailableCommandsChangedFromPlayer(int seq, @Nullable Bundle commandsBundle) {
if (commandsBundle == null) {
return;
}
Commands commandsFromPlayer; Commands commandsFromPlayer;
try { try {
commandsFromPlayer = Commands.fromBundle(commandsBundle); commandsFromPlayer = Commands.fromBundle(commandsBundle);
@ -145,8 +163,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@Override @Override
public void onCustomCommand(int seq, Bundle commandBundle, Bundle args) { public void onCustomCommand(int seq, @Nullable Bundle commandBundle, @Nullable Bundle args) {
if (args == null) { if (commandBundle == null || args == null) {
Log.w(TAG, "Ignoring custom command with null args."); Log.w(TAG, "Ignoring custom command with null args.");
return; return;
} }
@ -161,14 +179,22 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@Override @Override
public void onSessionActivityChanged(int seq, PendingIntent sessionActivity) public void onSessionActivityChanged(int seq, @Nullable PendingIntent sessionActivity)
throws RemoteException { throws RemoteException {
if (sessionActivity == null) {
Log.w(TAG, "Ignoring null session activity intent");
return;
}
dispatchControllerTaskOnHandler( dispatchControllerTaskOnHandler(
controller -> controller.onSetSessionActivity(seq, sessionActivity)); controller -> controller.onSetSessionActivity(seq, sessionActivity));
} }
@Override @Override
public void onPeriodicSessionPositionInfoChanged(int seq, Bundle sessionPositionInfoBundle) { public void onPeriodicSessionPositionInfoChanged(
int seq, @Nullable Bundle sessionPositionInfoBundle) {
if (sessionPositionInfoBundle == null) {
return;
}
SessionPositionInfo sessionPositionInfo; SessionPositionInfo sessionPositionInfo;
try { try {
sessionPositionInfo = SessionPositionInfo.fromBundle(sessionPositionInfoBundle); sessionPositionInfo = SessionPositionInfo.fromBundle(sessionPositionInfoBundle);
@ -185,7 +211,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
*/ */
@Override @Override
@Deprecated @Deprecated
public void onPlayerInfoChanged(int seq, Bundle playerInfoBundle, boolean isTimelineExcluded) { public void onPlayerInfoChanged(
int seq, @Nullable Bundle playerInfoBundle, boolean isTimelineExcluded) {
onPlayerInfoChangedWithExclusions( onPlayerInfoChangedWithExclusions(
seq, seq,
playerInfoBundle, playerInfoBundle,
@ -196,7 +223,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
/** Added in {@link #VERSION_INT} 2. */ /** Added in {@link #VERSION_INT} 2. */
@Override @Override
public void onPlayerInfoChangedWithExclusions( public void onPlayerInfoChangedWithExclusions(
int seq, Bundle playerInfoBundle, Bundle playerInfoExclusions) { int seq, @Nullable Bundle playerInfoBundle, @Nullable Bundle playerInfoExclusions) {
if (playerInfoBundle == null || playerInfoExclusions == null) {
return;
}
PlayerInfo playerInfo; PlayerInfo playerInfo;
try { try {
playerInfo = PlayerInfo.fromBundle(playerInfoBundle); playerInfo = PlayerInfo.fromBundle(playerInfoBundle);
@ -216,7 +246,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@Override @Override
public void onExtrasChanged(int seq, Bundle extras) { public void onExtrasChanged(int seq, @Nullable Bundle extras) {
if (extras == null) {
Log.w(TAG, "Ignoring null Bundle for extras");
return;
}
dispatchControllerTaskOnHandler(controller -> controller.onExtrasChanged(extras)); dispatchControllerTaskOnHandler(controller -> controller.onExtrasChanged(extras));
} }
@ -230,7 +264,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
@Override @Override
public void onSearchResultChanged( public void onSearchResultChanged(
int seq, String query, int itemCount, @Nullable Bundle libraryParams) int seq, @Nullable String query, int itemCount, @Nullable Bundle libraryParamsBundle)
throws RuntimeException { throws RuntimeException {
if (TextUtils.isEmpty(query)) { if (TextUtils.isEmpty(query)) {
Log.w(TAG, "onSearchResultChanged(): Ignoring empty query"); Log.w(TAG, "onSearchResultChanged(): Ignoring empty query");
@ -240,18 +274,22 @@ import org.checkerframework.checker.nullness.qual.NonNull;
Log.w(TAG, "onSearchResultChanged(): Ignoring negative itemCount: " + itemCount); Log.w(TAG, "onSearchResultChanged(): Ignoring negative itemCount: " + itemCount);
return; return;
} }
@Nullable LibraryParams libraryParams;
try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchControllerTaskOnHandler( dispatchControllerTaskOnHandler(
(ControllerTask<MediaBrowserImplBase>) (ControllerTask<MediaBrowserImplBase>)
browser -> browser -> browser.notifySearchResultChanged(query, itemCount, libraryParams));
browser.notifySearchResultChanged(
query,
itemCount,
libraryParams == null ? null : LibraryParams.fromBundle(libraryParams)));
} }
@Override @Override
public void onChildrenChanged( public void onChildrenChanged(
int seq, String parentId, int itemCount, @Nullable Bundle libraryParams) { int seq, @Nullable String parentId, int itemCount, @Nullable Bundle libraryParamsBundle) {
if (TextUtils.isEmpty(parentId)) { if (TextUtils.isEmpty(parentId)) {
Log.w(TAG, "onChildrenChanged(): Ignoring empty parentId"); Log.w(TAG, "onChildrenChanged(): Ignoring empty parentId");
return; return;
@ -260,13 +298,17 @@ import org.checkerframework.checker.nullness.qual.NonNull;
Log.w(TAG, "onChildrenChanged(): Ignoring negative itemCount: " + itemCount); Log.w(TAG, "onChildrenChanged(): Ignoring negative itemCount: " + itemCount);
return; return;
} }
@Nullable LibraryParams libraryParams;
try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchControllerTaskOnHandler( dispatchControllerTaskOnHandler(
(ControllerTask<MediaBrowserImplBase>) (ControllerTask<MediaBrowserImplBase>)
browser -> browser -> browser.notifyChildrenChanged(parentId, itemCount, libraryParams));
browser.notifyChildrenChanged(
parentId,
itemCount,
libraryParams == null ? null : LibraryParams.fromBundle(libraryParams)));
} }
public void destroy() { public void destroy() {

View File

@ -454,7 +454,10 @@ import java.util.concurrent.ExecutionException;
} }
@SuppressWarnings("UngroupedOverloads") // Overload belongs to AIDL interface that is separated. @SuppressWarnings("UngroupedOverloads") // Overload belongs to AIDL interface that is separated.
public void connect(IMediaController caller, ControllerInfo controllerInfo) { public void connect(@Nullable IMediaController caller, @Nullable ControllerInfo controllerInfo) {
if (caller == null || controllerInfo == null) {
return;
}
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get(); @Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
if (sessionImpl == null || sessionImpl.isReleased()) { if (sessionImpl == null || sessionImpl.isReleased()) {
try { try {
@ -507,8 +510,11 @@ import java.util.concurrent.ExecutionException;
connectionResult.availableSessionCommands, connectionResult.availableSessionCommands,
connectionResult.availablePlayerCommands); connectionResult.availablePlayerCommands);
sequencedFutureManager = sequencedFutureManager =
checkStateNotNull( connectedControllersManager.getSequencedFutureManager(controllerInfo);
connectedControllersManager.getSequencedFutureManager(controllerInfo)); if (sequencedFutureManager == null) {
Log.w(TAG, "Ignoring connection request from unknown controller info");
return;
}
// If connection is accepted, notify the current state to the controller. // If connection is accepted, notify the current state to the controller.
// It's needed because we cannot call synchronous calls between // It's needed because we cannot call synchronous calls between
// session/controller. // session/controller.
@ -596,8 +602,7 @@ import java.util.concurrent.ExecutionException;
public void connect( public void connect(
@Nullable IMediaController caller, @Nullable IMediaController caller,
int sequenceNumber, int sequenceNumber,
@Nullable Bundle connectionRequestBundle) @Nullable Bundle connectionRequestBundle) {
throws RuntimeException {
if (caller == null || connectionRequestBundle == null) { if (caller == null || connectionRequestBundle == null) {
return; return;
} }
@ -633,7 +638,7 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void stop(@Nullable IMediaController caller, int sequenceNumber) throws RemoteException { public void stop(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -653,8 +658,7 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void release(@Nullable IMediaController caller, int sequenceNumber) public void release(@Nullable IMediaController caller, int sequenceNumber) {
throws RemoteException {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -700,7 +704,7 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void play(@Nullable IMediaController caller, int sequenceNumber) throws RuntimeException { public void play(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -727,7 +731,7 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void pause(@Nullable IMediaController caller, int sequenceNumber) throws RuntimeException { public void pause(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -744,8 +748,7 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void prepare(@Nullable IMediaController caller, int sequenceNumber) public void prepare(@Nullable IMediaController caller, int sequenceNumber) {
throws RuntimeException {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -754,7 +757,7 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void seekToDefaultPosition(IMediaController caller, int sequenceNumber) { public void seekToDefaultPosition(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -767,7 +770,7 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void seekToDefaultPositionWithMediaItemIndex( public void seekToDefaultPositionWithMediaItemIndex(
IMediaController caller, int sequenceNumber, int mediaItemIndex) throws RemoteException { @Nullable IMediaController caller, int sequenceNumber, int mediaItemIndex) {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -782,8 +785,7 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void seekTo(@Nullable IMediaController caller, int sequenceNumber, long positionMs) public void seekTo(@Nullable IMediaController caller, int sequenceNumber, long positionMs) {
throws RuntimeException {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -796,8 +798,7 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void seekToWithMediaItemIndex( public void seekToWithMediaItemIndex(
IMediaController caller, int sequenceNumber, int mediaItemIndex, long positionMs) @Nullable IMediaController caller, int sequenceNumber, int mediaItemIndex, long positionMs) {
throws RemoteException {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -812,7 +813,7 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void seekBack(IMediaController caller, int sequenceNumber) { public void seekBack(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -880,9 +881,9 @@ import java.util.concurrent.ExecutionException;
public void setRatingWithMediaId( public void setRatingWithMediaId(
@Nullable IMediaController caller, @Nullable IMediaController caller,
int sequenceNumber, int sequenceNumber,
String mediaId, @Nullable String mediaId,
@Nullable Bundle ratingBundle) { @Nullable Bundle ratingBundle) {
if (caller == null || ratingBundle == null) { if (caller == null || mediaId == null || ratingBundle == null) {
return; return;
} }
if (TextUtils.isEmpty(mediaId)) { if (TextUtils.isEmpty(mediaId)) {
@ -941,11 +942,19 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void setPlaybackParameters( public void setPlaybackParameters(
@Nullable IMediaController caller, int sequenceNumber, Bundle playbackParametersBundle) { @Nullable IMediaController caller,
int sequenceNumber,
@Nullable Bundle playbackParametersBundle) {
if (caller == null || playbackParametersBundle == null) { if (caller == null || playbackParametersBundle == null) {
return; return;
} }
PlaybackParameters playbackParameters = PlaybackParameters.fromBundle(playbackParametersBundle); PlaybackParameters playbackParameters;
try {
playbackParameters = PlaybackParameters.fromBundle(playbackParametersBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for PlaybackParameters", e);
return;
}
queueSessionTaskWithPlayerCommand( queueSessionTaskWithPlayerCommand(
caller, caller,
sequenceNumber, sequenceNumber,
@ -1134,7 +1143,7 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void addMediaItem( public void addMediaItem(
@Nullable IMediaController caller, int sequenceNumber, Bundle mediaItemBundle) { @Nullable IMediaController caller, int sequenceNumber, @Nullable Bundle mediaItemBundle) {
if (caller == null || mediaItemBundle == null) { if (caller == null || mediaItemBundle == null) {
return; return;
} }
@ -1159,7 +1168,10 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void addMediaItemWithIndex( public void addMediaItemWithIndex(
@Nullable IMediaController caller, int sequenceNumber, int index, Bundle mediaItemBundle) { @Nullable IMediaController caller,
int sequenceNumber,
int index,
@Nullable Bundle mediaItemBundle) {
if (caller == null || mediaItemBundle == null) { if (caller == null || mediaItemBundle == null) {
return; return;
} }
@ -1317,7 +1329,10 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void replaceMediaItem( public void replaceMediaItem(
IMediaController caller, int sequenceNumber, int index, Bundle mediaItemBundle) { @Nullable IMediaController caller,
int sequenceNumber,
int index,
@Nullable Bundle mediaItemBundle) {
if (caller == null || mediaItemBundle == null) { if (caller == null || mediaItemBundle == null) {
return; return;
} }
@ -1351,11 +1366,11 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void replaceMediaItems( public void replaceMediaItems(
IMediaController caller, @Nullable IMediaController caller,
int sequenceNumber, int sequenceNumber,
int fromIndex, int fromIndex,
int toIndex, int toIndex,
IBinder mediaItemsRetriever) { @Nullable IBinder mediaItemsRetriever) {
if (caller == null || mediaItemsRetriever == null) { if (caller == null || mediaItemsRetriever == null) {
return; return;
} }
@ -1606,9 +1621,16 @@ import java.util.concurrent.ExecutionException;
public void setAudioAttributes( public void setAudioAttributes(
@Nullable IMediaController caller, @Nullable IMediaController caller,
int sequenceNumber, int sequenceNumber,
Bundle audioAttributes, @Nullable Bundle audioAttributes,
boolean handleAudioFocus) { boolean handleAudioFocus) {
if (caller == null) { if (caller == null || audioAttributes == null) {
return;
}
AudioAttributes attributes;
try {
attributes = AudioAttributes.fromBundle(audioAttributes);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for AudioAttributes", e);
return; return;
} }
queueSessionTaskWithPlayerCommand( queueSessionTaskWithPlayerCommand(
@ -1616,9 +1638,7 @@ import java.util.concurrent.ExecutionException;
sequenceNumber, sequenceNumber,
COMMAND_SET_AUDIO_ATTRIBUTES, COMMAND_SET_AUDIO_ATTRIBUTES,
sendSessionResultSuccess( sendSessionResultSuccess(
player -> player -> player.setAudioAttributes(attributes, handleAudioFocus)));
player.setAudioAttributes(
AudioAttributes.fromBundle(audioAttributes), handleAudioFocus)));
} }
@Override @Override
@ -1658,9 +1678,10 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void setTrackSelectionParameters( public void setTrackSelectionParameters(
@Nullable IMediaController caller, int sequenceNumber, Bundle trackSelectionParametersBundle) @Nullable IMediaController caller,
throws RemoteException { int sequenceNumber,
if (caller == null) { @Nullable Bundle trackSelectionParametersBundle) {
if (caller == null || trackSelectionParametersBundle == null) {
return; return;
} }
TrackSelectionParameters trackSelectionParameters; TrackSelectionParameters trackSelectionParameters;
@ -1689,14 +1710,18 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void getLibraryRoot( public void getLibraryRoot(
@Nullable IMediaController caller, int sequenceNumber, @Nullable Bundle libraryParamsBundle) @Nullable IMediaController caller, int sequenceNumber, @Nullable Bundle libraryParamsBundle) {
throws RuntimeException {
if (caller == null) { if (caller == null) {
return; return;
} }
@Nullable @Nullable LibraryParams libraryParams;
LibraryParams libraryParams = try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle); libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand( dispatchSessionTaskWithSessionCommand(
caller, caller,
sequenceNumber, sequenceNumber,
@ -1708,8 +1733,7 @@ import java.util.concurrent.ExecutionException;
@Override @Override
public void getItem( public void getItem(
@Nullable IMediaController caller, int sequenceNumber, @Nullable String mediaId) @Nullable IMediaController caller, int sequenceNumber, @Nullable String mediaId) {
throws RuntimeException {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -1730,11 +1754,10 @@ import java.util.concurrent.ExecutionException;
public void getChildren( public void getChildren(
@Nullable IMediaController caller, @Nullable IMediaController caller,
int sequenceNumber, int sequenceNumber,
String parentId, @Nullable String parentId,
int page, int page,
int pageSize, int pageSize,
@Nullable Bundle libraryParamsBundle) @Nullable Bundle libraryParamsBundle) {
throws RuntimeException {
if (caller == null) { if (caller == null) {
return; return;
} }
@ -1750,9 +1773,14 @@ import java.util.concurrent.ExecutionException;
Log.w(TAG, "getChildren(): Ignoring pageSize less than 1"); Log.w(TAG, "getChildren(): Ignoring pageSize less than 1");
return; return;
} }
@Nullable @Nullable LibraryParams libraryParams;
LibraryParams libraryParams = try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle); libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand( dispatchSessionTaskWithSessionCommand(
caller, caller,
sequenceNumber, sequenceNumber,
@ -1767,7 +1795,7 @@ import java.util.concurrent.ExecutionException;
public void search( public void search(
@Nullable IMediaController caller, @Nullable IMediaController caller,
int sequenceNumber, int sequenceNumber,
String query, @Nullable String query,
@Nullable Bundle libraryParamsBundle) { @Nullable Bundle libraryParamsBundle) {
if (caller == null) { if (caller == null) {
return; return;
@ -1776,9 +1804,14 @@ import java.util.concurrent.ExecutionException;
Log.w(TAG, "search(): Ignoring empty query"); Log.w(TAG, "search(): Ignoring empty query");
return; return;
} }
@Nullable @Nullable LibraryParams libraryParams;
LibraryParams libraryParams = try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle); libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand( dispatchSessionTaskWithSessionCommand(
caller, caller,
sequenceNumber, sequenceNumber,
@ -1792,7 +1825,7 @@ import java.util.concurrent.ExecutionException;
public void getSearchResult( public void getSearchResult(
@Nullable IMediaController caller, @Nullable IMediaController caller,
int sequenceNumber, int sequenceNumber,
String query, @Nullable String query,
int page, int page,
int pageSize, int pageSize,
@Nullable Bundle libraryParamsBundle) { @Nullable Bundle libraryParamsBundle) {
@ -1811,9 +1844,14 @@ import java.util.concurrent.ExecutionException;
Log.w(TAG, "getSearchResult(): Ignoring pageSize less than 1"); Log.w(TAG, "getSearchResult(): Ignoring pageSize less than 1");
return; return;
} }
@Nullable @Nullable LibraryParams libraryParams;
LibraryParams libraryParams = try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle); libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand( dispatchSessionTaskWithSessionCommand(
caller, caller,
sequenceNumber, sequenceNumber,
@ -1828,7 +1866,7 @@ import java.util.concurrent.ExecutionException;
public void subscribe( public void subscribe(
@Nullable IMediaController caller, @Nullable IMediaController caller,
int sequenceNumber, int sequenceNumber,
String parentId, @Nullable String parentId,
@Nullable Bundle libraryParamsBundle) { @Nullable Bundle libraryParamsBundle) {
if (caller == null) { if (caller == null) {
return; return;
@ -1837,9 +1875,14 @@ import java.util.concurrent.ExecutionException;
Log.w(TAG, "subscribe(): Ignoring empty parentId"); Log.w(TAG, "subscribe(): Ignoring empty parentId");
return; return;
} }
@Nullable @Nullable LibraryParams libraryParams;
LibraryParams libraryParams = try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle); libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand( dispatchSessionTaskWithSessionCommand(
caller, caller,
sequenceNumber, sequenceNumber,
@ -1850,7 +1893,8 @@ import java.util.concurrent.ExecutionException;
} }
@Override @Override
public void unsubscribe(@Nullable IMediaController caller, int sequenceNumber, String parentId) { public void unsubscribe(
@Nullable IMediaController caller, int sequenceNumber, @Nullable String parentId) {
if (caller == null) { if (caller == null) {
return; return;
} }

View File

@ -0,0 +1,143 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.session;
import static androidx.media3.test.utils.TestUtil.getThrowingBundle;
import android.content.Context;
import android.os.Bundle;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.test.utils.TestExoPlayerBuilder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link MediaControllerStub}. */
@RunWith(AndroidJUnit4.class)
public class MediaControllerStubTest {
@Test
public void invalidBinderArguments_doNotCrashController() throws Exception {
// Access controller stub directly and then send invalid arguments. None of them should crash
// the controller and this test asserts this by running through without throwing an exception.
Context context = ApplicationProvider.getApplicationContext();
ExoPlayer player = new TestExoPlayerBuilder(context).build();
MediaSession session = new MediaSession.Builder(context, player).build();
MediaController controller =
new MediaController.Builder(context, session.getToken()).buildAsync().get();
IMediaController binder = controller.getBinder();
// Call methods with non-primitive parameters set to null.
binder.onConnected(/* seq= */ 0, /* connectionResult= */ null);
binder.onSessionResult(/* seq= */ 0, /* sessionResult= */ null);
binder.onLibraryResult(/* seq= */ 0, /* libraryResult= */ null);
binder.onSetCustomLayout(/* seq= */ 0, /* commandButtonList= */ null);
binder.onCustomCommand(
/* seq= */ 0,
/* command= */ new SessionCommand(SessionCommand.COMMAND_CODE_LIBRARY_SEARCH).toBundle(),
/* args= */ null);
binder.onCustomCommand(/* seq= */ 0, /* command= */ null, /* args= */ new Bundle());
binder.onPlayerInfoChanged(
/* seq= */ 0, /* playerInfoBundle= */ null, /* isTimelineExcluded= */ false);
binder.onPlayerInfoChangedWithExclusions(
/* seq= */ 0,
/* playerInfoBundle= */ null,
/* playerInfoExclusions= */ new PlayerInfo.BundlingExclusions(
/* isTimelineExcluded= */ true, /* areCurrentTracksExcluded= */ false)
.toBundle());
binder.onPlayerInfoChangedWithExclusions(
/* seq= */ 0,
/* playerInfoBundle= */ PlayerInfo.DEFAULT.toBundle(),
/* playerInfoExclusions= */ null);
binder.onPeriodicSessionPositionInfoChanged(/* seq= */ 0, /* sessionPositionInfo= */ null);
binder.onAvailableCommandsChangedFromPlayer(/* seq= */ 0, /* commandsBundle= */ null);
binder.onAvailableCommandsChangedFromSession(
/* seq= */ 0,
/* sessionCommandsBundle= */ new SessionCommands.Builder().build().toBundle(),
/* playerCommandsBundle= */ null);
binder.onAvailableCommandsChangedFromSession(
/* seq= */ 0,
/* sessionCommandsBundle= */ null,
/* playerCommandsBundle= */ new Player.Commands.Builder().build().toBundle());
binder.onExtrasChanged(/* seq= */ 0, /* extras= */ null);
binder.onSessionActivityChanged(/* seq= */ 0, /* pendingIntent= */ null);
binder.onChildrenChanged(
/* seq= */ 0,
/* parentId= */ null,
/* itemCount= */ 1,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.onChildrenChanged(
/* seq= */ 0, /* parentId= */ "", /* itemCount= */ 1, /* libraryParams= */ null);
binder.onSearchResultChanged(
/* seq= */ 0,
/* query= */ null,
/* itemCount= */ 1,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.onSearchResultChanged(
/* seq= */ 0, /* query= */ "", /* itemCount= */ 1, /* libraryParams= */ null);
// Call methods with non-null arguments, but invalid Bundles.
binder.onConnected(/* seq= */ 0, /* connectionResult= */ getThrowingBundle());
binder.onSessionResult(/* seq= */ 0, /* sessionResult= */ getThrowingBundle());
binder.onLibraryResult(/* seq= */ 0, /* libraryResult= */ getThrowingBundle());
binder.onSetCustomLayout(
/* seq= */ 0, /* commandButtonList= */ ImmutableList.of(getThrowingBundle()));
binder.onCustomCommand(
/* seq= */ 0,
/* command= */ new SessionCommand(SessionCommand.COMMAND_CODE_LIBRARY_SEARCH).toBundle(),
/* args= */ getThrowingBundle());
binder.onCustomCommand(
/* seq= */ 0, /* command= */ getThrowingBundle(), /* args= */ new Bundle());
binder.onPlayerInfoChanged(
/* seq= */ 0, /* playerInfoBundle= */ getThrowingBundle(), /* isTimelineExcluded= */ false);
binder.onPlayerInfoChangedWithExclusions(
/* seq= */ 0,
/* playerInfoBundle= */ getThrowingBundle(),
/* playerInfoExclusions= */ new PlayerInfo.BundlingExclusions(
/* isTimelineExcluded= */ true, /* areCurrentTracksExcluded= */ false)
.toBundle());
binder.onPlayerInfoChangedWithExclusions(
/* seq= */ 0,
/* playerInfoBundle= */ PlayerInfo.DEFAULT.toBundle(),
/* playerInfoExclusions= */ getThrowingBundle());
binder.onPeriodicSessionPositionInfoChanged(
/* seq= */ 0, /* sessionPositionInfo= */ getThrowingBundle());
binder.onAvailableCommandsChangedFromPlayer(
/* seq= */ 0, /* commandsBundle= */ getThrowingBundle());
binder.onAvailableCommandsChangedFromSession(
/* seq= */ 0,
/* sessionCommandsBundle= */ new SessionCommands.Builder().build().toBundle(),
/* playerCommandsBundle= */ getThrowingBundle());
binder.onAvailableCommandsChangedFromSession(
/* seq= */ 0,
/* sessionCommandsBundle= */ getThrowingBundle(),
/* playerCommandsBundle= */ new Player.Commands.Builder().build().toBundle());
binder.onExtrasChanged(/* seq= */ 0, /* extras= */ getThrowingBundle());
binder.onChildrenChanged(
/* seq= */ 0,
/* parentId= */ "",
/* itemCount= */ 1,
/* libraryParams= */ getThrowingBundle());
binder.onSearchResultChanged(
/* seq= */ 0,
/* query= */ "",
/* itemCount= */ 1,
/* libraryParams= */ getThrowingBundle());
}
}

View File

@ -0,0 +1,436 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.session;
import static androidx.media3.test.utils.TestUtil.getThrowingBundle;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.BundleListRetriever;
import androidx.media3.common.HeartRating;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.test.utils.TestExoPlayerBuilder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link MediaSessionStub}. */
@RunWith(AndroidJUnit4.class)
public class MediaSessionStubTest {
@Test
public void invalidBinderArguments_doNotCrashSession() throws Exception {
// Access session stub directly and then send invalid arguments. None of them should crash the
// session app and this test asserts this by running through without throwing an exception.
Context context = ApplicationProvider.getApplicationContext();
ExoPlayer player = new TestExoPlayerBuilder(context).build();
MediaSession session = new MediaSession.Builder(context, player).setId("invalidArgs").build();
IMediaSession binder = (IMediaSession) session.getToken().getBinder();
// Call methods with caller==null and valid additional parameters.
binder.setVolume(/* caller= */ null, /* seq= */ 0, /* volume= */ 0);
binder.setDeviceVolume(/* caller= */ null, /* seq= */ 0, /* volume= */ 0);
binder.setDeviceVolumeWithFlags(
/* caller= */ null, /* seq= */ 0, /* volume= */ 0, /* flags= */ 0);
binder.increaseDeviceVolume(/* caller= */ null, /* seq= */ 0);
binder.increaseDeviceVolumeWithFlags(/* caller= */ null, /* seq= */ 0, /* flags= */ 0);
binder.decreaseDeviceVolume(/* caller= */ null, /* seq= */ 0);
binder.decreaseDeviceVolumeWithFlags(/* caller= */ null, /* seq= */ 0, /* flags= */ 0);
binder.setDeviceMuted(/* caller= */ null, /* seq= */ 0, /* muted= */ false);
binder.setDeviceMutedWithFlags(
/* caller= */ null, /* seq= */ 0, /* muted= */ false, /* flags= */ 0);
binder.setAudioAttributes(
/* caller= */ null,
/* seq= */ 0,
/* audioAttributes= */ AudioAttributes.DEFAULT.toBundle(),
/* handleAudioFocus= */ false);
binder.setMediaItem(
/* caller= */ null,
/* seq= */ 0,
/* mediaItemBundle= */ new MediaItem.Builder().build().toBundle());
binder.setMediaItemWithStartPosition(
/* caller= */ null,
/* seq= */ 0,
/* mediaItemBundle= */ new MediaItem.Builder().build().toBundle(),
/* startPositionMs= */ 0);
binder.setMediaItemWithResetPosition(
/* caller= */ null,
/* seq= */ 0,
/* mediaItemBundle= */ new MediaItem.Builder().build().toBundle(),
/* resetPosition= */ false);
binder.setMediaItems(
/* caller= */ null,
/* seq= */ 0,
/* mediaItems= */ new BundleListRetriever(
ImmutableList.of(new MediaItem.Builder().build().toBundle())));
binder.setMediaItemsWithResetPosition(
/* caller= */ null,
/* seq= */ 0,
/* mediaItems= */ new BundleListRetriever(
ImmutableList.of(new MediaItem.Builder().build().toBundle())),
/* resetPosition= */ false);
binder.setMediaItemsWithStartIndex(
/* caller= */ null,
/* seq= */ 0,
/* mediaItems= */ new BundleListRetriever(
ImmutableList.of(new MediaItem.Builder().build().toBundle())),
/* startIndex= */ 0,
/* startPositionMs= */ 0);
binder.setPlayWhenReady(/* caller= */ null, /* seq= */ 0, /* playWhenReady= */ false);
binder.onControllerResult(
/* caller= */ null,
/* seq= */ 0,
/* controllerResult= */ new SessionResult(SessionResult.RESULT_SUCCESS).toBundle());
binder.connect(
/* caller= */ null,
/* seq= */ 0,
/* connectionRequest= */ new ConnectionRequest(
"pkg", /* pid= */ 0, /* connectionHints= */ new Bundle())
.toBundle());
binder.onCustomCommand(
/* caller= */ null,
/* seq= */ 0,
/* sessionCommand= */ new SessionCommand(SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM)
.toBundle(),
/* args= */ new Bundle());
binder.setRepeatMode(
/* caller= */ null, /* seq= */ 0, /* repeatMode= */ Player.REPEAT_MODE_OFF);
binder.setShuffleModeEnabled(/* caller= */ null, /* seq= */ 0, /* shuffleModeEnabled= */ false);
binder.removeMediaItem(/* caller= */ null, /* seq= */ 0, /* index= */ 0);
binder.removeMediaItems(/* caller= */ null, /* seq= */ 0, /* fromIndex= */ 0, /* toIndex= */ 0);
binder.clearMediaItems(/* caller= */ null, /* seq= */ 0);
binder.moveMediaItem(
/* caller= */ null, /* seq= */ 0, /* currentIndex= */ 0, /* newIndex= */ 0);
binder.moveMediaItems(
/* caller= */ null, /* seq= */ 0, /* fromIndex= */ 0, /* toIndex= */ 0, /* newIndex= */ 0);
binder.replaceMediaItem(
/* caller= */ null,
/* seq= */ 0,
/* index= */ 0,
/* mediaItemBundle= */ new MediaItem.Builder().build().toBundle());
binder.replaceMediaItems(
/* caller= */ null,
/* seq= */ 0,
/* fromIndex= */ 0,
/* toIndex= */ 0,
/* mediaItems= */ new BundleListRetriever(
ImmutableList.of(new MediaItem.Builder().build().toBundle())));
binder.play(/* caller= */ null, /* seq= */ 0);
binder.pause(/* caller= */ null, /* seq= */ 0);
binder.prepare(/* caller= */ null, /* seq= */ 0);
binder.setPlaybackParameters(
/* caller= */ null,
/* seq= */ 0,
/* playbackParametersBundle= */ new PlaybackParameters(/* speed= */ 1f).toBundle());
binder.setPlaybackSpeed(/* caller= */ null, /* seq= */ 0, /* speed= */ 0);
binder.addMediaItem(
/* caller= */ null,
/* seq= */ 0,
/* mediaItemBundle= */ new MediaItem.Builder().build().toBundle());
binder.addMediaItemWithIndex(
/* caller= */ null,
/* seq= */ 0,
/* index= */ 0,
/* mediaItemBundle= */ new MediaItem.Builder().build().toBundle());
binder.addMediaItems(
/* caller= */ null,
/* seq= */ 0,
/* mediaItems= */ new BundleListRetriever(
ImmutableList.of(new MediaItem.Builder().build().toBundle())));
binder.addMediaItemsWithIndex(
/* caller= */ null,
/* seq= */ 0,
/* index= */ 0,
/* mediaItems= */ new BundleListRetriever(
ImmutableList.of(new MediaItem.Builder().build().toBundle())));
binder.setPlaylistMetadata(
/* caller= */ null,
/* seq= */ 0,
/* playlistMetadata= */ new MediaMetadata.Builder().build().toBundle());
binder.stop(/* caller= */ null, /* seq= */ 0);
binder.release(/* caller= */ null, /* seq= */ 0);
binder.seekToDefaultPosition(/* caller= */ null, /* seq= */ 0);
binder.seekToDefaultPositionWithMediaItemIndex(
/* caller= */ null, /* seq= */ 0, /* mediaItemIndex= */ 0);
binder.seekTo(/* caller= */ null, /* seq= */ 0, /* positionMs= */ 0);
binder.seekToWithMediaItemIndex(
/* caller= */ null, /* seq= */ 0, /* mediaItemIndex= */ 0, /* positionMs= */ 0);
binder.seekBack(/* caller= */ null, /* seq= */ 0);
binder.seekForward(/* caller= */ null, /* seq= */ 0);
binder.seekToPreviousMediaItem(/* caller= */ null, /* seq= */ 0);
binder.seekToNextMediaItem(/* caller= */ null, /* seq= */ 0);
binder.setVideoSurface(/* caller= */ null, /* seq= */ 0, /* surface= */ null);
binder.flushCommandQueue(/* caller= */ null);
binder.seekToPrevious(/* caller= */ null, /* seq= */ 0);
binder.seekToNext(/* caller= */ null, /* seq= */ 0);
binder.setTrackSelectionParameters(
/* caller= */ null,
/* seq= */ 0,
/* trackSelectionParametersBundle= */ new TrackSelectionParameters.Builder(context)
.build()
.toBundle());
binder.setRatingWithMediaId(
/* caller= */ null,
/* seq= */ 0,
/* mediaId= */ "",
/* rating= */ new HeartRating().toBundle());
binder.setRating(/* caller= */ null, /* seq= */ 0, /* rating= */ new HeartRating().toBundle());
binder.getLibraryRoot(
/* caller= */ null,
/* seq= */ 0,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.getItem(/* caller= */ null, /* seq= */ 0, /* mediaId= */ "");
binder.getChildren(
/* caller= */ null,
/* seq= */ 0,
/* parentId= */ "",
/* page= */ 0,
/* pageSize= */ 0,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.search(
/* caller= */ null,
/* seq= */ 0,
/* query= */ "",
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.getSearchResult(
/* caller= */ null,
/* seq= */ 0,
/* query= */ "",
/* page= */ 0,
/* pageSize= */ 0,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.subscribe(
/* caller= */ null,
/* seq= */ 0,
/* parentId= */ "",
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.unsubscribe(/* caller= */ null, /* seq= */ 0, /* parentId= */ "");
// Call methods with non-null caller, but other non-primitive parameters set to null.
MediaController controller =
new MediaController.Builder(context, session.getToken()).buildAsync().get();
IMediaController caller = controller.getBinder();
binder.setAudioAttributes(
caller, /* seq= */ 0, /* audioAttributes= */ null, /* handleAudioFocus= */ false);
binder.setMediaItem(caller, /* seq= */ 0, /* mediaItemBundle= */ null);
binder.setMediaItemWithStartPosition(
caller, /* seq= */ 0, /* mediaItemBundle= */ null, /* startPositionMs= */ 0);
binder.setMediaItemWithResetPosition(
caller, /* seq= */ 0, /* mediaItemBundle= */ null, /* resetPosition= */ false);
binder.setMediaItems(caller, /* seq= */ 0, /* mediaItems= */ null);
binder.setMediaItemsWithResetPosition(
caller, /* seq= */ 0, /* mediaItems= */ null, /* resetPosition= */ false);
binder.setMediaItemsWithStartIndex(
caller,
/* seq= */ 0,
/* mediaItems= */ null,
/* startIndex= */ 0,
/* startPositionMs= */ 0);
binder.onControllerResult(caller, /* seq= */ 0, /* controllerResult= */ null);
binder.connect(caller, /* seq= */ 0, /* connectionRequest= */ null);
binder.onCustomCommand(
caller, /* seq= */ 0, /* sessionCommand= */ null, /* args= */ new Bundle());
binder.onCustomCommand(
caller,
/* seq= */ 0,
/* sessionCommand= */ new SessionCommand(SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM)
.toBundle(),
/* args= */ null);
binder.replaceMediaItem(caller, /* seq= */ 0, /* index= */ 0, /* mediaItemBundle= */ null);
binder.replaceMediaItems(
caller, /* seq= */ 0, /* fromIndex= */ 0, /* toIndex= */ 0, /* mediaItems= */ null);
binder.setPlaybackParameters(caller, /* seq= */ 0, /* playbackParametersBundle= */ null);
binder.addMediaItem(caller, /* seq= */ 0, /* mediaItemBundle= */ null);
binder.addMediaItemWithIndex(caller, /* seq= */ 0, /* index= */ 0, /* mediaItemBundle= */ null);
binder.addMediaItems(caller, /* seq= */ 0, /* mediaItems= */ null);
binder.addMediaItemsWithIndex(caller, /* seq= */ 0, /* index= */ 0, /* mediaItems= */ null);
binder.setPlaylistMetadata(caller, /* seq= */ 0, /* playlistMetadata= */ null);
binder.setTrackSelectionParameters(
caller, /* seq= */ 0, /* trackSelectionParametersBundle= */ null);
binder.setRatingWithMediaId(
caller, /* seq= */ 0, /* mediaId= */ null, /* rating= */ new HeartRating().toBundle());
binder.setRatingWithMediaId(caller, /* seq= */ 0, /* mediaId= */ "", /* rating= */ null);
binder.setRating(caller, /* seq= */ 0, /* rating= */ null);
binder.getLibraryRoot(caller, /* seq= */ 0, /* libraryParams= */ null);
binder.getItem(caller, /* seq= */ 0, /* mediaId= */ null);
binder.getChildren(
caller,
/* seq= */ 0,
/* parentId= */ null,
/* page= */ 0,
/* pageSize= */ 0,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.getChildren(
caller,
/* seq= */ 0,
/* parentId= */ "",
/* page= */ 0,
/* pageSize= */ 0,
/* libraryParams= */ null);
binder.search(
caller,
/* seq= */ 0,
/* query= */ null,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.search(caller, /* seq= */ 0, /* query= */ "", /* libraryParams= */ null);
binder.getSearchResult(
caller,
/* seq= */ 0,
/* query= */ null,
/* page= */ 0,
/* pageSize= */ 0,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.getSearchResult(
caller,
/* seq= */ 0,
/* query= */ "",
/* page= */ 0,
/* pageSize= */ 0,
/* libraryParams= */ null);
binder.subscribe(
caller,
/* seq= */ 0,
/* parentId= */ null,
/* libraryParams= */ new MediaLibraryService.LibraryParams.Builder().build().toBundle());
binder.subscribe(caller, /* seq= */ 0, /* parentId= */ "", /* libraryParams= */ null);
binder.unsubscribe(caller, /* seq= */ 0, /* parentId= */ null);
// Call methods with non-null arguments, but invalid Bundles.
IBinder noopBinder = new Binder() {};
binder.setAudioAttributes(
caller,
/* seq= */ 0,
/* audioAttributes= */ getThrowingBundle(),
/* handleAudioFocus= */ false);
binder.setMediaItem(caller, /* seq= */ 0, /* mediaItemBundle= */ getThrowingBundle());
binder.setMediaItemWithStartPosition(
caller, /* seq= */ 0, /* mediaItemBundle= */ getThrowingBundle(), /* startPositionMs= */ 0);
binder.setMediaItemWithResetPosition(
caller,
/* seq= */ 0,
/* mediaItemBundle= */ getThrowingBundle(),
/* resetPosition= */ false);
binder.setMediaItems(caller, /* seq= */ 0, /* mediaItems= */ noopBinder);
binder.setMediaItems(
caller,
/* seq= */ 0,
/* mediaItems= */ new BundleListRetriever(ImmutableList.of(getThrowingBundle())));
binder.setMediaItemsWithResetPosition(
caller, /* seq= */ 0, /* mediaItems= */ noopBinder, /* resetPosition= */ false);
binder.setMediaItemsWithResetPosition(
caller,
/* seq= */ 0,
/* mediaItems= */ new BundleListRetriever(ImmutableList.of(getThrowingBundle())),
/* resetPosition= */ false);
binder.setMediaItemsWithStartIndex(
caller,
/* seq= */ 0,
/* mediaItems= */ noopBinder,
/* startIndex= */ 0,
/* startPositionMs= */ 0);
binder.setMediaItemsWithStartIndex(
caller,
/* seq= */ 0,
/* mediaItems= */ new BundleListRetriever(ImmutableList.of(getThrowingBundle())),
/* startIndex= */ 0,
/* startPositionMs= */ 0);
binder.onControllerResult(caller, /* seq= */ 0, /* controllerResult= */ getThrowingBundle());
binder.onCustomCommand(
caller, /* seq= */ 0, /* sessionCommand= */ getThrowingBundle(), /* args= */ new Bundle());
binder.replaceMediaItem(
caller, /* seq= */ 0, /* index= */ 0, /* mediaItemBundle= */ getThrowingBundle());
binder.replaceMediaItems(
caller, /* seq= */ 0, /* fromIndex= */ 0, /* toIndex= */ 0, /* mediaItems= */ noopBinder);
binder.replaceMediaItems(
caller,
/* seq= */ 0,
/* fromIndex= */ 0,
/* toIndex= */ 0,
/* mediaItems= */ new BundleListRetriever(ImmutableList.of(getThrowingBundle())));
binder.setPlaybackParameters(
caller, /* seq= */ 0, /* playbackParametersBundle= */ getThrowingBundle());
binder.addMediaItem(caller, /* seq= */ 0, /* mediaItemBundle= */ getThrowingBundle());
binder.addMediaItemWithIndex(
caller, /* seq= */ 0, /* index= */ 0, /* mediaItemBundle= */ getThrowingBundle());
binder.addMediaItems(caller, /* seq= */ 0, /* mediaItems= */ noopBinder);
binder.addMediaItems(
caller,
/* seq= */ 0,
/* mediaItems= */ new BundleListRetriever(ImmutableList.of(getThrowingBundle())));
binder.addMediaItemsWithIndex(
caller, /* seq= */ 0, /* index= */ 0, /* mediaItems= */ noopBinder);
binder.addMediaItemsWithIndex(
caller,
/* seq= */ 0,
/* index= */ 0,
/* mediaItems= */ new BundleListRetriever(ImmutableList.of(getThrowingBundle())));
binder.setPlaylistMetadata(caller, /* seq= */ 0, /* playlistMetadata= */ getThrowingBundle());
binder.setTrackSelectionParameters(
caller, /* seq= */ 0, /* trackSelectionParametersBundle= */ getThrowingBundle());
binder.setRatingWithMediaId(
caller, /* seq= */ 0, /* mediaId= */ "", /* rating= */ getThrowingBundle());
binder.setRating(caller, /* seq= */ 0, /* rating= */ getThrowingBundle());
binder.getLibraryRoot(caller, /* seq= */ 0, /* libraryParams= */ getThrowingBundle());
binder.getChildren(
caller,
/* seq= */ 0,
/* parentId= */ "",
/* page= */ 0,
/* pageSize= */ 0,
/* libraryParams= */ getThrowingBundle());
binder.search(caller, /* seq= */ 0, /* query= */ "", /* libraryParams= */ getThrowingBundle());
binder.getSearchResult(
caller,
/* seq= */ 0,
/* query= */ "",
/* page= */ 0,
/* pageSize= */ 0,
/* libraryParams= */ getThrowingBundle());
binder.subscribe(
caller, /* seq= */ 0, /* parentId= */ "", /* libraryParams= */ getThrowingBundle());
}
@Test
public void binderConnectRequest_withInvalidController_doesNotCrashSession() throws Exception {
// Obtain a direct reference to binder and attempt to connect with an invalid controller. This
// should not crash the session app and this test asserts this by running through without
// throwing an exception.
Context context = ApplicationProvider.getApplicationContext();
ExoPlayer player = new TestExoPlayerBuilder(context).build();
MediaSession session = new MediaSession.Builder(context, player).setId("connect").build();
IMediaSession binder = (IMediaSession) session.getToken().getBinder();
MediaController controller =
new MediaController.Builder(context, session.getToken()).buildAsync().get();
IMediaController caller = controller.getBinder();
binder.connect(
caller,
/* seq= */ 0,
/* connectionRequest= */ new ConnectionRequest(
/* packageName= */ "invalud", /* pid= */ 9999, /* connectionHints= */ new Bundle())
.toBundle());
}
}

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.test.utils; package androidx.media3.test.utils;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.content.Context; import android.content.Context;
@ -25,6 +26,8 @@ import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
@ -593,6 +596,26 @@ public class TestUtil {
.build(); .build();
} }
/** Returns a {@link Bundle} that will throw an exception at the first attempt to read a value. */
public static Bundle getThrowingBundle() {
// Create Bundle containing a Parcelable class that will require a ClassLoader.
Bundle bundle = new Bundle();
bundle.putParcelable("0", new StreamKey(0, 0));
// Serialize this Bundle to a Parcel to remove the direct object reference.
Parcel parcel = Parcel.obtain();
parcel.writeBundle(bundle);
// Read the same Bundle from the Parcel again, but with a ClassLoader that can't load the class.
parcel.setDataPosition(0);
ClassLoader throwingClassLoader =
new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException();
}
};
return checkNotNull(parcel.readBundle(throwingClassLoader));
}
private static final class NoUidOrShufflingTimeline extends Timeline { private static final class NoUidOrShufflingTimeline extends Timeline {
private final Timeline delegate; private final Timeline delegate;

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.test.utils;
import static org.junit.Assert.assertThrows;
import android.os.Bundle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link TestUtil}. */
@RunWith(AndroidJUnit4.class)
public class TestUtilTest {
@Test
public void getThrowingBundle_throwsWhenUsed() {
Bundle bundle = TestUtil.getThrowingBundle();
assertThrows(RuntimeException.class, () -> bundle.getInt("0"));
}
}