Add SessionError and use it in service results

This change adds `SessionError` and uses it in `SessionResult`
and `LibraryResult` to report errors to callers.

Constructors and factory method that used a simple `errorCode` to
construct error variants of `SessionResult` and `LibraryResult`
have been overloaded with a variant that uses a `SessionError`
instead. While these methods and constructors are supposed to be
deprecated, they aren't yet deprecated until the newly added
alternative is stabilized.

PiperOrigin-RevId: 642254336
This commit is contained in:
bachinger 2024-06-11 06:51:13 -07:00 committed by Copybara-Service
parent 06e95ad2fb
commit efff1ee2f1
32 changed files with 847 additions and 231 deletions

View File

@ -21,6 +21,9 @@
* Add `MediaSession.Callback.onPlayerInteractionFinished` to inform * Add `MediaSession.Callback.onPlayerInteractionFinished` to inform
sessions when a series of player interactions from a specific controller sessions when a series of player interactions from a specific controller
finished. finished.
* Add `SessionError` and use it in `SessionResult` and `LibraryResult`
instead of the error code to provide more information about the error
and how to resolve the error if possible.
* UI: * UI:
* Add customisation of various icons in `PlayerControlView` through xml * Add customisation of various icons in `PlayerControlView` through xml
attributes to allow different drawables per `PlayerView` instance, attributes to allow different drawables per `PlayerView` instance,

View File

