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.
*
* <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
* which to begin playback, in microseconds.

View File

@ -1970,6 +1970,13 @@ public class MediaController implements Player {
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() {
checkState(Looper.myLooper() == getApplicationLooper(), WRONG_THREAD_ERROR_MESSAGE);
}
@ -2215,5 +2222,8 @@ public class MediaController implements Player {
@Nullable
MediaBrowserCompat getBrowserCompat();
@Nullable
IMediaController getBinder();
}
}

View File

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

View File

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

View File

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

View File

@ -454,7 +454,10 @@ import java.util.concurrent.ExecutionException;
}
@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();
if (sessionImpl == null || sessionImpl.isReleased()) {
try {
@ -507,8 +510,11 @@ import java.util.concurrent.ExecutionException;
connectionResult.availableSessionCommands,
connectionResult.availablePlayerCommands);
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.
// It's needed because we cannot call synchronous calls between
// session/controller.
@ -596,8 +602,7 @@ import java.util.concurrent.ExecutionException;
public void connect(
@Nullable IMediaController caller,
int sequenceNumber,
@Nullable Bundle connectionRequestBundle)
throws RuntimeException {
@Nullable Bundle connectionRequestBundle) {
if (caller == null || connectionRequestBundle == null) {
return;
}
@ -633,7 +638,7 @@ import java.util.concurrent.ExecutionException;
}
@Override
public void stop(@Nullable IMediaController caller, int sequenceNumber) throws RemoteException {
public void stop(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) {
return;
}
@ -653,8 +658,7 @@ import java.util.concurrent.ExecutionException;
}
@Override
public void release(@Nullable IMediaController caller, int sequenceNumber)
throws RemoteException {
public void release(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) {
return;
}
@ -700,7 +704,7 @@ import java.util.concurrent.ExecutionException;
}
@Override
public void play(@Nullable IMediaController caller, int sequenceNumber) throws RuntimeException {
public void play(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) {
return;
}
@ -727,7 +731,7 @@ import java.util.concurrent.ExecutionException;
}
@Override
public void pause(@Nullable IMediaController caller, int sequenceNumber) throws RuntimeException {
public void pause(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) {
return;
}
@ -744,8 +748,7 @@ import java.util.concurrent.ExecutionException;
}
@Override
public void prepare(@Nullable IMediaController caller, int sequenceNumber)
throws RuntimeException {
public void prepare(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) {
return;
}
@ -754,7 +757,7 @@ import java.util.concurrent.ExecutionException;
}
@Override
public void seekToDefaultPosition(IMediaController caller, int sequenceNumber) {
public void seekToDefaultPosition(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) {
return;
}
@ -767,7 +770,7 @@ import java.util.concurrent.ExecutionException;
@Override
public void seekToDefaultPositionWithMediaItemIndex(
IMediaController caller, int sequenceNumber, int mediaItemIndex) throws RemoteException {
@Nullable IMediaController caller, int sequenceNumber, int mediaItemIndex) {
if (caller == null) {
return;
}
@ -782,8 +785,7 @@ import java.util.concurrent.ExecutionException;
}
@Override
public void seekTo(@Nullable IMediaController caller, int sequenceNumber, long positionMs)
throws RuntimeException {
public void seekTo(@Nullable IMediaController caller, int sequenceNumber, long positionMs) {
if (caller == null) {
return;
}
@ -796,8 +798,7 @@ import java.util.concurrent.ExecutionException;
@Override
public void seekToWithMediaItemIndex(
IMediaController caller, int sequenceNumber, int mediaItemIndex, long positionMs)
throws RemoteException {
@Nullable IMediaController caller, int sequenceNumber, int mediaItemIndex, long positionMs) {
if (caller == null) {
return;
}
@ -812,7 +813,7 @@ import java.util.concurrent.ExecutionException;
}
@Override
public void seekBack(IMediaController caller, int sequenceNumber) {
public void seekBack(@Nullable IMediaController caller, int sequenceNumber) {
if (caller == null) {
return;
}
@ -880,9 +881,9 @@ import java.util.concurrent.ExecutionException;
public void setRatingWithMediaId(
@Nullable IMediaController caller,
int sequenceNumber,
String mediaId,
@Nullable String mediaId,
@Nullable Bundle ratingBundle) {
if (caller == null || ratingBundle == null) {
if (caller == null || mediaId == null || ratingBundle == null) {
return;
}
if (TextUtils.isEmpty(mediaId)) {
@ -941,11 +942,19 @@ import java.util.concurrent.ExecutionException;
@Override
public void setPlaybackParameters(
@Nullable IMediaController caller, int sequenceNumber, Bundle playbackParametersBundle) {
@Nullable IMediaController caller,
int sequenceNumber,
@Nullable Bundle playbackParametersBundle) {
if (caller == null || playbackParametersBundle == null) {
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(
caller,
sequenceNumber,
@ -1134,7 +1143,7 @@ import java.util.concurrent.ExecutionException;
@Override
public void addMediaItem(
@Nullable IMediaController caller, int sequenceNumber, Bundle mediaItemBundle) {
@Nullable IMediaController caller, int sequenceNumber, @Nullable Bundle mediaItemBundle) {
if (caller == null || mediaItemBundle == null) {
return;
}
@ -1159,7 +1168,10 @@ import java.util.concurrent.ExecutionException;
@Override
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) {
return;
}
@ -1317,7 +1329,10 @@ import java.util.concurrent.ExecutionException;
@Override
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) {
return;
}
@ -1351,11 +1366,11 @@ import java.util.concurrent.ExecutionException;
@Override
public void replaceMediaItems(
IMediaController caller,
@Nullable IMediaController caller,
int sequenceNumber,
int fromIndex,
int toIndex,
IBinder mediaItemsRetriever) {
@Nullable IBinder mediaItemsRetriever) {
if (caller == null || mediaItemsRetriever == null) {
return;
}
@ -1606,9 +1621,16 @@ import java.util.concurrent.ExecutionException;
public void setAudioAttributes(
@Nullable IMediaController caller,
int sequenceNumber,
Bundle audioAttributes,
@Nullable Bundle audioAttributes,
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;
}
queueSessionTaskWithPlayerCommand(
@ -1616,9 +1638,7 @@ import java.util.concurrent.ExecutionException;
sequenceNumber,
COMMAND_SET_AUDIO_ATTRIBUTES,
sendSessionResultSuccess(
player ->
player.setAudioAttributes(
AudioAttributes.fromBundle(audioAttributes), handleAudioFocus)));
player -> player.setAudioAttributes(attributes, handleAudioFocus)));
}
@Override
@ -1658,9 +1678,10 @@ import java.util.concurrent.ExecutionException;
@Override
public void setTrackSelectionParameters(
@Nullable IMediaController caller, int sequenceNumber, Bundle trackSelectionParametersBundle)
throws RemoteException {
if (caller == null) {
@Nullable IMediaController caller,
int sequenceNumber,
@Nullable Bundle trackSelectionParametersBundle) {
if (caller == null || trackSelectionParametersBundle == null) {
return;
}
TrackSelectionParameters trackSelectionParameters;
@ -1689,14 +1710,18 @@ import java.util.concurrent.ExecutionException;
@Override
public void getLibraryRoot(
@Nullable IMediaController caller, int sequenceNumber, @Nullable Bundle libraryParamsBundle)
throws RuntimeException {
@Nullable IMediaController caller, int sequenceNumber, @Nullable Bundle libraryParamsBundle) {
if (caller == null) {
return;
}
@Nullable
LibraryParams libraryParams =
@Nullable LibraryParams libraryParams;
try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand(
caller,
sequenceNumber,
@ -1708,8 +1733,7 @@ import java.util.concurrent.ExecutionException;
@Override
public void getItem(
@Nullable IMediaController caller, int sequenceNumber, @Nullable String mediaId)
throws RuntimeException {
@Nullable IMediaController caller, int sequenceNumber, @Nullable String mediaId) {
if (caller == null) {
return;
}
@ -1730,11 +1754,10 @@ import java.util.concurrent.ExecutionException;
public void getChildren(
@Nullable IMediaController caller,
int sequenceNumber,
String parentId,
@Nullable String parentId,
int page,
int pageSize,
@Nullable Bundle libraryParamsBundle)
throws RuntimeException {
@Nullable Bundle libraryParamsBundle) {
if (caller == null) {
return;
}
@ -1750,9 +1773,14 @@ import java.util.concurrent.ExecutionException;
Log.w(TAG, "getChildren(): Ignoring pageSize less than 1");
return;
}
@Nullable
LibraryParams libraryParams =
@Nullable LibraryParams libraryParams;
try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand(
caller,
sequenceNumber,
@ -1767,7 +1795,7 @@ import java.util.concurrent.ExecutionException;
public void search(
@Nullable IMediaController caller,
int sequenceNumber,
String query,
@Nullable String query,
@Nullable Bundle libraryParamsBundle) {
if (caller == null) {
return;
@ -1776,9 +1804,14 @@ import java.util.concurrent.ExecutionException;
Log.w(TAG, "search(): Ignoring empty query");
return;
}
@Nullable
LibraryParams libraryParams =
@Nullable LibraryParams libraryParams;
try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand(
caller,
sequenceNumber,
@ -1792,7 +1825,7 @@ import java.util.concurrent.ExecutionException;
public void getSearchResult(
@Nullable IMediaController caller,
int sequenceNumber,
String query,
@Nullable String query,
int page,
int pageSize,
@Nullable Bundle libraryParamsBundle) {
@ -1811,9 +1844,14 @@ import java.util.concurrent.ExecutionException;
Log.w(TAG, "getSearchResult(): Ignoring pageSize less than 1");
return;
}
@Nullable
LibraryParams libraryParams =
@Nullable LibraryParams libraryParams;
try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand(
caller,
sequenceNumber,
@ -1828,7 +1866,7 @@ import java.util.concurrent.ExecutionException;
public void subscribe(
@Nullable IMediaController caller,
int sequenceNumber,
String parentId,
@Nullable String parentId,
@Nullable Bundle libraryParamsBundle) {
if (caller == null) {
return;
@ -1837,9 +1875,14 @@ import java.util.concurrent.ExecutionException;
Log.w(TAG, "subscribe(): Ignoring empty parentId");
return;
}
@Nullable
LibraryParams libraryParams =
@Nullable LibraryParams libraryParams;
try {
libraryParams =
libraryParamsBundle == null ? null : LibraryParams.fromBundle(libraryParamsBundle);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for LibraryParams", e);
return;
}
dispatchSessionTaskWithSessionCommand(
caller,
sequenceNumber,
@ -1850,7 +1893,8 @@ import java.util.concurrent.ExecutionException;
}
@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) {
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;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@ -25,6 +26,8 @@ import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.MediaCodec;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
@ -593,6 +596,26 @@ public class TestUtil {
.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 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"));
}
}