@ -1511,7 +1511,7 @@ package androidx.media3.session {
field @Nullable public final V value; field @Nullable public final V value;
} }
@IntDef({androidx.media3.session.LibraryResult.RESULT_SUCCESS, androidx.media3.session.LibraryResult.RESULT_ERROR_UNKNOWN, androidx.media3.session.LibraryResult.RESULT_ERROR_INVALID_STATE, androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE, androidx.media3.session.LibraryResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media3.session.LibraryResult.RESULT_ERROR_IO, androidx.media3.session.LibraryResult.RESULT_INFO_SKIPPED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface LibraryResult.Code { @IntDef({androidx.media3.session.LibraryResult.RESULT_SUCCESS, androidx.media3.session.SessionError.INFO_SKIPPED, androidx.media3.session.SessionError.ERROR_UNKNOWN, androidx.media3.session.SessionError.ERROR_INVALID_STATE, androidx.media3.session.SessionError.ERROR_BAD_VALUE, androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED, androidx.media3.session.SessionError.ERROR_IO, androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED, androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionError.ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface LibraryResult.Code {
} }
public final class MediaBrowser extends androidx.media3.session.MediaController { public final class MediaBrowser extends androidx.media3.session.MediaController {
@ -1866,7 +1866,7 @@ package androidx.media3.session {
field @androidx.media3.session.SessionResult.Code public final int resultCode; field @androidx.media3.session.SessionResult.Code public final int resultCode;
} }
@IntDef({androidx.media3.session.SessionResult.RESULT_SUCCESS, androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN, androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE, androidx.media3.session.SessionResult.RESULT_ERROR_BAD_VALUE, androidx.media3.session.SessionResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media3.session.SessionResult.RESULT_ERROR_IO, androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface SessionResult.Code { @IntDef({androidx.media3.session.SessionResult.RESULT_SUCCESS, androidx.media3.session.SessionError.INFO_SKIPPED, androidx.media3.session.SessionError.ERROR_UNKNOWN, androidx.media3.session.SessionError.ERROR_INVALID_STATE, androidx.media3.session.SessionError.ERROR_BAD_VALUE, androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED, androidx.media3.session.SessionError.ERROR_IO, androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED, androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionError.ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface SessionResult.Code {
} }
public final class SessionToken { public final class SessionToken {

View File

@ -27,6 +27,7 @@ import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
import androidx.media3.session.SessionCommand import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionError
import androidx.media3.session.SessionResult import androidx.media3.session.SessionResult
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
@ -132,7 +133,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
MediaItemTree.getItem(mediaId)?.let { MediaItemTree.getItem(mediaId)?.let {
return Futures.immediateFuture(LibraryResult.ofItem(it, /* params= */ null)) return Futures.immediateFuture(LibraryResult.ofItem(it, /* params= */ null))
} }
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)) return Futures.immediateFuture(LibraryResult.ofError(SessionError.ERROR_BAD_VALUE))
} }
override fun onGetChildren( override fun onGetChildren(
@ -147,7 +148,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
if (children.isNotEmpty()) { if (children.isNotEmpty()) {
return Futures.immediateFuture(LibraryResult.ofItemList(children, params)) return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
} }
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)) return Futures.immediateFuture(LibraryResult.ofError(SessionError.ERROR_BAD_VALUE))
} }
override fun onAddMediaItems( override fun onAddMediaItems(

View File

@ -54,21 +54,21 @@ public final class LibraryResult<V> implements Bundleable {
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({ @IntDef({
RESULT_SUCCESS, RESULT_SUCCESS,
RESULT_ERROR_UNKNOWN, SessionError.INFO_SKIPPED,
RESULT_ERROR_INVALID_STATE, SessionError.ERROR_UNKNOWN,
RESULT_ERROR_BAD_VALUE, SessionError.ERROR_INVALID_STATE,
RESULT_ERROR_PERMISSION_DENIED, SessionError.ERROR_BAD_VALUE,
RESULT_ERROR_IO, SessionError.ERROR_PERMISSION_DENIED,
RESULT_INFO_SKIPPED, SessionError.ERROR_IO,
RESULT_ERROR_SESSION_DISCONNECTED, SessionError.ERROR_SESSION_DISCONNECTED,
RESULT_ERROR_NOT_SUPPORTED, SessionError.ERROR_NOT_SUPPORTED,
RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED,
RESULT_ERROR_SESSION_SETUP_REQUIRED SessionError.ERROR_SESSION_SETUP_REQUIRED
}) })
public @interface Code {} public @interface Code {}
@ -82,56 +82,64 @@ public final class LibraryResult<V> implements Bundleable {
*/ */
public static final int RESULT_SUCCESS = 0; public static final int RESULT_SUCCESS = 0;
/** Result code representing that the command is skipped. */
public static final int RESULT_INFO_SKIPPED = SessionError.INFO_SKIPPED;
/** Result code representing that the command is ended with an unknown error. */ /** Result code representing that the command is ended with an unknown error. */
public static final int RESULT_ERROR_UNKNOWN = -1; public static final int RESULT_ERROR_UNKNOWN = SessionError.ERROR_UNKNOWN;
/** /**
* Result code representing that the command cannot be completed because the current state is not * Result code representing that the command cannot be completed because the current state is not
* valid for the command. * valid for the command.
*/ */
public static final int RESULT_ERROR_INVALID_STATE = -2; public static final int RESULT_ERROR_INVALID_STATE = SessionError.ERROR_INVALID_STATE;
/** Result code representing that an argument is illegal. */ /** Result code representing that an argument is illegal. */
public static final int RESULT_ERROR_BAD_VALUE = -3; public static final int RESULT_ERROR_BAD_VALUE = SessionError.ERROR_BAD_VALUE;
/** Result code representing that the command is not allowed. */ /** Result code representing that the command is not allowed. */
public static final int RESULT_ERROR_PERMISSION_DENIED = -4; public static final int RESULT_ERROR_PERMISSION_DENIED = SessionError.ERROR_PERMISSION_DENIED;
/** Result code representing that a file or network related error happened. */ /** Result code representing that a file or network related error happened. */
public static final int RESULT_ERROR_IO = -5; public static final int RESULT_ERROR_IO = SessionError.ERROR_IO;
/** Result code representing that the command is not supported. */ /** Result code representing that the command is not supported. */
public static final int RESULT_ERROR_NOT_SUPPORTED = -6; public static final int RESULT_ERROR_NOT_SUPPORTED = SessionError.ERROR_NOT_SUPPORTED;
/** Result code representing that the command is skipped. */
public static final int RESULT_INFO_SKIPPED = 1;
/** Result code representing that the session and controller were disconnected. */ /** Result code representing that the session and controller were disconnected. */
public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; public static final int RESULT_ERROR_SESSION_DISCONNECTED =
SessionError.ERROR_SESSION_DISCONNECTED;
/** Result code representing that the authentication has expired. */ /** Result code representing that the authentication has expired. */
public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED =
SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED;
/** Result code representing that a premium account is required. */ /** Result code representing that a premium account is required. */
public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED =
SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED;
/** Result code representing that too many concurrent streams are detected. */ /** Result code representing that too many concurrent streams are detected. */
public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT =
SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT;
/** Result code representing that the content is blocked due to parental controls. */ /** Result code representing that the content is blocked due to parental controls. */
public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED =
SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED;
/** Result code representing that the content is blocked due to being regionally unavailable. */ /** Result code representing that the content is blocked due to being regionally unavailable. */
public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION =
SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION;
/** /**
* Result code representing that the application cannot skip any more because the skip limit is * Result code representing that the application cannot skip any more because the skip limit is
* reached. * reached.
*/ */
public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED =
SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED;
/** Result code representing that the session needs user's manual intervention. */ /** Result code representing that the session needs user's manual intervention. */
public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED =
SessionError.ERROR_SESSION_SETUP_REQUIRED;
/** The {@link Code} of this result. */ /** The {@link Code} of this result. */
public final @Code int resultCode; public final @Code int resultCode;
@ -153,12 +161,16 @@ public final class LibraryResult<V> implements Bundleable {
/** The optional parameters. */ /** The optional parameters. */
@Nullable public final MediaLibraryService.LibraryParams params; @Nullable public final MediaLibraryService.LibraryParams params;
/** The optional session error. */
@UnstableApi @Nullable public final SessionError sessionError;
/** Creates an instance with {@link #resultCode}{@code ==}{@link #RESULT_SUCCESS}. */ /** Creates an instance with {@link #resultCode}{@code ==}{@link #RESULT_SUCCESS}. */
public static LibraryResult<Void> ofVoid() { public static LibraryResult<Void> ofVoid() {
return new LibraryResult<>( return new LibraryResult<>(
RESULT_SUCCESS, RESULT_SUCCESS,
SystemClock.elapsedRealtime(), SystemClock.elapsedRealtime(),
/* params= */ null, /* params= */ null,
/* sessionError= */ null,
/* value= */ null, /* value= */ null,
VALUE_TYPE_VOID); VALUE_TYPE_VOID);
} }
@ -169,7 +181,12 @@ public final class LibraryResult<V> implements Bundleable {
*/ */
public static LibraryResult<Void> ofVoid(@Nullable LibraryParams params) { public static LibraryResult<Void> ofVoid(@Nullable LibraryParams params) {
return new LibraryResult<>( return new LibraryResult<>(
RESULT_SUCCESS, SystemClock.elapsedRealtime(), params, /* value= */ null, VALUE_TYPE_VOID); RESULT_SUCCESS,
SystemClock.elapsedRealtime(),
params,
/* sessionError= */ null,
/* value= */ null,
VALUE_TYPE_VOID);
} }
/** /**
@ -184,7 +201,12 @@ public final class LibraryResult<V> implements Bundleable {
public static LibraryResult<MediaItem> ofItem(MediaItem item, @Nullable LibraryParams params) { public static LibraryResult<MediaItem> ofItem(MediaItem item, @Nullable LibraryParams params) {
verifyMediaItem(item); verifyMediaItem(item);
return new LibraryResult<>( return new LibraryResult<>(
RESULT_SUCCESS, SystemClock.elapsedRealtime(), params, item, VALUE_TYPE_ITEM); RESULT_SUCCESS,
SystemClock.elapsedRealtime(),
params,
/* sessionError= */ null,
item,
VALUE_TYPE_ITEM);
} }
/** /**
@ -206,6 +228,7 @@ public final class LibraryResult<V> implements Bundleable {
RESULT_SUCCESS, RESULT_SUCCESS,
SystemClock.elapsedRealtime(), SystemClock.elapsedRealtime(),
params, params,
/* sessionError= */ null,
ImmutableList.copyOf(items), ImmutableList.copyOf(items),
VALUE_TYPE_ITEM_LIST); VALUE_TYPE_ITEM_LIST);
} }
@ -215,10 +238,13 @@ public final class LibraryResult<V> implements Bundleable {
* *
* <p>{@code errorCode} must not be {@link #RESULT_SUCCESS}. * <p>{@code errorCode} must not be {@link #RESULT_SUCCESS}.
* *
* <p>Note: This method will be deprecated when {@link #ofError(SessionError)} is promoted to
* stable API status.
*
* @param errorCode The error code. * @param errorCode The error code.
*/ */
public static <V> LibraryResult<V> ofError(@Code int errorCode) { public static <V> LibraryResult<V> ofError(@Code int errorCode) {
return ofError(errorCode, /* params= */ null); return ofError(new SessionError(errorCode, SessionError.DEFAULT_ERROR_MESSAGE, Bundle.EMPTY));
} }
/** /**
@ -227,15 +253,54 @@ public final class LibraryResult<V> implements Bundleable {
* *
* <p>{@code errorCode} must not be {@link #RESULT_SUCCESS}. * <p>{@code errorCode} must not be {@link #RESULT_SUCCESS}.
* *
* <p>Note: This method will be deprecated when {@link #ofError(SessionError, LibraryParams)} is
* promoted to stable API status.
*
* @param errorCode The error code. * @param errorCode The error code.
* @param params The optional parameters to describe the error. * @param params The optional parameters to describe the error.
*/ */
public static <V> LibraryResult<V> ofError(@Code int errorCode, @Nullable LibraryParams params) { public static <V> LibraryResult<V> ofError(@Code int errorCode, @Nullable LibraryParams params) {
checkArgument(errorCode != RESULT_SUCCESS);
return new LibraryResult<>( return new LibraryResult<>(
/* resultCode= */ errorCode, /* resultCode= */ errorCode,
SystemClock.elapsedRealtime(), SystemClock.elapsedRealtime(),
/* params= */ params, /* params= */ params,
new SessionError(errorCode, SessionError.DEFAULT_ERROR_MESSAGE, Bundle.EMPTY),
/* value= */ null,
VALUE_TYPE_ERROR);
}
/**
* Creates an instance with a {@link SessionError} to describe the error. The {@link #resultCode}
* is taken from {@link SessionError#code}.
*
* @param sessionError The {@link SessionError}.
*/
@UnstableApi
public static <V> LibraryResult<V> ofError(SessionError sessionError) {
return new LibraryResult<>(
/* resultCode= */ sessionError.code,
SystemClock.elapsedRealtime(),
/* params= */ null,
sessionError,
/* value= */ null,
VALUE_TYPE_ERROR);
}
/**
* Creates an instance with a {@link SessionError} to describe the error, and the {@linkplain
* LibraryParams parameters sent by the browser}. The {@link #resultCode} is taken from {@link
* SessionError#code}.
*
* @param sessionError The {@link SessionError}.
* @param params The {@link LibraryParams} sent by the browser.
*/
@UnstableApi
public static <V> LibraryResult<V> ofError(SessionError sessionError, LibraryParams params) {
return new LibraryResult<>(
/* resultCode= */ sessionError.code,
SystemClock.elapsedRealtime(),
/* params= */ params,
sessionError,
/* value= */ null, /* value= */ null,
VALUE_TYPE_ERROR); VALUE_TYPE_ERROR);
} }
@ -244,11 +309,13 @@ public final class LibraryResult<V> implements Bundleable {
@Code int resultCode, @Code int resultCode,
long completionTimeMs, long completionTimeMs,
@Nullable LibraryParams params, @Nullable LibraryParams params,
@Nullable SessionError sessionError,
@Nullable V value, @Nullable V value,
@ValueType int valueType) { @ValueType int valueType) {
this.resultCode = resultCode; this.resultCode = resultCode;
this.completionTimeMs = completionTimeMs; this.completionTimeMs = completionTimeMs;
this.params = params; this.params = params;
this.sessionError = sessionError;
this.value = value; this.value = value;
this.valueType = valueType; this.valueType = valueType;
} }
@ -266,6 +333,7 @@ public final class LibraryResult<V> implements Bundleable {
private static final String FIELD_PARAMS = Util.intToStringMaxRadix(2); private static final String FIELD_PARAMS = Util.intToStringMaxRadix(2);
private static final String FIELD_VALUE = Util.intToStringMaxRadix(3); private static final String FIELD_VALUE = Util.intToStringMaxRadix(3);
private static final String FIELD_VALUE_TYPE = Util.intToStringMaxRadix(4); private static final String FIELD_VALUE_TYPE = Util.intToStringMaxRadix(4);
private static final String FIELD_SESSION_ERROR = Util.intToStringMaxRadix(5);
// Casting V to ImmutableList<MediaItem> is safe if valueType == VALUE_TYPE_ITEM_LIST. // Casting V to ImmutableList<MediaItem> is safe if valueType == VALUE_TYPE_ITEM_LIST.
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -278,6 +346,9 @@ public final class LibraryResult<V> implements Bundleable {
if (params != null) { if (params != null) {
bundle.putBundle(FIELD_PARAMS, params.toBundle()); bundle.putBundle(FIELD_PARAMS, params.toBundle());
} }
if (sessionError != null) {
bundle.putBundle(FIELD_SESSION_ERROR, sessionError.toBundle());
}
bundle.putInt(FIELD_VALUE_TYPE, valueType); bundle.putInt(FIELD_VALUE_TYPE, valueType);
if (value == null) { if (value == null) {
@ -391,6 +462,14 @@ public final class LibraryResult<V> implements Bundleable {
@Nullable @Nullable
MediaLibraryService.LibraryParams params = MediaLibraryService.LibraryParams params =
paramsBundle == null ? null : LibraryParams.fromBundle(paramsBundle); paramsBundle == null ? null : LibraryParams.fromBundle(paramsBundle);
@Nullable SessionError sessionError = null;
@Nullable Bundle sessionErrorBundle = bundle.getBundle(FIELD_SESSION_ERROR);
if (sessionErrorBundle != null) {
sessionError = SessionError.fromBundle(sessionErrorBundle);
} else if (resultCode != RESULT_SUCCESS) {
// Result from a session with a library version that doesn't have the SessionError.
sessionError = new SessionError(resultCode, SessionError.DEFAULT_ERROR_MESSAGE);
}
@ValueType int valueType = bundle.getInt(FIELD_VALUE_TYPE); @ValueType int valueType = bundle.getInt(FIELD_VALUE_TYPE);
@Nullable Object value; @Nullable Object value;
switch (valueType) { switch (valueType) {
@ -416,7 +495,8 @@ public final class LibraryResult<V> implements Bundleable {
throw new IllegalStateException(); throw new IllegalStateException();
} }
return new LibraryResult<>(resultCode, completionTimeMs, params, value, valueType); return new LibraryResult<>(
resultCode, completionTimeMs, params, sessionError, value, valueType);
} }
@Documented @Documented

View File

@ -20,7 +20,7 @@ import static androidx.media3.common.util.Assertions.checkNotEmpty;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.postOrRun; import static androidx.media3.common.util.Util.postOrRun;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED; import static androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@ -422,7 +422,7 @@ public final class MediaBrowser extends MediaController {
} }
private static <V> ListenableFuture<LibraryResult<V>> createDisconnectedFuture() { private static <V> ListenableFuture<LibraryResult<V>> createDisconnectedFuture() {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
} }
private void verifyApplicationThread() { private void verifyApplicationThread() {

View File

@ -15,9 +15,6 @@
*/ */
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_PERMISSION_DENIED;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED;
import static androidx.media3.session.LibraryResult.RESULT_INFO_SKIPPED;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN; import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM; import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT; import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT;
@ -25,6 +22,9 @@ import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_SE
import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SEARCH; import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SEARCH;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE; import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE; import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE;
import static androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED;
import static androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED;
import static androidx.media3.session.SessionError.INFO_SKIPPED;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@ -189,20 +189,20 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
IMediaSession iSession = getSessionInterfaceWithSessionCommandIfAble(commandCode); IMediaSession iSession = getSessionInterfaceWithSessionCommandIfAble(commandCode);
if (iSession != null) { if (iSession != null) {
SequencedFuture<LibraryResult<V>> result = SequencedFuture<LibraryResult<V>> result =
sequencedFutureManager.createSequencedFuture(LibraryResult.ofError(RESULT_INFO_SKIPPED)); sequencedFutureManager.createSequencedFuture(LibraryResult.ofError(INFO_SKIPPED));
try { try {
task.run(iSession, result.getSequenceNumber()); task.run(iSession, result.getSequenceNumber());
} catch (RemoteException e) { } catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e); Log.w(TAG, "Cannot connect to the service or the session is gone", e);
sequencedFutureManager.setFutureResult( sequencedFutureManager.setFutureResult(
result.getSequenceNumber(), LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); result.getSequenceNumber(), LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
} }
return result; return result;
} else { } else {
// Don't create Future with SequencedFutureManager. // Don't create Future with SequencedFutureManager.
// Otherwise session would receive discontinued sequence number, and it would make // Otherwise session would receive discontinued sequence number, and it would make
// future work item 'keeping call sequence when session execute commands' impossible. // future work item 'keeping call sequence when session execute commands' impossible.
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_PERMISSION_DENIED));
} }
} }

View File

@ -15,10 +15,10 @@
*/ */
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE; import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_PERMISSION_DENIED; import static androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED; import static androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_UNKNOWN; import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@ -92,7 +92,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
public ListenableFuture<LibraryResult<MediaItem>> getLibraryRoot(@Nullable LibraryParams params) { public ListenableFuture<LibraryResult<MediaItem>> getLibraryRoot(@Nullable LibraryParams params) {
if (!getInstance() if (!getInstance()
.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT)) { .isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_PERMISSION_DENIED));
} }
SettableFuture<LibraryResult<MediaItem>> result = SettableFuture.create(); SettableFuture<LibraryResult<MediaItem>> result = SettableFuture.create();
MediaBrowserCompat browserCompat = getBrowserCompat(params); MediaBrowserCompat browserCompat = getBrowserCompat(params);
@ -117,11 +117,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
public ListenableFuture<LibraryResult<Void>> subscribe( public ListenableFuture<LibraryResult<Void>> subscribe(
String parentId, @Nullable LibraryParams params) { String parentId, @Nullable LibraryParams params) {
if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE)) { if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
if (browserCompat == null) { if (browserCompat == null) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
} }
SettableFuture<LibraryResult<Void>> future = SettableFuture.create(); SettableFuture<LibraryResult<Void>> future = SettableFuture.create();
SubscribeCallback callback = new SubscribeCallback(future); SubscribeCallback callback = new SubscribeCallback(future);
@ -138,17 +138,17 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
@Override @Override
public ListenableFuture<LibraryResult<Void>> unsubscribe(String parentId) { public ListenableFuture<LibraryResult<Void>> unsubscribe(String parentId) {
if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE)) { if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
if (browserCompat == null) { if (browserCompat == null) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
} }
// Note: don't use MediaBrowserCompat#unsubscribe(String) here, to keep the subscription // Note: don't use MediaBrowserCompat#unsubscribe(String) here, to keep the subscription
// callback for getChildren. // callback for getChildren.
List<SubscribeCallback> list = subscribeCallbacks.get(parentId); List<SubscribeCallback> list = subscribeCallbacks.get(parentId);
if (list == null) { if (list == null) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_BAD_VALUE));
} }
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
browserCompat.unsubscribe(parentId, list.get(i)); browserCompat.unsubscribe(parentId, list.get(i));
@ -163,11 +163,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
String parentId, int page, int pageSize, @Nullable LibraryParams params) { String parentId, int page, int pageSize, @Nullable LibraryParams params) {
if (!getInstance() if (!getInstance()
.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN)) { .isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
if (browserCompat == null) { if (browserCompat == null) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
} }
SettableFuture<LibraryResult<ImmutableList<MediaItem>>> future = SettableFuture.create(); SettableFuture<LibraryResult<ImmutableList<MediaItem>>> future = SettableFuture.create();
@ -179,11 +179,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
@Override @Override
public ListenableFuture<LibraryResult<MediaItem>> getItem(String mediaId) { public ListenableFuture<LibraryResult<MediaItem>> getItem(String mediaId) {
if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM)) { if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
if (browserCompat == null) { if (browserCompat == null) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
} }
SettableFuture<LibraryResult<MediaItem>> result = SettableFuture.create(); SettableFuture<LibraryResult<MediaItem>> result = SettableFuture.create();
browserCompat.getItem( browserCompat.getItem(
@ -196,13 +196,13 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
LibraryResult.ofItem( LibraryResult.ofItem(
LegacyConversions.convertToMediaItem(item), /* params= */ null)); LegacyConversions.convertToMediaItem(item), /* params= */ null));
} else { } else {
result.set(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE)); result.set(LibraryResult.ofError(ERROR_BAD_VALUE));
} }
} }
@Override @Override
public void onError(String itemId) { public void onError(String itemId) {
result.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN)); result.set(LibraryResult.ofError(ERROR_UNKNOWN));
} }
}); });
return result; return result;
@ -212,11 +212,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
public ListenableFuture<LibraryResult<Void>> search( public ListenableFuture<LibraryResult<Void>> search(
String query, @Nullable LibraryParams params) { String query, @Nullable LibraryParams params) {
if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_SEARCH)) { if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_SEARCH)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
if (browserCompat == null) { if (browserCompat == null) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
} }
browserCompat.search( browserCompat.search(
query, query,
@ -259,11 +259,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
String query, int page, int pageSize, @Nullable LibraryParams params) { String query, int page, int pageSize, @Nullable LibraryParams params) {
if (!getInstance() if (!getInstance()
.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT)) { .isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
if (browserCompat == null) { if (browserCompat == null) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
} }
SettableFuture<LibraryResult<ImmutableList<MediaItem>>> future = SettableFuture.create(); SettableFuture<LibraryResult<ImmutableList<MediaItem>>> future = SettableFuture.create();
@ -285,7 +285,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
@Override @Override
public void onError(String query, @Nullable Bundle extrasSent) { public void onError(String query, @Nullable Bundle extrasSent) {
future.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN)); future.set(LibraryResult.ofError(ERROR_UNKNOWN));
} }
}); });
return future; return future;
@ -339,7 +339,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
MediaBrowserCompat browserCompat = browserCompats.get(params); MediaBrowserCompat browserCompat = browserCompats.get(params);
if (browserCompat == null) { if (browserCompat == null) {
// Shouldn't be happen. Internal error? // Shouldn't be happen. Internal error?
result.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN)); result.set(LibraryResult.ofError(ERROR_UNKNOWN));
} else { } else {
result.set( result.set(
LibraryResult.ofItem( LibraryResult.ofItem(
@ -356,7 +356,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
@Override @Override
public void onConnectionFailed() { public void onConnectionFailed() {
// Unknown extra field. // Unknown extra field.
result.set(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE)); result.set(LibraryResult.ofError(ERROR_BAD_VALUE));
release(); release();
} }
} }
@ -396,7 +396,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
private void onErrorInternal() { private void onErrorInternal() {
// Don't need to unsubscribe here, because MediaBrowserServiceCompat can notify children // Don't need to unsubscribe here, because MediaBrowserServiceCompat can notify children
// changed after the initial failure and MediaBrowserCompat could receive the changes. // changed after the initial failure and MediaBrowserCompat could receive the changes.
future.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN)); future.set(LibraryResult.ofError(ERROR_UNKNOWN));
} }
private void onChildrenLoadedInternal( private void onChildrenLoadedInternal(
@ -468,7 +468,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
} }
private void onErrorInternal() { private void onErrorInternal() {
future.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN)); future.set(LibraryResult.ofError(ERROR_UNKNOWN));
} }
private void onChildrenLoadedInternal( private void onChildrenLoadedInternal(
@ -479,14 +479,14 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
if (browserCompat == null) { if (browserCompat == null) {
future.set(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED)); future.set(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
return; return;
} }
browserCompat.unsubscribe(this.parentId, GetChildrenCallback.this); browserCompat.unsubscribe(this.parentId, GetChildrenCallback.this);
if (children == null) { if (children == null) {
// list are non-Null, so it must be internal error. // list are non-Null, so it must be internal error.
future.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN)); future.set(LibraryResult.ofError(ERROR_UNKNOWN));
} else { } else {
// Don't set extra here, because 'extra' have different meanings between old // Don't set extra here, because 'extra' have different meanings between old
// API and new API as follows. // API and new API as follows.

View File

@ -21,6 +21,8 @@ import static androidx.media3.common.util.Assertions.checkNotEmpty;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.postOrRun; import static androidx.media3.common.util.Util.postOrRun;
import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
import static androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
@ -377,7 +379,7 @@ public class MediaController implements Player {
*/ */
default ListenableFuture<SessionResult> onSetCustomLayout( default ListenableFuture<SessionResult> onSetCustomLayout(
MediaController controller, List<CommandButton> layout) { MediaController controller, List<CommandButton> layout) {
return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(new SessionResult(ERROR_NOT_SUPPORTED));
} }
/** /**
@ -416,7 +418,7 @@ public class MediaController implements Player {
* Futures#immediateFuture(Object)}. * Futures#immediateFuture(Object)}.
* *
* <p>The default implementation returns {@link ListenableFuture} of {@link * <p>The default implementation returns {@link ListenableFuture} of {@link
* SessionResult#RESULT_ERROR_NOT_SUPPORTED}. * SessionError#ERROR_NOT_SUPPORTED}.
* *
* @param controller The controller. * @param controller The controller.
* @param command The custom command. * @param command The custom command.
@ -425,7 +427,7 @@ public class MediaController implements Player {
*/ */
default ListenableFuture<SessionResult> onCustomCommand( default ListenableFuture<SessionResult> onCustomCommand(
MediaController controller, SessionCommand command, Bundle args) { MediaController controller, SessionCommand command, Bundle args) {
return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(new SessionResult(SessionError.ERROR_NOT_SUPPORTED));
} }
/** /**
@ -2029,8 +2031,7 @@ public class MediaController implements Player {
} }
private static ListenableFuture<SessionResult> createDisconnectedFuture() { private static ListenableFuture<SessionResult> createDisconnectedFuture() {
return Futures.immediateFuture( return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED));
} }
/* package */ final void runOnApplicationLooper(Runnable runnable) { /* package */ final void runOnApplicationLooper(Runnable runnable) {

View File

@ -23,6 +23,9 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.usToMs; import static androidx.media3.common.util.Util.usToMs;
import static androidx.media3.session.MediaUtils.calculateBufferedPercentage; import static androidx.media3.session.MediaUtils.calculateBufferedPercentage;
import static androidx.media3.session.MediaUtils.mergePlayerInfo; import static androidx.media3.session.MediaUtils.mergePlayerInfo;
import static androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED;
import static androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED;
import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -327,8 +330,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
int sequenceNumber = int sequenceNumber =
((SequencedFutureManager.SequencedFuture<SessionResult>) future).getSequenceNumber(); ((SequencedFutureManager.SequencedFuture<SessionResult>) future).getSequenceNumber();
pendingMaskingSequencedFutureNumbers.remove(sequenceNumber); pendingMaskingSequencedFutureNumbers.remove(sequenceNumber);
sequencedFutureManager.setFutureResult( sequencedFutureManager.setFutureResult(sequenceNumber, new SessionResult(ERROR_UNKNOWN));
sequenceNumber, new SessionResult(SessionResult.RESULT_ERROR_UNKNOWN));
} }
Log.w(TAG, "Synchronous command takes too long on the session side.", e); Log.w(TAG, "Synchronous command takes too long on the session side.", e);
// TODO(b/188888693): Let developers know the failure in their code. // TODO(b/188888693): Let developers know the failure in their code.
@ -377,15 +379,14 @@ import org.checkerframework.checker.nullness.qual.NonNull;
Log.w(TAG, "Cannot connect to the service or the session is gone", e); Log.w(TAG, "Cannot connect to the service or the session is gone", e);
pendingMaskingSequencedFutureNumbers.remove(sequenceNumber); pendingMaskingSequencedFutureNumbers.remove(sequenceNumber);
sequencedFutureManager.setFutureResult( sequencedFutureManager.setFutureResult(
sequenceNumber, new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED)); sequenceNumber, new SessionResult(ERROR_SESSION_DISCONNECTED));
} }
return result; return result;
} else { } else {
// Don't create Future with SequencedFutureManager. // Don't create Future with SequencedFutureManager.
// Otherwise session would receive discontinued sequence number, and it would make // Otherwise session would receive discontinued sequence number, and it would make
// future work item 'keeping call sequence when session execute commands' impossible. // future work item 'keeping call sequence when session execute commands' impossible.
return Futures.immediateFuture( return Futures.immediateFuture(new SessionResult(ERROR_PERMISSION_DENIED));
new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
} }
} }
@ -2662,7 +2663,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
result = new SessionResult(SessionResult.RESULT_INFO_SKIPPED); result = new SessionResult(SessionResult.RESULT_INFO_SKIPPED);
} catch (ExecutionException | InterruptedException e) { } catch (ExecutionException | InterruptedException e) {
Log.w(TAG, "Session operation failed", e); Log.w(TAG, "Session operation failed", e);
result = new SessionResult(SessionResult.RESULT_ERROR_UNKNOWN); result = new SessionResult(ERROR_UNKNOWN);
} }
sendControllerResult(seq, result); sendControllerResult(seq, result);
}, },

View File

@ -18,9 +18,10 @@ package androidx.media3.session;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotEmpty; import static androidx.media3.common.util.Assertions.checkNotEmpty;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS; import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
import static androidx.media3.session.LibraryResult.ofVoid; import static androidx.media3.session.LibraryResult.ofVoid;
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
@ -158,7 +159,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
*/ */
default ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot( default ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(
MediaLibrarySession session, ControllerInfo browser, @Nullable LibraryParams params) { MediaLibrarySession session, ControllerInfo browser, @Nullable LibraryParams params) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED));
} }
/** /**
@ -181,7 +182,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
*/ */
default ListenableFuture<LibraryResult<MediaItem>> onGetItem( default ListenableFuture<LibraryResult<MediaItem>> onGetItem(
MediaLibrarySession session, ControllerInfo browser, String mediaId) { MediaLibrarySession session, ControllerInfo browser, String mediaId) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED));
} }
/** /**
@ -215,7 +216,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
@IntRange(from = 0) int page, @IntRange(from = 0) int page,
@IntRange(from = 1) int pageSize, @IntRange(from = 1) int pageSize,
@Nullable LibraryParams params) { @Nullable LibraryParams params) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED));
} }
/** /**
@ -271,9 +272,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
// Reject subscription if no browsable item for the parent media ID is returned. // Reject subscription if no browsable item for the parent media ID is returned.
return Futures.immediateFuture( return Futures.immediateFuture(
LibraryResult.ofError( LibraryResult.ofError(
result.resultCode != RESULT_SUCCESS result.resultCode != RESULT_SUCCESS ? result.resultCode : ERROR_BAD_VALUE));
? result.resultCode
: LibraryResult.RESULT_ERROR_BAD_VALUE));
} }
if (browser.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) { if (browser.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) {
// For legacy browsers, android.service.media.MediaBrowserService already calls // For legacy browsers, android.service.media.MediaBrowserService already calls
@ -343,7 +342,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
ControllerInfo browser, ControllerInfo browser,
String query, String query,
@Nullable LibraryParams params) { @Nullable LibraryParams params) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED));
} }
/** /**
@ -381,7 +380,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
@IntRange(from = 0) int page, @IntRange(from = 0) int page,
@IntRange(from = 1) int pageSize, @IntRange(from = 1) int pageSize,
@Nullable LibraryParams params) { @Nullable LibraryParams params) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED));
} }
} }

View File

@ -17,11 +17,14 @@ package androidx.media3.session;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED;
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS; import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
import static androidx.media3.session.MediaConstants.ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT; import static androidx.media3.session.MediaConstants.ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT;
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT; import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
import static androidx.media3.session.PlayerWrapper.STATUS_CODE_SUCCESS_COMPAT;
import static androidx.media3.session.SessionError.ERROR_INVALID_STATE;
import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
import static androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED;
import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -122,7 +125,7 @@ import java.util.concurrent.Future;
if (params != null && params.isRecent && isSystemUiController(browser)) { if (params != null && params.isRecent && isSystemUiController(browser)) {
// Advertise support for playback resumption, if enabled. // Advertise support for playback resumption, if enabled.
return !canResumePlaybackOnStart() return !canResumePlaybackOnStart()
? Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED)) ? Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED))
: Futures.immediateFuture( : Futures.immediateFuture(
LibraryResult.ofItem( LibraryResult.ofItem(
new MediaItem.Builder() new MediaItem.Builder()
@ -156,7 +159,7 @@ import java.util.concurrent.Future;
@Nullable LibraryParams params) { @Nullable LibraryParams params) {
if (Objects.equals(parentId, RECENT_LIBRARY_ROOT_MEDIA_ID)) { if (Objects.equals(parentId, RECENT_LIBRARY_ROOT_MEDIA_ID)) {
if (!canResumePlaybackOnStart()) { if (!canResumePlaybackOnStart()) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED));
} }
// Advertise support for playback resumption. If STATE_IDLE, the request arrives at boot time // Advertise support for playback resumption. If STATE_IDLE, the request arrives at boot time
// to get the full item data to build a notification. If not STATE_IDLE we don't need to // to get the full item data to build a notification. If not STATE_IDLE we don't need to
@ -369,25 +372,40 @@ import java.util.concurrent.Future;
private void maybeUpdateLegacyErrorState(LibraryResult<?> result) { private void maybeUpdateLegacyErrorState(LibraryResult<?> result) {
PlayerWrapper playerWrapper = getPlayerWrapper(); PlayerWrapper playerWrapper = getPlayerWrapper();
if (result.resultCode == RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED if (setLegacyErrorState(result)) {
&& result.params != null // Sync playback state if legacy error state changed.
&& result.params.extras.containsKey(EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT)) { getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat());
// Mapping this error to the legacy error state provides backwards compatibility for the } else if (playerWrapper.getLegacyStatusCode() != STATUS_CODE_SUCCESS_COMPAT) {
// Automotive OS sign-in.
MediaSessionCompat mediaSessionCompat = getSessionCompat();
if (playerWrapper.getLegacyStatusCode() != RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED) {
playerWrapper.setLegacyErrorStatus(
ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT,
getContext().getString(R.string.authentication_required),
result.params.extras);
mediaSessionCompat.setPlaybackState(playerWrapper.createPlaybackStateCompat());
}
} else if (playerWrapper.getLegacyStatusCode() != RESULT_SUCCESS) {
playerWrapper.clearLegacyErrorStatus(); playerWrapper.clearLegacyErrorStatus();
getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat()); getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat());
} }
} }
private boolean setLegacyErrorState(LibraryResult<?> result) {
if (result.resultCode == ERROR_SESSION_AUTHENTICATION_EXPIRED
&& getPlayerWrapper().getLegacyStatusCode() != ERROR_SESSION_AUTHENTICATION_EXPIRED) {
// Mapping this error to the legacy error state provides backwards compatibility for the
// Automotive OS sign-in.
Bundle bundle = Bundle.EMPTY;
if (result.params != null
&& result.params.extras.containsKey(EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT)) {
// Backwards compatibility for Callbacks before SessionError was introduced.
bundle = result.params.extras;
} else if (result.sessionError != null
&& result.sessionError.extras.containsKey(
EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT)) {
bundle = result.sessionError.extras;
}
getPlayerWrapper()
.setLegacyErrorStatus(
ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT,
getContext().getString(R.string.authentication_required),
bundle);
return true;
}
return false;
}
@Nullable @Nullable
private static <T> T tryGetFutureResult(Future<T> future) { private static <T> T tryGetFutureResult(Future<T> future) {
checkState(future.isDone()); checkState(future.isDone());
@ -436,8 +454,7 @@ import java.util.concurrent.Future;
@Override @Override
public void onSuccess(MediaSession.MediaItemsWithStartPosition playlist) { public void onSuccess(MediaSession.MediaItemsWithStartPosition playlist) {
if (playlist.mediaItems.isEmpty()) { if (playlist.mediaItems.isEmpty()) {
settableFuture.set( settableFuture.set(LibraryResult.ofError(ERROR_INVALID_STATE, params));
LibraryResult.ofError(LibraryResult.RESULT_ERROR_INVALID_STATE, params));
return; return;
} }
int sanitizedStartIndex = int sanitizedStartIndex =
@ -449,7 +466,7 @@ import java.util.concurrent.Future;
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
settableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_UNKNOWN, params)); settableFuture.set(LibraryResult.ofError(ERROR_UNKNOWN, params));
Log.e(TAG, "Failed fetching recent media item at boot time: " + t.getMessage(), t); Log.e(TAG, "Failed fetching recent media item at boot time: " + t.getMessage(), t);
} }
}, },

View File

@ -19,7 +19,7 @@ import static androidx.annotation.VisibleForTesting.PRIVATE;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.session.SessionResult.RESULT_ERROR_NOT_SUPPORTED; import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
import static androidx.media3.session.SessionResult.RESULT_SUCCESS; import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -1363,7 +1363,7 @@ public class MediaSession {
*/ */
default ListenableFuture<SessionResult> onSetRating( default ListenableFuture<SessionResult> onSetRating(
MediaSession session, ControllerInfo controller, String mediaId, Rating rating) { MediaSession session, ControllerInfo controller, String mediaId, Rating rating) {
return Futures.immediateFuture(new SessionResult(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(new SessionResult(ERROR_NOT_SUPPORTED));
} }
/** /**
@ -1385,7 +1385,7 @@ public class MediaSession {
*/ */
default ListenableFuture<SessionResult> onSetRating( default ListenableFuture<SessionResult> onSetRating(
MediaSession session, ControllerInfo controller, Rating rating) { MediaSession session, ControllerInfo controller, Rating rating) {
return Futures.immediateFuture(new SessionResult(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(new SessionResult(ERROR_NOT_SUPPORTED));
} }
/** /**
@ -1418,7 +1418,7 @@ public class MediaSession {
ControllerInfo controller, ControllerInfo controller,
SessionCommand customCommand, SessionCommand customCommand,
Bundle args) { Bundle args) {
return Futures.immediateFuture(new SessionResult(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(new SessionResult(ERROR_NOT_SUPPORTED));
} }
/** /**

View File

@ -31,9 +31,9 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.postOrRun; import static androidx.media3.common.util.Util.postOrRun;
import static androidx.media3.session.MediaSessionStub.UNKNOWN_SEQUENCE_NUMBER; import static androidx.media3.session.MediaSessionStub.UNKNOWN_SEQUENCE_NUMBER;
import static androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED; import static androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED;
import static androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN; import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED; import static androidx.media3.session.SessionError.INFO_SKIPPED;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ComponentName; import android.content.ComponentName;
@ -113,7 +113,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public static final String TAG = "MediaSessionImpl"; public static final String TAG = "MediaSessionImpl";
private static final SessionResult RESULT_WHEN_CLOSED = new SessionResult(RESULT_INFO_SKIPPED); private static final SessionResult RESULT_WHEN_CLOSED = new SessionResult(INFO_SKIPPED);
private final Object lock = new Object(); private final Object lock = new Object();
@ -1090,7 +1090,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
seq = ((SequencedFuture<SessionResult>) future).getSequenceNumber(); seq = ((SequencedFuture<SessionResult>) future).getSequenceNumber();
} else { } else {
if (!isConnected(controller)) { if (!isConnected(controller)) {
return Futures.immediateFuture(new SessionResult(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
} }
// 0 is OK for legacy controllers, because they didn't have sequence numbers. // 0 is OK for legacy controllers, because they didn't have sequence numbers.
seq = 0; seq = 0;
@ -1104,7 +1104,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return future; return future;
} catch (DeadObjectException e) { } catch (DeadObjectException e) {
onDeadObjectException(controller); onDeadObjectException(controller);
return Futures.immediateFuture(new SessionResult(RESULT_ERROR_SESSION_DISCONNECTED)); return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
} catch (RemoteException e) { } catch (RemoteException e) {
// Currently it's TransactionTooLargeException or DeadSystemException. // Currently it's TransactionTooLargeException or DeadSystemException.
// We'd better to leave log for those cases because // We'd better to leave log for those cases because
@ -1113,7 +1113,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// - DeadSystemException means that errors around it can be ignored. // - DeadSystemException means that errors around it can be ignored.
Log.w(TAG, "Exception in " + controller.toString(), e); Log.w(TAG, "Exception in " + controller.toString(), e);
} }
return Futures.immediateFuture(new SessionResult(RESULT_ERROR_UNKNOWN)); return Futures.immediateFuture(new SessionResult(ERROR_UNKNOWN));
} }
/** Removes controller. Call this when DeadObjectException is happened with binder call. */ /** Removes controller. Call this when DeadObjectException is happened with binder call. */

View File

@ -37,7 +37,7 @@ import static androidx.media3.common.util.Util.castNonNull;
import static androidx.media3.common.util.Util.postOrRun; import static androidx.media3.common.util.Util.postOrRun;
import static androidx.media3.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES; import static androidx.media3.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_CUSTOM; import static androidx.media3.session.SessionCommand.COMMAND_CODE_CUSTOM;
import static androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN; import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED; import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
import static androidx.media3.session.SessionResult.RESULT_SUCCESS; import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
@ -936,7 +936,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
result = new SessionResult(RESULT_INFO_SKIPPED); result = new SessionResult(RESULT_INFO_SKIPPED);
} catch (ExecutionException | InterruptedException e) { } catch (ExecutionException | InterruptedException e) {
Log.w(TAG, "Custom command failed", e); Log.w(TAG, "Custom command failed", e);
result = new SessionResult(RESULT_ERROR_UNKNOWN); result = new SessionResult(ERROR_UNKNOWN);
} }
receiver.send(result.resultCode, result.extras); receiver.send(result.resultCode, result.extras);
}, },

View File

@ -53,6 +53,11 @@ import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SEARCH
import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE; import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE; import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING; import static androidx.media3.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING;
import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
import static androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED;
import static androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED;
import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
import static androidx.media3.session.SessionError.INFO_SKIPPED;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.os.Binder; import android.os.Binder;
@ -192,8 +197,8 @@ import java.util.concurrent.ExecutionException;
result = result =
new SessionResult( new SessionResult(
exception.getCause() instanceof UnsupportedOperationException exception.getCause() instanceof UnsupportedOperationException
? SessionResult.RESULT_ERROR_NOT_SUPPORTED ? ERROR_NOT_SUPPORTED
: SessionResult.RESULT_ERROR_UNKNOWN); : ERROR_UNKNOWN);
} }
sendSessionResult(controller, sequenceNumber, result); sendSessionResult(controller, sequenceNumber, result);
}); });
@ -205,8 +210,7 @@ import java.util.concurrent.ExecutionException;
MediaItemPlayerTask mediaItemPlayerTask) { MediaItemPlayerTask mediaItemPlayerTask) {
return (sessionImpl, controller, sequenceNumber) -> { return (sessionImpl, controller, sequenceNumber) -> {
if (sessionImpl.isReleased()) { if (sessionImpl.isReleased()) {
return Futures.immediateFuture( return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED));
} }
return transformFutureAsync( return transformFutureAsync(
mediaItemsTask.run(sessionImpl, controller, sequenceNumber), mediaItemsTask.run(sessionImpl, controller, sequenceNumber),
@ -231,8 +235,7 @@ import java.util.concurrent.ExecutionException;
MediaItemsWithStartPositionPlayerTask mediaItemPlayerTask) { MediaItemsWithStartPositionPlayerTask mediaItemPlayerTask) {
return (sessionImpl, controller, sequenceNumber) -> { return (sessionImpl, controller, sequenceNumber) -> {
if (sessionImpl.isReleased()) { if (sessionImpl.isReleased()) {
return Futures.immediateFuture( return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED));
} }
return transformFutureAsync( return transformFutureAsync(
mediaItemsTask.run(sessionImpl, controller, sequenceNumber), mediaItemsTask.run(sessionImpl, controller, sequenceNumber),
@ -275,10 +278,10 @@ import java.util.concurrent.ExecutionException;
result = checkNotNull(future.get(), "LibraryResult must not be null"); result = checkNotNull(future.get(), "LibraryResult must not be null");
} catch (CancellationException e) { } catch (CancellationException e) {
Log.w(TAG, "Library operation cancelled", e); Log.w(TAG, "Library operation cancelled", e);
result = LibraryResult.ofError(LibraryResult.RESULT_INFO_SKIPPED); result = LibraryResult.ofError(INFO_SKIPPED);
} catch (ExecutionException | InterruptedException e) { } catch (ExecutionException | InterruptedException e) {
Log.w(TAG, "Library operation failed", e); Log.w(TAG, "Library operation failed", e);
result = LibraryResult.ofError(LibraryResult.RESULT_ERROR_UNKNOWN); result = LibraryResult.ofError(ERROR_UNKNOWN);
} }
sendLibraryResult(controller, sequenceNumber, result); sendLibraryResult(controller, sequenceNumber, result);
}); });
@ -314,9 +317,7 @@ import java.util.concurrent.ExecutionException;
() -> { () -> {
if (!connectedControllersManager.isPlayerCommandAvailable(controller, command)) { if (!connectedControllersManager.isPlayerCommandAvailable(controller, command)) {
sendSessionResult( sendSessionResult(
controller, controller, sequenceNumber, new SessionResult(ERROR_PERMISSION_DENIED));
sequenceNumber,
new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
return; return;
} }
@SessionResult.Code @SessionResult.Code
@ -393,17 +394,13 @@ import java.util.concurrent.ExecutionException;
if (!connectedControllersManager.isSessionCommandAvailable( if (!connectedControllersManager.isSessionCommandAvailable(
controller, sessionCommand)) { controller, sessionCommand)) {
sendSessionResult( sendSessionResult(
controller, controller, sequenceNumber, new SessionResult(ERROR_PERMISSION_DENIED));
sequenceNumber,
new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
return; return;
} }
} else { } else {
if (!connectedControllersManager.isSessionCommandAvailable(controller, commandCode)) { if (!connectedControllersManager.isSessionCommandAvailable(controller, commandCode)) {
sendSessionResult( sendSessionResult(
controller, controller, sequenceNumber, new SessionResult(ERROR_PERMISSION_DENIED));
sequenceNumber,
new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
return; return;
} }
} }

View File

@ -63,7 +63,7 @@ import java.util.List;
*/ */
/* package */ final class PlayerWrapper extends ForwardingPlayer { /* package */ final class PlayerWrapper extends ForwardingPlayer {
private static final int STATUS_CODE_SUCCESS_COMPAT = -1; /* package */ static final int STATUS_CODE_SUCCESS_COMPAT = -1;
private final boolean playIfSuppressed; private final boolean playIfSuppressed;

View File

@ -0,0 +1,194 @@
/*
* 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 java.lang.annotation.ElementType.TYPE_USE;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;
/** Provides information about a session error. */
@UnstableApi
public final class SessionError {
/**
* Info and error result codes.
*
* <ul>
* <li>Info code: Positive integer
* <li>Error code: Negative integer
* </ul>
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
INFO_SKIPPED,
ERROR_UNKNOWN,
ERROR_INVALID_STATE,
ERROR_BAD_VALUE,
ERROR_PERMISSION_DENIED,
ERROR_IO,
ERROR_SESSION_DISCONNECTED,
ERROR_NOT_SUPPORTED,
ERROR_SESSION_AUTHENTICATION_EXPIRED,
ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
ERROR_SESSION_SKIP_LIMIT_REACHED,
ERROR_SESSION_SETUP_REQUIRED
})
public @interface Code {}
/** Info code representing that the command is skipped. */
public static final int INFO_SKIPPED = 1;
/** Error code representing that the command is ended with an unknown error. */
public static final int ERROR_UNKNOWN = -1;
/**
* Error code representing that the command cannot be completed because the current state is not
* valid for the command.
*/
public static final int ERROR_INVALID_STATE = -2;
/** Error code representing that an argument is illegal. */
public static final int ERROR_BAD_VALUE = -3;
/** Error code representing that the command is not allowed. */
public static final int ERROR_PERMISSION_DENIED = -4;
/** Error code representing that a file or network related error happened. */
public static final int ERROR_IO = -5;
/** Error code representing that the command is not supported. */
public static final int ERROR_NOT_SUPPORTED = -6;
/** Error code representing that the session and controller were disconnected. */
public static final int ERROR_SESSION_DISCONNECTED = -100;
/** Error code representing that the authentication has expired. */
public static final int ERROR_SESSION_AUTHENTICATION_EXPIRED = -102;
/** Error code representing that a premium account is required. */
public static final int ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103;
/** Error code representing that too many concurrent streams are detected. */
public static final int ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104;
/** Error code representing that the content is blocked due to parental controls. */
public static final int ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105;
/** Error code representing that the content is blocked due to being regionally unavailable. */
public static final int ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106;
/**
* Error code representing that the application cannot skip any more because the skip limit is
* reached.
*/
public static final int ERROR_SESSION_SKIP_LIMIT_REACHED = -107;
/** Error code representing that the session needs user's manual intervention. */
public static final int ERROR_SESSION_SETUP_REQUIRED = -108;
/** Default error message. Only used by deprecated methods and for backwards compatibility. */
public static final String DEFAULT_ERROR_MESSAGE = "no error message provided";
public @SessionError.Code int code;
public String message;
public Bundle extras;
/**
* Creates an instance with {@linkplain Bundle#EMPTY an empty extras bundle}.
*
* @param code The error result code.
* @param message The error message.
* @throws IllegalArgumentException if the result code is not an error result code.
*/
public SessionError(@SessionError.Code int code, String message) {
this(code, message, Bundle.EMPTY);
}
/**
* Creates an instance.
*
* @param code The error result code.
* @param message The error message.
* @param extras The error extras.
* @throws IllegalArgumentException if the result code is not an error result code.
*/
public SessionError(@SessionError.Code int code, String message, Bundle extras) {
Assertions.checkArgument(code < 0 || code == INFO_SKIPPED);
this.code = code;
this.message = message;
this.extras = extras;
}
/** Checks the given error for equality while ignoring {@link #extras}. */
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SessionError)) {
return false;
}
SessionError that = (SessionError) o;
return code == that.code && Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(code, message);
}
// Bundleable implementation.
private static final String FIELD_CODE = Util.intToStringMaxRadix(0);
private static final String FIELD_MESSAGE = Util.intToStringMaxRadix(1);
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(2);
/** Returns a {@link Bundle} representing the information stored in this object. */
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putInt(FIELD_CODE, code);
bundle.putString(FIELD_MESSAGE, message);
if (!extras.isEmpty()) {
bundle.putBundle(FIELD_EXTRAS, extras);
}
return bundle;
}
/** Restores a {@code SessionError} from a {@link Bundle}. */
public static SessionError fromBundle(Bundle bundle) {
int code =
bundle.getInt(FIELD_CODE, /* defaultValue= */ PlaybackException.ERROR_CODE_UNSPECIFIED);
String message = bundle.getString(FIELD_MESSAGE, /* defaultValue= */ "");
@Nullable Bundle extras = bundle.getBundle(FIELD_EXTRAS);
return new SessionError(code, message, extras == null ? Bundle.EMPTY : extras);
}
}

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.common.util.Assertions.checkArgument;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.os.Bundle; import android.os.Bundle;
@ -57,21 +58,21 @@ public final class SessionResult implements Bundleable {
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({ @IntDef({
RESULT_SUCCESS, RESULT_SUCCESS,
RESULT_ERROR_UNKNOWN, SessionError.INFO_SKIPPED,
RESULT_ERROR_INVALID_STATE, SessionError.ERROR_UNKNOWN,
RESULT_ERROR_BAD_VALUE, SessionError.ERROR_INVALID_STATE,
RESULT_ERROR_PERMISSION_DENIED, SessionError.ERROR_BAD_VALUE,
RESULT_ERROR_IO, SessionError.ERROR_PERMISSION_DENIED,
RESULT_INFO_SKIPPED, SessionError.ERROR_IO,
RESULT_ERROR_SESSION_DISCONNECTED, SessionError.ERROR_SESSION_DISCONNECTED,
RESULT_ERROR_NOT_SUPPORTED, SessionError.ERROR_NOT_SUPPORTED,
RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED,
RESULT_ERROR_SESSION_SETUP_REQUIRED SessionError.ERROR_SESSION_SETUP_REQUIRED
}) })
public @interface Code {} public @interface Code {}
@ -85,56 +86,64 @@ public final class SessionResult implements Bundleable {
*/ */
public static final int RESULT_SUCCESS = 0; public static final int RESULT_SUCCESS = 0;
/** Result code representing that the command is skipped. */
public static final int RESULT_INFO_SKIPPED = SessionError.INFO_SKIPPED;
/** Result code representing that the command is ended with an unknown error. */ /** Result code representing that the command is ended with an unknown error. */
public static final int RESULT_ERROR_UNKNOWN = -1; public static final int RESULT_ERROR_UNKNOWN = SessionError.ERROR_UNKNOWN;
/** /**
* Result code representing that the command cannot be completed because the current state is not * Result code representing that the command cannot be completed because the current state is not
* valid for the command. * valid for the command.
*/ */
public static final int RESULT_ERROR_INVALID_STATE = -2; public static final int RESULT_ERROR_INVALID_STATE = SessionError.ERROR_INVALID_STATE;
/** Result code representing that an argument is illegal. */ /** Result code representing that an argument is illegal. */
public static final int RESULT_ERROR_BAD_VALUE = -3; public static final int RESULT_ERROR_BAD_VALUE = SessionError.ERROR_BAD_VALUE;
/** Result code representing that the command is not allowed. */ /** Result code representing that the command is not allowed. */
public static final int RESULT_ERROR_PERMISSION_DENIED = -4; public static final int RESULT_ERROR_PERMISSION_DENIED = SessionError.ERROR_PERMISSION_DENIED;
/** Result code representing that a file or network related error happened. */ /** Result code representing that a file or network related error happened. */
public static final int RESULT_ERROR_IO = -5; public static final int RESULT_ERROR_IO = SessionError.ERROR_IO;
/** Result code representing that the command is not supported. */ /** Result code representing that the command is not supported. */
public static final int RESULT_ERROR_NOT_SUPPORTED = -6; public static final int RESULT_ERROR_NOT_SUPPORTED = SessionError.ERROR_NOT_SUPPORTED;
/** Result code representing that the command is skipped. */
public static final int RESULT_INFO_SKIPPED = 1;
/** Result code representing that the session and controller were disconnected. */ /** Result code representing that the session and controller were disconnected. */
public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; public static final int RESULT_ERROR_SESSION_DISCONNECTED =
SessionError.ERROR_SESSION_DISCONNECTED;
/** Result code representing that the authentication has expired. */ /** Result code representing that the authentication has expired. */
public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED =
SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED;
/** Result code representing that a premium account is required. */ /** Result code representing that a premium account is required. */
public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED =
SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED;
/** Result code representing that too many concurrent streams are detected. */ /** Result code representing that too many concurrent streams are detected. */
public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT =
SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT;
/** Result code representing that the content is blocked due to parental controls. */ /** Result code representing that the content is blocked due to parental controls. */
public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED =
SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED;
/** Result code representing that the content is blocked due to being regionally unavailable. */ /** Result code representing that the content is blocked due to being regionally unavailable. */
public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION =
SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION;
/** /**
* Result code representing that the application cannot skip any more because the skip limit is * Result code representing that the application cannot skip any more because the skip limit is
* reached. * reached.
*/ */
public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED =
SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED;
/** Result code representing that the session needs user's manual intervention. */ /** Result code representing that the session needs user's manual intervention. */
public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED =
SessionError.ERROR_SESSION_SETUP_REQUIRED;
/** The {@link Code} of this result. */ /** The {@link Code} of this result. */
public final @Code int resultCode; public final @Code int resultCode;
@ -148,9 +157,15 @@ public final class SessionResult implements Bundleable {
*/ */
public final long completionTimeMs; public final long completionTimeMs;
/** The optional session error. */
@UnstableApi @Nullable public final SessionError sessionError;
/** /**
* Creates an instance with a result code. * Creates an instance with a result code.
* *
* <p>Note: Use {@link SessionResult#SessionResult(SessionError)} for errors to provide a
* localized error message for your users.
*
* @param resultCode The result code. * @param resultCode The result code.
*/ */
public SessionResult(@Code int resultCode) { public SessionResult(@Code int resultCode) {
@ -160,17 +175,64 @@ public final class SessionResult implements Bundleable {
/** /**
* Creates an instance with a result code and an extra {@link Bundle}. * Creates an instance with a result code and an extra {@link Bundle}.
* *
* <p>Note: Use {@link SessionResult#SessionResult(SessionError, Bundle)} for errors to provide a
* localized error message for your users.
*
* @param resultCode The result code. * @param resultCode The result code.
* @param extras The extra {@link Bundle}. * @param extras The extra {@link Bundle}.
*/ */
public SessionResult(@Code int resultCode, Bundle extras) { public SessionResult(@Code int resultCode, Bundle extras) {
this(resultCode, extras, SystemClock.elapsedRealtime()); this(
resultCode,
extras,
/* completionTimeMs= */ SystemClock.elapsedRealtime(),
/* sessionError= */ null);
} }
private SessionResult(@Code int resultCode, Bundle extras, long completionTimeMs) { /**
* Creates an instance from a {@link SessionError}. The {@link #resultCode} is taken from {@link
* SessionError#code} and the session result extras {@link Bundle} is empty.
*
* @param sessionError The {@linkplain SessionError session error}.
*/
@UnstableApi
public SessionResult(SessionError sessionError) {
this(
sessionError.code,
Bundle.EMPTY,
/* completionTimeMs= */ SystemClock.elapsedRealtime(),
sessionError);
}
/**
* Creates an instance from a {@link SessionError} and an extras {@link Bundle}. The {@link
* #resultCode} is taken from the {@link SessionError}.
*
* @param sessionError The {@link SessionError}.
* @param extras The extra {@link Bundle}.
*/
@UnstableApi
public SessionResult(SessionError sessionError, Bundle extras) {
this(
sessionError.code,
extras,
/* completionTimeMs= */ SystemClock.elapsedRealtime(),
sessionError);
}
private SessionResult(
@Code int resultCode,
Bundle extras,
long completionTimeMs,
@Nullable SessionError sessionError) {
checkArgument(sessionError == null || resultCode < 0);
this.resultCode = resultCode; this.resultCode = resultCode;
this.extras = new Bundle(extras); this.extras = new Bundle(extras);
this.completionTimeMs = completionTimeMs; this.completionTimeMs = completionTimeMs;
this.sessionError =
sessionError == null && resultCode < 0
? new SessionError(resultCode, SessionError.DEFAULT_ERROR_MESSAGE)
: sessionError;
} }
// Bundleable implementation. // Bundleable implementation.
@ -178,6 +240,7 @@ public final class SessionResult implements Bundleable {
private static final String FIELD_RESULT_CODE = Util.intToStringMaxRadix(0); private static final String FIELD_RESULT_CODE = Util.intToStringMaxRadix(0);
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1); private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1);
private static final String FIELD_COMPLETION_TIME_MS = Util.intToStringMaxRadix(2); private static final String FIELD_COMPLETION_TIME_MS = Util.intToStringMaxRadix(2);
private static final String FIELD_SESSION_ERROR = Util.intToStringMaxRadix(3);
@UnstableApi @UnstableApi
@Override @Override
@ -186,6 +249,9 @@ public final class SessionResult implements Bundleable {
bundle.putInt(FIELD_RESULT_CODE, resultCode); bundle.putInt(FIELD_RESULT_CODE, resultCode);
bundle.putBundle(FIELD_EXTRAS, extras); bundle.putBundle(FIELD_EXTRAS, extras);
bundle.putLong(FIELD_COMPLETION_TIME_MS, completionTimeMs); bundle.putLong(FIELD_COMPLETION_TIME_MS, completionTimeMs);
if (sessionError != null) {
bundle.putBundle(FIELD_SESSION_ERROR, sessionError.toBundle());
}
return bundle; return bundle;
} }
@ -202,10 +268,21 @@ public final class SessionResult implements Bundleable {
/** Restores a {@code SessionResult} from a {@link Bundle}. */ /** Restores a {@code SessionResult} from a {@link Bundle}. */
@UnstableApi @UnstableApi
public static SessionResult fromBundle(Bundle bundle) { public static SessionResult fromBundle(Bundle bundle) {
int resultCode = bundle.getInt(FIELD_RESULT_CODE, /* defaultValue= */ RESULT_ERROR_UNKNOWN); int resultCode =
bundle.getInt(FIELD_RESULT_CODE, /* defaultValue= */ SessionError.ERROR_UNKNOWN);
@Nullable Bundle extras = bundle.getBundle(FIELD_EXTRAS); @Nullable Bundle extras = bundle.getBundle(FIELD_EXTRAS);
long completionTimeMs = long completionTimeMs =
bundle.getLong(FIELD_COMPLETION_TIME_MS, /* defaultValue= */ SystemClock.elapsedRealtime()); bundle.getLong(FIELD_COMPLETION_TIME_MS, /* defaultValue= */ SystemClock.elapsedRealtime());
return new SessionResult(resultCode, extras == null ? Bundle.EMPTY : extras, completionTimeMs); @Nullable SessionError sessionError = null;
@Nullable Bundle sessionErrorBundle = bundle.getBundle(FIELD_SESSION_ERROR);
if (sessionErrorBundle != null) {
sessionError = SessionError.fromBundle(sessionErrorBundle);
} else if (resultCode != RESULT_SUCCESS) {
// Populate the session error if the session is of a library version that doesn't have the
// SessionError yet.
sessionError = new SessionError(resultCode, SessionError.DEFAULT_ERROR_MESSAGE);
}
return new SessionResult(
resultCode, extras == null ? Bundle.EMPTY : extras, completionTimeMs, sessionError);
} }
} }

View File

@ -15,7 +15,7 @@
*/ */
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED; import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
@ -99,8 +99,7 @@ public class LibraryResultTest {
@Test @Test
public void toBundle_errorResultThatWasUnbundledAsAnUnknownType_noException() { public void toBundle_errorResultThatWasUnbundledAsAnUnknownType_noException() {
LibraryResult<ImmutableList<Error>> libraryResult = LibraryResult<ImmutableList<Error>> libraryResult = LibraryResult.ofError(ERROR_NOT_SUPPORTED);
LibraryResult.ofError(LibraryResult.RESULT_ERROR_NOT_SUPPORTED);
Bundle errorLibraryResultBundle = libraryResult.toBundle(); Bundle errorLibraryResultBundle = libraryResult.toBundle();
LibraryResult<?> libraryResultFromUntyped = LibraryResult<?> libraryResultFromUntyped =
LibraryResult.fromUnknownBundle(errorLibraryResultBundle); LibraryResult.fromUnknownBundle(errorLibraryResultBundle);
@ -109,13 +108,12 @@ public class LibraryResultTest {
assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).value).isNull(); assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).value).isNull();
assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).resultCode) assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).resultCode)
.isEqualTo(RESULT_ERROR_NOT_SUPPORTED); .isEqualTo(ERROR_NOT_SUPPORTED);
} }
@Test @Test
public void toBundle_voidResultThatWasUnbundledAsAnUnknownType_noException() { public void toBundle_voidResultThatWasUnbundledAsAnUnknownType_noException() {
LibraryResult<ImmutableList<Error>> libraryResult = LibraryResult<ImmutableList<Error>> libraryResult = LibraryResult.ofError(ERROR_NOT_SUPPORTED);
LibraryResult.ofError(LibraryResult.RESULT_ERROR_NOT_SUPPORTED);
Bundle errorLibraryResultBundle = libraryResult.toBundle(); Bundle errorLibraryResultBundle = libraryResult.toBundle();
LibraryResult<?> libraryResultFromUntyped = LibraryResult<?> libraryResultFromUntyped =
LibraryResult.fromUnknownBundle(errorLibraryResultBundle); LibraryResult.fromUnknownBundle(errorLibraryResultBundle);
@ -124,6 +122,27 @@ public class LibraryResultTest {
assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).value).isNull(); assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).value).isNull();
assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).resultCode) assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).resultCode)
.isEqualTo(RESULT_ERROR_NOT_SUPPORTED); .isEqualTo(ERROR_NOT_SUPPORTED);
}
@Test
public void toBundle_roundTrip_equalsWithOriginal() {
Bundle errorExtras = new Bundle();
errorExtras.putString("errorKey", "errorValue");
LibraryResult<SessionError> errorLibraryResult =
LibraryResult.ofError(new SessionError(ERROR_NOT_SUPPORTED, "error message", errorExtras));
LibraryResult<?> errorLibraryResultFromBundle =
LibraryResult.fromUnknownBundle(errorLibraryResult.toBundle());
assertThat(errorLibraryResultFromBundle.resultCode).isEqualTo(errorLibraryResult.resultCode);
assertThat(errorLibraryResultFromBundle.sessionError)
.isEqualTo(errorLibraryResult.sessionError);
assertThat(errorLibraryResultFromBundle.sessionError.extras.size()).isEqualTo(1);
assertThat(errorLibraryResultFromBundle.sessionError.extras.getString("errorKey"))
.isEqualTo("errorValue");
assertThat(errorLibraryResultFromBundle.value).isEqualTo(errorLibraryResult.value);
assertThat(errorLibraryResultFromBundle.completionTimeMs)
.isEqualTo(errorLibraryResult.completionTimeMs);
} }
} }

View File

@ -0,0 +1,67 @@
/*
* 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.session.SessionError.ERROR_BAD_VALUE;
import static com.google.common.truth.Truth.assertThat;
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;
/** Tests for {@link SessionError}. */
@RunWith(AndroidJUnit4.class)
public class SessionErrorTest {
@Test
public void constructor_twoArguments_usesEmptyBundle() {
SessionError error = new SessionError(ERROR_BAD_VALUE, "error message");
assertThat(error.extras.size()).isEqualTo(0);
}
@Test
public void constructor_withNonErrorCode_throwsIllegalArgumentException() {
assertThrows(
IllegalArgumentException.class,
() -> new SessionError(SessionResult.RESULT_SUCCESS, "error message"));
}
@Test
public void equals_differentBundles_bundleIgnored() {
Bundle errorBundle1 = new Bundle();
errorBundle1.putString("key", "value");
SessionError error1 = new SessionError(ERROR_BAD_VALUE, "error message", errorBundle1);
SessionError error2 = new SessionError(ERROR_BAD_VALUE, "error message");
assertThat(error1).isEqualTo(error2);
}
@Test
public void toBundle_roundTrip_resultsInEqualObjectWithSameBundle() {
Bundle errorBundle = new Bundle();
errorBundle.putString("key", "value");
SessionError error = new SessionError(ERROR_BAD_VALUE, "error message", errorBundle);
SessionError sessionErrorFromBundle = SessionError.fromBundle(error.toBundle());
assertThat(sessionErrorFromBundle).isEqualTo(error);
assertThat(sessionErrorFromBundle.extras.size()).isEqualTo(1);
assertThat(sessionErrorFromBundle.extras.getString("key")).isEqualTo("value");
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED;
import static androidx.media3.session.SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT;
import static com.google.common.truth.Truth.assertThat;
import android.os.Bundle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link SessionResult}. */
@RunWith(AndroidJUnit4.class)
public class SessionResultTest {
@Test
public void constructor_errorCodeOnly_createsDefaultSessionError() {
SessionResult sessionResult = new SessionResult(ERROR_SESSION_AUTHENTICATION_EXPIRED);
assertThat(sessionResult.resultCode).isEqualTo(ERROR_SESSION_AUTHENTICATION_EXPIRED);
assertThat(sessionResult.extras.size()).isEqualTo(0);
assertThat(sessionResult.sessionError.code).isEqualTo(ERROR_SESSION_AUTHENTICATION_EXPIRED);
assertThat(sessionResult.sessionError.message).isEqualTo(SessionError.DEFAULT_ERROR_MESSAGE);
assertThat(sessionResult.sessionError.extras.size()).isEqualTo(0);
}
@Test
public void constructor_errorCodeAndBundleOnly_createsDefaultSessionError() {
Bundle bundle = new Bundle();
bundle.putString("key", "value");
SessionResult sessionResult = new SessionResult(ERROR_SESSION_CONCURRENT_STREAM_LIMIT, bundle);
assertThat(sessionResult.resultCode).isEqualTo(ERROR_SESSION_CONCURRENT_STREAM_LIMIT);
assertThat(sessionResult.extras.size()).isEqualTo(1);
assertThat(sessionResult.extras.getString("key")).isEqualTo("value");
assertThat(sessionResult.sessionError.code).isEqualTo(ERROR_SESSION_CONCURRENT_STREAM_LIMIT);
assertThat(sessionResult.sessionError.message).isEqualTo(SessionError.DEFAULT_ERROR_MESSAGE);
assertThat(sessionResult.sessionError.extras.size()).isEqualTo(0);
}
@Test
public void toBundle_roundTrip_resultsInEqualObjectWithSameBundle() {
Bundle errorExtras = new Bundle();
errorExtras.putString("errorKey", "errorValue");
SessionResult sessionResult =
new SessionResult(
new SessionError(SessionError.ERROR_NOT_SUPPORTED, "error message", errorExtras));
SessionResult resultFromBundle = SessionResult.fromBundle(sessionResult.toBundle());
assertThat(resultFromBundle.resultCode).isEqualTo(sessionResult.resultCode);
assertThat(resultFromBundle.completionTimeMs).isEqualTo(sessionResult.completionTimeMs);
assertThat(resultFromBundle.sessionError.code).isEqualTo(sessionResult.sessionError.code);
assertThat(resultFromBundle.sessionError.message).isEqualTo(sessionResult.sessionError.message);
assertThat(resultFromBundle.sessionError.extras.size()).isEqualTo(1);
assertThat(resultFromBundle.sessionError.extras.getString("errorKey")).isEqualTo("errorValue");
assertThat(resultFromBundle.extras.size()).isEqualTo(0);
}
}

View File

@ -38,6 +38,8 @@ public class MediaBrowserConstants {
public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children"; public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
public static final String PARENT_ID_ERROR = "parent_id_error"; public static final String PARENT_ID_ERROR = "parent_id_error";
public static final String PARENT_ID_AUTH_EXPIRED_ERROR = "parent_auth_expired_error"; public static final String PARENT_ID_AUTH_EXPIRED_ERROR = "parent_auth_expired_error";
public static final String PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED =
"parent_auth_expired_error_deprecated";
public static final String PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL = public static final String PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL =
"parent_auth_expired_error_label"; "parent_auth_expired_error_label";

View File

@ -23,6 +23,8 @@ public class MediaBrowserServiceCompatConstants {
"testOnChildrenChanged_subscribeAndUnsubscribe"; "testOnChildrenChanged_subscribeAndUnsubscribe";
public static final String TEST_GET_LIBRARY_ROOT = "getLibraryRoot_correctExtraKeyAndValue"; public static final String TEST_GET_LIBRARY_ROOT = "getLibraryRoot_correctExtraKeyAndValue";
public static final String TEST_GET_CHILDREN = "getChildren_correctMetadataExtras"; public static final String TEST_GET_CHILDREN = "getChildren_correctMetadataExtras";
public static final String TEST_GET_CHILDREN_WITH_NULL_LIST =
"onChildrenChanged_withNullChildrenListInLegacyService_convertedToSessionError";
private MediaBrowserServiceCompatConstants() {} private MediaBrowserServiceCompatConstants() {}
} }

View File

@ -38,6 +38,7 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID
import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_PLAYABLE_ITEM; import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_PLAYABLE_ITEM;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
@ -381,8 +382,20 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
} }
@Test @Test
public void getChildren_authErrorResult() throws Exception { public void getChildren_authErrorResult_correctPlaybackStateCompatUpdates() throws Exception {
String testParentId = PARENT_ID_AUTH_EXPIRED_ERROR; assertGetChildrenAuthenticationRequired(PARENT_ID_AUTH_EXPIRED_ERROR);
}
@Test
public void getChildren_authErrorResultDeprecated_correctPlaybackStateCompatUpdates()
throws Exception {
// Tests the deprecated approach where apps were expected to pass the error extras back as the
// extras of the LibraryParams of the LibraryResult because the SessionError type didn't then
// exist as part of the LibraryResult.
assertGetChildrenAuthenticationRequired(PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED);
}
public void assertGetChildrenAuthenticationRequired(String testParentId) throws Exception {
connectAndWait(/* rootHints= */ Bundle.EMPTY); connectAndWait(/* rootHints= */ Bundle.EMPTY);
CountDownLatch errorLatch = new CountDownLatch(1); CountDownLatch errorLatch = new CountDownLatch(1);
AtomicReference<String> parentIdRefOnError = new AtomicReference<>(); AtomicReference<String> parentIdRefOnError = new AtomicReference<>();
@ -399,6 +412,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
assertThat(errorLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(errorLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(parentIdRefOnError.get()).isEqualTo(testParentId); assertThat(parentIdRefOnError.get()).isEqualTo(testParentId);
assertThat(firstPlaybackStateCompatReported.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(lastReportedPlaybackStateCompat.getState()) assertThat(lastReportedPlaybackStateCompat.getState())
.isEqualTo(PlaybackStateCompat.STATE_ERROR); .isEqualTo(PlaybackStateCompat.STATE_ERROR);
assertThat( assertThat(
@ -456,12 +470,11 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
} }
@Test @Test
public void getChildren_nullResult() throws Exception { public void getChildren_errorLibraryResult() throws Exception {
String testParentId = PARENT_ID_ERROR; String testParentId = PARENT_ID_ERROR;
connectAndWait(/* rootHints= */ Bundle.EMPTY); connectAndWait(/* rootHints= */ Bundle.EMPTY);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
AtomicReference<String> parentIdRef = new AtomicReference<>(); AtomicReference<String> parentIdRef = new AtomicReference<>();
AtomicBoolean onChildrenLoadedWithBundleCalled = new AtomicBoolean();
browserCompat.subscribe( browserCompat.subscribe(
testParentId, testParentId,
@ -471,16 +484,10 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
parentIdRef.set(parentId); parentIdRef.set(parentId);
latch.countDown(); latch.countDown();
} }
@Override
public void onChildrenLoaded(String parentId, List<MediaItem> children, Bundle options) {
onChildrenLoadedWithBundleCalled.set(true);
}
}); });
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(parentIdRef.get()).isEqualTo(testParentId); assertThat(parentIdRef.get()).isEqualTo(testParentId);
assertThat(onChildrenLoadedWithBundleCalled.get()).isFalse();
} }
@Test @Test

View File

@ -58,16 +58,18 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
Context context; Context context;
TestHandler handler; TestHandler handler;
MediaBrowserCompat browserCompat; @Nullable MediaBrowserCompat browserCompat;
@Nullable MediaControllerCompat controllerCompat; @Nullable MediaControllerCompat controllerCompat;
TestConnectionCallback connectionCallback; TestConnectionCallback connectionCallback;
@Nullable PlaybackStateCompat lastReportedPlaybackStateCompat; @Nullable PlaybackStateCompat lastReportedPlaybackStateCompat;
@Nullable CountDownLatch firstPlaybackStateCompatReported;
@Before @Before
public void setUp() { public void setUp() {
context = ApplicationProvider.getApplicationContext(); context = ApplicationProvider.getApplicationContext();
handler = threadTestRule.getHandler(); handler = threadTestRule.getHandler();
connectionCallback = new TestConnectionCallback(); connectionCallback = new TestConnectionCallback();
firstPlaybackStateCompatReported = new CountDownLatch(1);
} }
@After @After
@ -131,13 +133,13 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
@Override @Override
public void onConnected() { public void onConnected() {
super.onConnected(); super.onConnected();
// Make browser's internal handler to be initialized with test thread.
controllerCompat = new MediaControllerCompat(context, browserCompat.getSessionToken()); controllerCompat = new MediaControllerCompat(context, browserCompat.getSessionToken());
controllerCompatCallback = controllerCompatCallback =
new MediaControllerCompat.Callback() { new MediaControllerCompat.Callback() {
@Override @Override
public void onPlaybackStateChanged(PlaybackStateCompat state) { public void onPlaybackStateChanged(PlaybackStateCompat state) {
lastReportedPlaybackStateCompat = state; lastReportedPlaybackStateCompat = state;
firstPlaybackStateCompatReported.countDown();
} }
}; };
controllerCompat.registerCallback(controllerCompatCallback); controllerCompat.registerCallback(controllerCompatCallback);

View File

@ -15,11 +15,11 @@
*/ */
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS; import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS; import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle; import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle;
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE; import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS; import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT; import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
@ -180,7 +180,7 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
.getHandler() .getHandler()
.postAndSync(() -> browser.getItem(mediaId)) .postAndSync(() -> browser.getItem(mediaId))
.get(TIMEOUT_MS, MILLISECONDS); .get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_ERROR_BAD_VALUE); assertThat(result.resultCode).isEqualTo(ERROR_BAD_VALUE);
assertThat(result.value).isNull(); assertThat(result.value).isNull();
} }
@ -246,14 +246,33 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
@Test @Test
public void getChildren_nullResult() throws Exception { public void getChildren_nullResult() throws Exception {
String parentId = MediaBrowserConstants.PARENT_ID_ERROR; String parentId = MediaBrowserConstants.PARENT_ID_ERROR;
MediaBrowser browser = createBrowser(); MediaBrowser browser = createBrowser();
LibraryResult<ImmutableList<MediaItem>> result = LibraryResult<ImmutableList<MediaItem>> result =
threadTestRule threadTestRule
.getHandler() .getHandler()
.postAndSync(() -> browser.getChildren(parentId, 1, 1, null)) .postAndSync(() -> browser.getChildren(parentId, 1, 1, null))
.get(TIMEOUT_MS, MILLISECONDS); .get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isNotEqualTo(RESULT_SUCCESS);
assertThat(result.resultCode).isLessThan(0);
assertThat(result.value).isNull();
}
@Test
public void getChildren_errorLibraryResult() throws Exception {
String parentId = MediaBrowserConstants.PARENT_ID_ERROR;
MediaBrowser browser = createBrowser();
LibraryResult<ImmutableList<MediaItem>> result =
threadTestRule
.getHandler()
.postAndSync(() -> browser.getChildren(parentId, 1, 1, null))
.get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isLessThan(0);
assertThat(result.sessionError.code).isLessThan(0);
assertThat(result.sessionError.message).isEqualTo("error message");
assertThat(result.sessionError.extras.getString("key")).isEqualTo("value");
assertThat(result.value).isNull(); assertThat(result.value).isNull();
} }

View File

@ -27,6 +27,7 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY; import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_CHILDREN; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_CHILDREN;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_CHILDREN_WITH_NULL_LIST;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_LIBRARY_ROOT; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_LIBRARY_ROOT;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE;
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
@ -151,6 +152,25 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
Thread.sleep(TIMEOUT_MS); Thread.sleep(TIMEOUT_MS);
} }
@Test
public void onChildrenChanged_withNullChildrenListInLegacyService_convertedToSessionError()
throws Exception {
String testParentId = TEST_GET_CHILDREN_WITH_NULL_LIST;
remoteService.setProxyForTest(TEST_GET_CHILDREN_WITH_NULL_LIST);
MediaBrowser browser = createBrowser(/* listener= */ null);
LibraryResult<Void> resultForSubscribe =
threadTestRule
.getHandler()
.postAndSync(() -> browser.subscribe(testParentId, null))
.get(TIMEOUT_MS, MILLISECONDS);
assertThat(resultForSubscribe.resultCode).isEqualTo(SessionError.ERROR_UNKNOWN);
assertThat(resultForSubscribe.sessionError.code).isEqualTo(SessionError.ERROR_UNKNOWN);
assertThat(resultForSubscribe.sessionError.message)
.isEqualTo(SessionError.DEFAULT_ERROR_MESSAGE);
}
@Test @Test
public void getLibraryRoot_correctExtraKeyAndValue() throws Exception { public void getLibraryRoot_correctExtraKeyAndValue() throws Exception {
remoteService.setProxyForTest(TEST_GET_LIBRARY_ROOT); remoteService.setProxyForTest(TEST_GET_LIBRARY_ROOT);

View File

@ -15,8 +15,9 @@
*/ */
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED; import static androidx.media3.session.SessionError.ERROR_INVALID_STATE;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SETUP_REQUIRED; import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
import static androidx.media3.session.SessionError.ERROR_SESSION_SETUP_REQUIRED;
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_1; import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_1;
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@ -166,7 +167,7 @@ public class MediaLibrarySessionCallbackTest {
@Nullable LibraryParams params) { @Nullable LibraryParams params) {
latch.countDown(); latch.countDown();
subscribedControllers.addAll(session.getSubscribedControllers(parentId)); subscribedControllers.addAll(session.getSubscribedControllers(parentId));
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED));
} }
}; };
MockMediaLibraryService service = new MockMediaLibraryService(); MockMediaLibraryService service = new MockMediaLibraryService();
@ -215,7 +216,7 @@ public class MediaLibrarySessionCallbackTest {
int resultCode = browser.subscribe(testParentId, testParams).resultCode; int resultCode = browser.subscribe(testParentId, testParams).resultCode;
assertThat(session.getSubscribedControllers(testParentId)).isEmpty(); assertThat(session.getSubscribedControllers(testParentId)).isEmpty();
assertThat(resultCode).isEqualTo(RESULT_ERROR_NOT_SUPPORTED); assertThat(resultCode).isEqualTo(ERROR_NOT_SUPPORTED);
assertThat(session.getSubscribedControllers(testParentId)).isEmpty(); assertThat(session.getSubscribedControllers(testParentId)).isEmpty();
} }
@ -234,7 +235,7 @@ public class MediaLibrarySessionCallbackTest {
public ListenableFuture<LibraryResult<MediaItem>> onGetItem( public ListenableFuture<LibraryResult<MediaItem>> onGetItem(
MediaLibrarySession session, ControllerInfo browser, String mediaId) { MediaLibrarySession session, ControllerInfo browser, String mediaId) {
return Futures.immediateFuture( return Futures.immediateFuture(
LibraryResult.ofError(RESULT_ERROR_SESSION_SETUP_REQUIRED)); LibraryResult.ofError(ERROR_SESSION_SETUP_REQUIRED));
} }
}) })
.setId("testOnSubscribe") .setId("testOnSubscribe")
@ -244,7 +245,7 @@ public class MediaLibrarySessionCallbackTest {
int resultCode = browser.subscribe(SUBSCRIBE_PARENT_ID_1, testParams).resultCode; int resultCode = browser.subscribe(SUBSCRIBE_PARENT_ID_1, testParams).resultCode;
assertThat(resultCode).isEqualTo(RESULT_ERROR_SESSION_SETUP_REQUIRED); assertThat(resultCode).isEqualTo(ERROR_SESSION_SETUP_REQUIRED);
assertThat(session.getSubscribedControllers(SUBSCRIBE_PARENT_ID_1)).isEmpty(); assertThat(session.getSubscribedControllers(SUBSCRIBE_PARENT_ID_1)).isEmpty();
} }
@ -413,7 +414,7 @@ public class MediaLibrarySessionCallbackTest {
/* params= */ null); /* params= */ null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(recentItem.resultCode).isEqualTo(LibraryResult.RESULT_ERROR_INVALID_STATE); assertThat(recentItem.resultCode).isEqualTo(ERROR_INVALID_STATE);
} }
@Test @Test

View File

@ -17,8 +17,8 @@ package androidx.media3.session;
import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE; import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
import static androidx.media3.session.MediaTestUtils.createMediaItem; import static androidx.media3.session.MediaTestUtils.createMediaItem;
import static androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE; import static androidx.media3.session.SessionError.ERROR_INVALID_STATE;
import static androidx.media3.session.SessionResult.RESULT_ERROR_PERMISSION_DENIED; import static androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED;
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED; import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
import static androidx.media3.session.SessionResult.RESULT_SUCCESS; import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
import static androidx.media3.test.session.common.CommonConstants.METADATA_MEDIA_URI; import static androidx.media3.test.session.common.CommonConstants.METADATA_MEDIA_URI;
@ -203,7 +203,7 @@ public class MediaSessionCallbackTest {
assertThat(layout).containsExactly(button1Disabled, button2).inOrder(); assertThat(layout).containsExactly(button1Disabled, button2).inOrder();
assertThat(remoteController.sendCustomCommand(button1.sessionCommand, Bundle.EMPTY).resultCode) assertThat(remoteController.sendCustomCommand(button1.sessionCommand, Bundle.EMPTY).resultCode)
.isEqualTo(RESULT_ERROR_PERMISSION_DENIED); .isEqualTo(ERROR_PERMISSION_DENIED);
assertThat(remoteController.sendCustomCommand(button2.sessionCommand, Bundle.EMPTY).resultCode) assertThat(remoteController.sendCustomCommand(button2.sessionCommand, Bundle.EMPTY).resultCode)
.isEqualTo(RESULT_SUCCESS); .isEqualTo(RESULT_SUCCESS);
} }
@ -369,7 +369,7 @@ public class MediaSessionCallbackTest {
assertThat(controllerInfo.isTrusted()).isFalse(); assertThat(controllerInfo.isTrusted()).isFalse();
commands.add(command); commands.add(command);
if (command == Player.COMMAND_PREPARE) { if (command == Player.COMMAND_PREPARE) {
return RESULT_ERROR_INVALID_STATE; return ERROR_INVALID_STATE;
} }
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }

View File

@ -23,7 +23,7 @@ import static androidx.media3.common.Player.STATE_ENDED;
import static androidx.media3.common.Player.STATE_IDLE; import static androidx.media3.common.Player.STATE_IDLE;
import static androidx.media3.common.Player.STATE_READY; import static androidx.media3.common.Player.STATE_READY;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE; import static androidx.media3.session.SessionError.ERROR_INVALID_STATE;
import static androidx.media3.session.SessionResult.RESULT_SUCCESS; import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME; import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
@ -1883,7 +1883,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
commands.add(command); commands.add(command);
if (command == COMMAND_PLAY_PAUSE) { if (command == COMMAND_PLAY_PAUSE) {
latchForPause.countDown(); latchForPause.countDown();
return RESULT_ERROR_INVALID_STATE; return ERROR_INVALID_STATE;
} }
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }

View File

@ -25,6 +25,7 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY; import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_CHILDREN; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_CHILDREN;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_CHILDREN_WITH_NULL_LIST;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_LIBRARY_ROOT; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_LIBRARY_ROOT;
import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE;
@ -250,6 +251,9 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
case TEST_GET_CHILDREN: case TEST_GET_CHILDREN:
setProxyForTestGetChildren_correctMetadataExtras(); setProxyForTestGetChildren_correctMetadataExtras();
break; break;
case TEST_GET_CHILDREN_WITH_NULL_LIST:
setProxyForTestOnChildrenChanged_withNullChildrenListInLegacyService_convertedToSessionError();
break;
default: default:
throw new IllegalArgumentException("Unknown testName: " + testName); throw new IllegalArgumentException("Unknown testName: " + testName);
} }
@ -298,6 +302,23 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
}); });
} }
private void
setProxyForTestOnChildrenChanged_withNullChildrenListInLegacyService_convertedToSessionError() {
setMediaBrowserServiceProxy(
new MockMediaBrowserServiceCompat.Proxy() {
@Override
public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
onLoadChildren(parentId, result, new Bundle());
}
@Override
public void onLoadChildren(
String parentId, Result<List<MediaItem>> result, Bundle bundle) {
result.sendResult(null);
}
});
}
private void setProxyForTestGetLibraryRoot_correctExtraKeyAndValue() { private void setProxyForTestGetLibraryRoot_correctExtraKeyAndValue() {
setMediaBrowserServiceProxy( setMediaBrowserServiceProxy(
new MockMediaBrowserServiceCompat.Proxy() { new MockMediaBrowserServiceCompat.Proxy() {

View File

@ -16,12 +16,13 @@
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS; import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT; import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT; import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT;
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY; import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
import static androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED;
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME; import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION; import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION;
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS; import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
@ -37,6 +38,7 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID
import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_PLAYABLE_ITEM; import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_PLAYABLE_ITEM;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
@ -319,7 +321,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null)); LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null));
default: // fall out default: // fall out
} }
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_BAD_VALUE));
} }
@Override @Override
@ -345,8 +347,12 @@ public class MockMediaLibraryService extends MediaLibraryService {
} }
return Futures.immediateFuture(LibraryResult.ofItemList(list, params)); return Futures.immediateFuture(LibraryResult.ofItemList(list, params));
} else if (Objects.equals(parentId, PARENT_ID_ERROR)) { } else if (Objects.equals(parentId, PARENT_ID_ERROR)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE)); Bundle errorBundle = new Bundle();
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)) { errorBundle.putString("key", "value");
return Futures.immediateFuture(
LibraryResult.ofError(new SessionError(ERROR_BAD_VALUE, "error message", errorBundle)));
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)
|| Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED)) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
Intent signInIntent = new Intent("action"); Intent signInIntent = new Intent("action");
int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0; int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0;
@ -357,12 +363,17 @@ public class MockMediaLibraryService extends MediaLibraryService {
bundle.putString( bundle.putString(
EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT, EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT,
PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL); PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL);
return Futures.immediateFuture( return Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)
LibraryResult.ofError( ? Futures.immediateFuture(
LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, LibraryResult.ofError(
new LibraryParams.Builder().setExtras(bundle).build())); new SessionError(ERROR_SESSION_AUTHENTICATION_EXPIRED, "error message", bundle),
new LibraryParams.Builder().build()))
: Futures.immediateFuture(
LibraryResult.ofError(
ERROR_SESSION_AUTHENTICATION_EXPIRED,
new LibraryParams.Builder().setExtras(bundle).build()));
} }
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE, params)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_BAD_VALUE, params));
} }
@Override @Override
@ -451,7 +462,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params)); return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
} else { } else {
// SEARCH_QUERY_ERROR will be handled here. // SEARCH_QUERY_ERROR will be handled here.
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE)); return Futures.immediateFuture(LibraryResult.ofError(ERROR_BAD_VALUE));
} }
} }
@ -474,7 +485,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
default: // fall out default: // fall out
} }
return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_ERROR_BAD_VALUE)); return Futures.immediateFuture(new SessionResult(ERROR_BAD_VALUE));
} }
private void assertLibraryParams(@Nullable LibraryParams params) { private void assertLibraryParams(@Nullable LibraryParams params) {