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:
parent
06e95ad2fb
commit
efff1ee2f1
@ -21,6 +21,9 @@
|
||||
* Add `MediaSession.Callback.onPlayerInteractionFinished` to inform
|
||||
sessions when a series of player interactions from a specific controller
|
||||
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:
|
||||
* Add customisation of various icons in `PlayerControlView` through xml
|
||||
attributes to allow different drawables per `PlayerView` instance,
|
||||
|
4
api.txt
4
api.txt
@ -1511,7 +1511,7 @@ package androidx.media3.session {
|
||||
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 {
|
||||
@ -1866,7 +1866,7 @@ package androidx.media3.session {
|
||||
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 {
|
||||
|
@ -27,6 +27,7 @@ import androidx.media3.session.MediaLibraryService
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
|
||||
import androidx.media3.session.SessionCommand
|
||||
import androidx.media3.session.SessionError
|
||||
import androidx.media3.session.SessionResult
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.util.concurrent.Futures
|
||||
@ -132,7 +133,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||
MediaItemTree.getItem(mediaId)?.let {
|
||||
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(
|
||||
@ -147,7 +148,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||
if (children.isNotEmpty()) {
|
||||
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(
|
||||
|
@ -54,21 +54,21 @@ public final class LibraryResult<V> implements Bundleable {
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
RESULT_SUCCESS,
|
||||
RESULT_ERROR_UNKNOWN,
|
||||
RESULT_ERROR_INVALID_STATE,
|
||||
RESULT_ERROR_BAD_VALUE,
|
||||
RESULT_ERROR_PERMISSION_DENIED,
|
||||
RESULT_ERROR_IO,
|
||||
RESULT_INFO_SKIPPED,
|
||||
RESULT_ERROR_SESSION_DISCONNECTED,
|
||||
RESULT_ERROR_NOT_SUPPORTED,
|
||||
RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
||||
RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
|
||||
RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
|
||||
RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
|
||||
RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
|
||||
RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED,
|
||||
RESULT_ERROR_SESSION_SETUP_REQUIRED
|
||||
SessionError.INFO_SKIPPED,
|
||||
SessionError.ERROR_UNKNOWN,
|
||||
SessionError.ERROR_INVALID_STATE,
|
||||
SessionError.ERROR_BAD_VALUE,
|
||||
SessionError.ERROR_PERMISSION_DENIED,
|
||||
SessionError.ERROR_IO,
|
||||
SessionError.ERROR_SESSION_DISCONNECTED,
|
||||
SessionError.ERROR_NOT_SUPPORTED,
|
||||
SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
||||
SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
|
||||
SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
|
||||
SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
|
||||
SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
|
||||
SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED,
|
||||
SessionError.ERROR_SESSION_SETUP_REQUIRED
|
||||
})
|
||||
public @interface Code {}
|
||||
|
||||
@ -82,56 +82,64 @@ public final class LibraryResult<V> implements Bundleable {
|
||||
*/
|
||||
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. */
|
||||
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
|
||||
* 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. */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
public static final int RESULT_ERROR_NOT_SUPPORTED = -6;
|
||||
|
||||
/** Result code representing that the command is skipped. */
|
||||
public static final int RESULT_INFO_SKIPPED = 1;
|
||||
public static final int RESULT_ERROR_NOT_SUPPORTED = SessionError.ERROR_NOT_SUPPORTED;
|
||||
|
||||
/** 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. */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
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
|
||||
* 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. */
|
||||
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. */
|
||||
public final @Code int resultCode;
|
||||
@ -153,12 +161,16 @@ public final class LibraryResult<V> implements Bundleable {
|
||||
/** The optional parameters. */
|
||||
@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}. */
|
||||
public static LibraryResult<Void> ofVoid() {
|
||||
return new LibraryResult<>(
|
||||
RESULT_SUCCESS,
|
||||
SystemClock.elapsedRealtime(),
|
||||
/* params= */ null,
|
||||
/* sessionError= */ null,
|
||||
/* value= */ null,
|
||||
VALUE_TYPE_VOID);
|
||||
}
|
||||
@ -169,7 +181,12 @@ public final class LibraryResult<V> implements Bundleable {
|
||||
*/
|
||||
public static LibraryResult<Void> ofVoid(@Nullable LibraryParams params) {
|
||||
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) {
|
||||
verifyMediaItem(item);
|
||||
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,
|
||||
SystemClock.elapsedRealtime(),
|
||||
params,
|
||||
/* sessionError= */ null,
|
||||
ImmutableList.copyOf(items),
|
||||
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>Note: This method will be deprecated when {@link #ofError(SessionError)} is promoted to
|
||||
* stable API status.
|
||||
*
|
||||
* @param errorCode The error code.
|
||||
*/
|
||||
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>Note: This method will be deprecated when {@link #ofError(SessionError, LibraryParams)} is
|
||||
* promoted to stable API status.
|
||||
*
|
||||
* @param errorCode The error code.
|
||||
* @param params The optional parameters to describe the error.
|
||||
*/
|
||||
public static <V> LibraryResult<V> ofError(@Code int errorCode, @Nullable LibraryParams params) {
|
||||
checkArgument(errorCode != RESULT_SUCCESS);
|
||||
return new LibraryResult<>(
|
||||
/* resultCode= */ errorCode,
|
||||
SystemClock.elapsedRealtime(),
|
||||
/* 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_TYPE_ERROR);
|
||||
}
|
||||
@ -244,11 +309,13 @@ public final class LibraryResult<V> implements Bundleable {
|
||||
@Code int resultCode,
|
||||
long completionTimeMs,
|
||||
@Nullable LibraryParams params,
|
||||
@Nullable SessionError sessionError,
|
||||
@Nullable V value,
|
||||
@ValueType int valueType) {
|
||||
this.resultCode = resultCode;
|
||||
this.completionTimeMs = completionTimeMs;
|
||||
this.params = params;
|
||||
this.sessionError = sessionError;
|
||||
this.value = value;
|
||||
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_VALUE = Util.intToStringMaxRadix(3);
|
||||
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.
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -278,6 +346,9 @@ public final class LibraryResult<V> implements Bundleable {
|
||||
if (params != null) {
|
||||
bundle.putBundle(FIELD_PARAMS, params.toBundle());
|
||||
}
|
||||
if (sessionError != null) {
|
||||
bundle.putBundle(FIELD_SESSION_ERROR, sessionError.toBundle());
|
||||
}
|
||||
bundle.putInt(FIELD_VALUE_TYPE, valueType);
|
||||
|
||||
if (value == null) {
|
||||
@ -391,6 +462,14 @@ public final class LibraryResult<V> implements Bundleable {
|
||||
@Nullable
|
||||
MediaLibraryService.LibraryParams params =
|
||||
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);
|
||||
@Nullable Object value;
|
||||
switch (valueType) {
|
||||
@ -416,7 +495,8 @@ public final class LibraryResult<V> implements Bundleable {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return new LibraryResult<>(resultCode, completionTimeMs, params, value, valueType);
|
||||
return new LibraryResult<>(
|
||||
resultCode, completionTimeMs, params, sessionError, value, valueType);
|
||||
}
|
||||
|
||||
@Documented
|
||||
|
@ -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.checkState;
|
||||
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.os.Bundle;
|
||||
@ -422,7 +422,7 @@ public final class MediaBrowser extends MediaController {
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -15,9 +15,6 @@
|
||||
*/
|
||||
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_ITEM;
|
||||
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_SUBSCRIBE;
|
||||
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.os.Bundle;
|
||||
@ -189,20 +189,20 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
IMediaSession iSession = getSessionInterfaceWithSessionCommandIfAble(commandCode);
|
||||
if (iSession != null) {
|
||||
SequencedFuture<LibraryResult<V>> result =
|
||||
sequencedFutureManager.createSequencedFuture(LibraryResult.ofError(RESULT_INFO_SKIPPED));
|
||||
sequencedFutureManager.createSequencedFuture(LibraryResult.ofError(INFO_SKIPPED));
|
||||
try {
|
||||
task.run(iSession, result.getSequenceNumber());
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
|
||||
sequencedFutureManager.setFutureResult(
|
||||
result.getSequenceNumber(), LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED));
|
||||
result.getSequenceNumber(), LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Don't create Future with SequencedFutureManager.
|
||||
// Otherwise session would receive discontinued sequence number, and it would make
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,10 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
|
||||
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_ERROR_UNKNOWN;
|
||||
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
|
||||
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 android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@ -92,7 +92,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
public ListenableFuture<LibraryResult<MediaItem>> getLibraryRoot(@Nullable LibraryParams params) {
|
||||
if (!getInstance()
|
||||
.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();
|
||||
MediaBrowserCompat browserCompat = getBrowserCompat(params);
|
||||
@ -117,11 +117,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
public ListenableFuture<LibraryResult<Void>> subscribe(
|
||||
String parentId, @Nullable LibraryParams params) {
|
||||
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();
|
||||
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();
|
||||
SubscribeCallback callback = new SubscribeCallback(future);
|
||||
@ -138,17 +138,17 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<Void>> unsubscribe(String parentId) {
|
||||
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();
|
||||
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
|
||||
// callback for getChildren.
|
||||
List<SubscribeCallback> list = subscribeCallbacks.get(parentId);
|
||||
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++) {
|
||||
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) {
|
||||
if (!getInstance()
|
||||
.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();
|
||||
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();
|
||||
@ -179,11 +179,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<MediaItem>> getItem(String mediaId) {
|
||||
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();
|
||||
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();
|
||||
browserCompat.getItem(
|
||||
@ -196,13 +196,13 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
LibraryResult.ofItem(
|
||||
LegacyConversions.convertToMediaItem(item), /* params= */ null));
|
||||
} else {
|
||||
result.set(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||
result.set(LibraryResult.ofError(ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String itemId) {
|
||||
result.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN));
|
||||
result.set(LibraryResult.ofError(ERROR_UNKNOWN));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
@ -212,11 +212,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
public ListenableFuture<LibraryResult<Void>> search(
|
||||
String query, @Nullable LibraryParams params) {
|
||||
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();
|
||||
if (browserCompat == null) {
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED));
|
||||
return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
|
||||
}
|
||||
browserCompat.search(
|
||||
query,
|
||||
@ -259,11 +259,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
String query, int page, int pageSize, @Nullable LibraryParams params) {
|
||||
if (!getInstance()
|
||||
.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();
|
||||
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();
|
||||
@ -285,7 +285,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
|
||||
@Override
|
||||
public void onError(String query, @Nullable Bundle extrasSent) {
|
||||
future.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN));
|
||||
future.set(LibraryResult.ofError(ERROR_UNKNOWN));
|
||||
}
|
||||
});
|
||||
return future;
|
||||
@ -339,7 +339,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
MediaBrowserCompat browserCompat = browserCompats.get(params);
|
||||
if (browserCompat == null) {
|
||||
// Shouldn't be happen. Internal error?
|
||||
result.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN));
|
||||
result.set(LibraryResult.ofError(ERROR_UNKNOWN));
|
||||
} else {
|
||||
result.set(
|
||||
LibraryResult.ofItem(
|
||||
@ -356,7 +356,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
@Override
|
||||
public void onConnectionFailed() {
|
||||
// Unknown extra field.
|
||||
result.set(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||
result.set(LibraryResult.ofError(ERROR_BAD_VALUE));
|
||||
release();
|
||||
}
|
||||
}
|
||||
@ -396,7 +396,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
private void onErrorInternal() {
|
||||
// Don't need to unsubscribe here, because MediaBrowserServiceCompat can notify children
|
||||
// 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(
|
||||
@ -468,7 +468,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
private void onErrorInternal() {
|
||||
future.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN));
|
||||
future.set(LibraryResult.ofError(ERROR_UNKNOWN));
|
||||
}
|
||||
|
||||
private void onChildrenLoadedInternal(
|
||||
@ -479,14 +479,14 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
MediaBrowserCompat browserCompat = getBrowserCompat();
|
||||
if (browserCompat == null) {
|
||||
future.set(LibraryResult.ofError(RESULT_ERROR_SESSION_DISCONNECTED));
|
||||
future.set(LibraryResult.ofError(ERROR_SESSION_DISCONNECTED));
|
||||
return;
|
||||
}
|
||||
browserCompat.unsubscribe(this.parentId, GetChildrenCallback.this);
|
||||
|
||||
if (children == null) {
|
||||
// list are non-Null, so it must be internal error.
|
||||
future.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN));
|
||||
future.set(LibraryResult.ofError(ERROR_UNKNOWN));
|
||||
} else {
|
||||
// Don't set extra here, because 'extra' have different meanings between old
|
||||
// API and new API as follows.
|
||||
|
@ -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.checkState;
|
||||
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.content.Context;
|
||||
@ -377,7 +379,7 @@ public class MediaController implements Player {
|
||||
*/
|
||||
default ListenableFuture<SessionResult> onSetCustomLayout(
|
||||
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)}.
|
||||
*
|
||||
* <p>The default implementation returns {@link ListenableFuture} of {@link
|
||||
* SessionResult#RESULT_ERROR_NOT_SUPPORTED}.
|
||||
* SessionError#ERROR_NOT_SUPPORTED}.
|
||||
*
|
||||
* @param controller The controller.
|
||||
* @param command The custom command.
|
||||
@ -425,7 +427,7 @@ public class MediaController implements Player {
|
||||
*/
|
||||
default ListenableFuture<SessionResult> onCustomCommand(
|
||||
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() {
|
||||
return Futures.immediateFuture(
|
||||
new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED));
|
||||
return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
|
||||
}
|
||||
|
||||
/* package */ final void runOnApplicationLooper(Runnable runnable) {
|
||||
|
@ -23,6 +23,9 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.usToMs;
|
||||
import static androidx.media3.session.MediaUtils.calculateBufferedPercentage;
|
||||
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.min;
|
||||
|
||||
@ -327,8 +330,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
int sequenceNumber =
|
||||
((SequencedFutureManager.SequencedFuture<SessionResult>) future).getSequenceNumber();
|
||||
pendingMaskingSequencedFutureNumbers.remove(sequenceNumber);
|
||||
sequencedFutureManager.setFutureResult(
|
||||
sequenceNumber, new SessionResult(SessionResult.RESULT_ERROR_UNKNOWN));
|
||||
sequencedFutureManager.setFutureResult(sequenceNumber, new SessionResult(ERROR_UNKNOWN));
|
||||
}
|
||||
Log.w(TAG, "Synchronous command takes too long on the session side.", e);
|
||||
// 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);
|
||||
pendingMaskingSequencedFutureNumbers.remove(sequenceNumber);
|
||||
sequencedFutureManager.setFutureResult(
|
||||
sequenceNumber, new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED));
|
||||
sequenceNumber, new SessionResult(ERROR_SESSION_DISCONNECTED));
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Don't create Future with SequencedFutureManager.
|
||||
// Otherwise session would receive discontinued sequence number, and it would make
|
||||
// future work item 'keeping call sequence when session execute commands' impossible.
|
||||
return Futures.immediateFuture(
|
||||
new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
|
||||
return Futures.immediateFuture(new SessionResult(ERROR_PERMISSION_DENIED));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2662,7 +2663,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
result = new SessionResult(SessionResult.RESULT_INFO_SKIPPED);
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.w(TAG, "Session operation failed", e);
|
||||
result = new SessionResult(SessionResult.RESULT_ERROR_UNKNOWN);
|
||||
result = new SessionResult(ERROR_UNKNOWN);
|
||||
}
|
||||
sendControllerResult(seq, result);
|
||||
},
|
||||
|
@ -18,9 +18,10 @@ package androidx.media3.session;
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotEmpty;
|
||||
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.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.content.Context;
|
||||
@ -158,7 +159,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
*/
|
||||
default ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(
|
||||
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(
|
||||
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 = 1) int pageSize,
|
||||
@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.
|
||||
return Futures.immediateFuture(
|
||||
LibraryResult.ofError(
|
||||
result.resultCode != RESULT_SUCCESS
|
||||
? result.resultCode
|
||||
: LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
result.resultCode != RESULT_SUCCESS ? result.resultCode : ERROR_BAD_VALUE));
|
||||
}
|
||||
if (browser.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) {
|
||||
// For legacy browsers, android.service.media.MediaBrowserService already calls
|
||||
@ -343,7 +342,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
ControllerInfo browser,
|
||||
String query,
|
||||
@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 = 1) int pageSize,
|
||||
@Nullable LibraryParams params) {
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED));
|
||||
return Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,14 @@ package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
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.MediaConstants.ERROR_CODE_AUTHENTICATION_EXPIRED_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.min;
|
||||
|
||||
@ -122,7 +125,7 @@ import java.util.concurrent.Future;
|
||||
if (params != null && params.isRecent && isSystemUiController(browser)) {
|
||||
// Advertise support for playback resumption, if enabled.
|
||||
return !canResumePlaybackOnStart()
|
||||
? Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED))
|
||||
? Futures.immediateFuture(LibraryResult.ofError(ERROR_NOT_SUPPORTED))
|
||||
: Futures.immediateFuture(
|
||||
LibraryResult.ofItem(
|
||||
new MediaItem.Builder()
|
||||
@ -156,7 +159,7 @@ import java.util.concurrent.Future;
|
||||
@Nullable LibraryParams params) {
|
||||
if (Objects.equals(parentId, RECENT_LIBRARY_ROOT_MEDIA_ID)) {
|
||||
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
|
||||
// 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) {
|
||||
PlayerWrapper playerWrapper = getPlayerWrapper();
|
||||
if (result.resultCode == RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED
|
||||
&& result.params != null
|
||||
&& result.params.extras.containsKey(EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT)) {
|
||||
// Mapping this error to the legacy error state provides backwards compatibility for the
|
||||
// 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) {
|
||||
if (setLegacyErrorState(result)) {
|
||||
// Sync playback state if legacy error state changed.
|
||||
getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat());
|
||||
} else if (playerWrapper.getLegacyStatusCode() != STATUS_CODE_SUCCESS_COMPAT) {
|
||||
playerWrapper.clearLegacyErrorStatus();
|
||||
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
|
||||
private static <T> T tryGetFutureResult(Future<T> future) {
|
||||
checkState(future.isDone());
|
||||
@ -436,8 +454,7 @@ import java.util.concurrent.Future;
|
||||
@Override
|
||||
public void onSuccess(MediaSession.MediaItemsWithStartPosition playlist) {
|
||||
if (playlist.mediaItems.isEmpty()) {
|
||||
settableFuture.set(
|
||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_INVALID_STATE, params));
|
||||
settableFuture.set(LibraryResult.ofError(ERROR_INVALID_STATE, params));
|
||||
return;
|
||||
}
|
||||
int sanitizedStartIndex =
|
||||
@ -449,7 +466,7 @@ import java.util.concurrent.Future;
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
@ -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.checkNotNull;
|
||||
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 android.app.PendingIntent;
|
||||
@ -1363,7 +1363,7 @@ public class MediaSession {
|
||||
*/
|
||||
default ListenableFuture<SessionResult> onSetRating(
|
||||
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(
|
||||
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,
|
||||
SessionCommand customCommand,
|
||||
Bundle args) {
|
||||
return Futures.immediateFuture(new SessionResult(RESULT_ERROR_NOT_SUPPORTED));
|
||||
return Futures.immediateFuture(new SessionResult(ERROR_NOT_SUPPORTED));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.Util.postOrRun;
|
||||
import static androidx.media3.session.MediaSessionStub.UNKNOWN_SEQUENCE_NUMBER;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN;
|
||||
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
|
||||
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.content.ComponentName;
|
||||
@ -113,7 +113,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
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();
|
||||
|
||||
@ -1090,7 +1090,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
seq = ((SequencedFuture<SessionResult>) future).getSequenceNumber();
|
||||
} else {
|
||||
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.
|
||||
seq = 0;
|
||||
@ -1104,7 +1104,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
return future;
|
||||
} catch (DeadObjectException e) {
|
||||
onDeadObjectException(controller);
|
||||
return Futures.immediateFuture(new SessionResult(RESULT_ERROR_SESSION_DISCONNECTED));
|
||||
return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
|
||||
} catch (RemoteException e) {
|
||||
// Currently it's TransactionTooLargeException or DeadSystemException.
|
||||
// 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.
|
||||
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. */
|
||||
|
@ -37,7 +37,7 @@ import static androidx.media3.common.util.Util.castNonNull;
|
||||
import static androidx.media3.common.util.Util.postOrRun;
|
||||
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.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_SUCCESS;
|
||||
|
||||
@ -936,7 +936,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
result = new SessionResult(RESULT_INFO_SKIPPED);
|
||||
} catch (ExecutionException | InterruptedException 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);
|
||||
},
|
||||
|
@ -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_UNSUBSCRIBE;
|
||||
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.os.Binder;
|
||||
@ -192,8 +197,8 @@ import java.util.concurrent.ExecutionException;
|
||||
result =
|
||||
new SessionResult(
|
||||
exception.getCause() instanceof UnsupportedOperationException
|
||||
? SessionResult.RESULT_ERROR_NOT_SUPPORTED
|
||||
: SessionResult.RESULT_ERROR_UNKNOWN);
|
||||
? ERROR_NOT_SUPPORTED
|
||||
: ERROR_UNKNOWN);
|
||||
}
|
||||
sendSessionResult(controller, sequenceNumber, result);
|
||||
});
|
||||
@ -205,8 +210,7 @@ import java.util.concurrent.ExecutionException;
|
||||
MediaItemPlayerTask mediaItemPlayerTask) {
|
||||
return (sessionImpl, controller, sequenceNumber) -> {
|
||||
if (sessionImpl.isReleased()) {
|
||||
return Futures.immediateFuture(
|
||||
new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED));
|
||||
return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
|
||||
}
|
||||
return transformFutureAsync(
|
||||
mediaItemsTask.run(sessionImpl, controller, sequenceNumber),
|
||||
@ -231,8 +235,7 @@ import java.util.concurrent.ExecutionException;
|
||||
MediaItemsWithStartPositionPlayerTask mediaItemPlayerTask) {
|
||||
return (sessionImpl, controller, sequenceNumber) -> {
|
||||
if (sessionImpl.isReleased()) {
|
||||
return Futures.immediateFuture(
|
||||
new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED));
|
||||
return Futures.immediateFuture(new SessionResult(ERROR_SESSION_DISCONNECTED));
|
||||
}
|
||||
return transformFutureAsync(
|
||||
mediaItemsTask.run(sessionImpl, controller, sequenceNumber),
|
||||
@ -275,10 +278,10 @@ import java.util.concurrent.ExecutionException;
|
||||
result = checkNotNull(future.get(), "LibraryResult must not be null");
|
||||
} catch (CancellationException e) {
|
||||
Log.w(TAG, "Library operation cancelled", e);
|
||||
result = LibraryResult.ofError(LibraryResult.RESULT_INFO_SKIPPED);
|
||||
result = LibraryResult.ofError(INFO_SKIPPED);
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.w(TAG, "Library operation failed", e);
|
||||
result = LibraryResult.ofError(LibraryResult.RESULT_ERROR_UNKNOWN);
|
||||
result = LibraryResult.ofError(ERROR_UNKNOWN);
|
||||
}
|
||||
sendLibraryResult(controller, sequenceNumber, result);
|
||||
});
|
||||
@ -314,9 +317,7 @@ import java.util.concurrent.ExecutionException;
|
||||
() -> {
|
||||
if (!connectedControllersManager.isPlayerCommandAvailable(controller, command)) {
|
||||
sendSessionResult(
|
||||
controller,
|
||||
sequenceNumber,
|
||||
new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
|
||||
controller, sequenceNumber, new SessionResult(ERROR_PERMISSION_DENIED));
|
||||
return;
|
||||
}
|
||||
@SessionResult.Code
|
||||
@ -393,17 +394,13 @@ import java.util.concurrent.ExecutionException;
|
||||
if (!connectedControllersManager.isSessionCommandAvailable(
|
||||
controller, sessionCommand)) {
|
||||
sendSessionResult(
|
||||
controller,
|
||||
sequenceNumber,
|
||||
new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
|
||||
controller, sequenceNumber, new SessionResult(ERROR_PERMISSION_DENIED));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!connectedControllersManager.isSessionCommandAvailable(controller, commandCode)) {
|
||||
sendSessionResult(
|
||||
controller,
|
||||
sequenceNumber,
|
||||
new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
|
||||
controller, sequenceNumber, new SessionResult(ERROR_PERMISSION_DENIED));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ import java.util.List;
|
||||
*/
|
||||
/* 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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
@ -57,21 +58,21 @@ public final class SessionResult implements Bundleable {
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
RESULT_SUCCESS,
|
||||
RESULT_ERROR_UNKNOWN,
|
||||
RESULT_ERROR_INVALID_STATE,
|
||||
RESULT_ERROR_BAD_VALUE,
|
||||
RESULT_ERROR_PERMISSION_DENIED,
|
||||
RESULT_ERROR_IO,
|
||||
RESULT_INFO_SKIPPED,
|
||||
RESULT_ERROR_SESSION_DISCONNECTED,
|
||||
RESULT_ERROR_NOT_SUPPORTED,
|
||||
RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
||||
RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
|
||||
RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
|
||||
RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
|
||||
RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
|
||||
RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED,
|
||||
RESULT_ERROR_SESSION_SETUP_REQUIRED
|
||||
SessionError.INFO_SKIPPED,
|
||||
SessionError.ERROR_UNKNOWN,
|
||||
SessionError.ERROR_INVALID_STATE,
|
||||
SessionError.ERROR_BAD_VALUE,
|
||||
SessionError.ERROR_PERMISSION_DENIED,
|
||||
SessionError.ERROR_IO,
|
||||
SessionError.ERROR_SESSION_DISCONNECTED,
|
||||
SessionError.ERROR_NOT_SUPPORTED,
|
||||
SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
||||
SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED,
|
||||
SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT,
|
||||
SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED,
|
||||
SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION,
|
||||
SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED,
|
||||
SessionError.ERROR_SESSION_SETUP_REQUIRED
|
||||
})
|
||||
public @interface Code {}
|
||||
|
||||
@ -85,56 +86,64 @@ public final class SessionResult implements Bundleable {
|
||||
*/
|
||||
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. */
|
||||
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
|
||||
* 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. */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
public static final int RESULT_ERROR_NOT_SUPPORTED = -6;
|
||||
|
||||
/** Result code representing that the command is skipped. */
|
||||
public static final int RESULT_INFO_SKIPPED = 1;
|
||||
public static final int RESULT_ERROR_NOT_SUPPORTED = SessionError.ERROR_NOT_SUPPORTED;
|
||||
|
||||
/** 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. */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
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
|
||||
* 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. */
|
||||
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. */
|
||||
public final @Code int resultCode;
|
||||
@ -148,9 +157,15 @@ public final class SessionResult implements Bundleable {
|
||||
*/
|
||||
public final long completionTimeMs;
|
||||
|
||||
/** The optional session error. */
|
||||
@UnstableApi @Nullable public final SessionError sessionError;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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}.
|
||||
*
|
||||
* <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 extras The extra {@link Bundle}.
|
||||
*/
|
||||
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.extras = new Bundle(extras);
|
||||
this.completionTimeMs = completionTimeMs;
|
||||
this.sessionError =
|
||||
sessionError == null && resultCode < 0
|
||||
? new SessionError(resultCode, SessionError.DEFAULT_ERROR_MESSAGE)
|
||||
: sessionError;
|
||||
}
|
||||
|
||||
// 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_EXTRAS = Util.intToStringMaxRadix(1);
|
||||
private static final String FIELD_COMPLETION_TIME_MS = Util.intToStringMaxRadix(2);
|
||||
private static final String FIELD_SESSION_ERROR = Util.intToStringMaxRadix(3);
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
@ -186,6 +249,9 @@ public final class SessionResult implements Bundleable {
|
||||
bundle.putInt(FIELD_RESULT_CODE, resultCode);
|
||||
bundle.putBundle(FIELD_EXTRAS, extras);
|
||||
bundle.putLong(FIELD_COMPLETION_TIME_MS, completionTimeMs);
|
||||
if (sessionError != null) {
|
||||
bundle.putBundle(FIELD_SESSION_ERROR, sessionError.toBundle());
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@ -202,10 +268,21 @@ public final class SessionResult implements Bundleable {
|
||||
/** Restores a {@code SessionResult} from a {@link Bundle}. */
|
||||
@UnstableApi
|
||||
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);
|
||||
long completionTimeMs =
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
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 org.junit.Assert.assertThrows;
|
||||
|
||||
@ -99,8 +99,7 @@ public class LibraryResultTest {
|
||||
|
||||
@Test
|
||||
public void toBundle_errorResultThatWasUnbundledAsAnUnknownType_noException() {
|
||||
LibraryResult<ImmutableList<Error>> libraryResult =
|
||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_NOT_SUPPORTED);
|
||||
LibraryResult<ImmutableList<Error>> libraryResult = LibraryResult.ofError(ERROR_NOT_SUPPORTED);
|
||||
Bundle errorLibraryResultBundle = libraryResult.toBundle();
|
||||
LibraryResult<?> libraryResultFromUntyped =
|
||||
LibraryResult.fromUnknownBundle(errorLibraryResultBundle);
|
||||
@ -109,13 +108,12 @@ public class LibraryResultTest {
|
||||
|
||||
assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).value).isNull();
|
||||
assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).resultCode)
|
||||
.isEqualTo(RESULT_ERROR_NOT_SUPPORTED);
|
||||
.isEqualTo(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBundle_voidResultThatWasUnbundledAsAnUnknownType_noException() {
|
||||
LibraryResult<ImmutableList<Error>> libraryResult =
|
||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_NOT_SUPPORTED);
|
||||
LibraryResult<ImmutableList<Error>> libraryResult = LibraryResult.ofError(ERROR_NOT_SUPPORTED);
|
||||
Bundle errorLibraryResultBundle = libraryResult.toBundle();
|
||||
LibraryResult<?> libraryResultFromUntyped =
|
||||
LibraryResult.fromUnknownBundle(errorLibraryResultBundle);
|
||||
@ -124,6 +122,27 @@ public class LibraryResultTest {
|
||||
|
||||
assertThat(LibraryResult.fromUnknownBundle(bundleOfUntyped).value).isNull();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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_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_DEPRECATED =
|
||||
"parent_auth_expired_error_deprecated";
|
||||
public static final String PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL =
|
||||
"parent_auth_expired_error_label";
|
||||
|
||||
|
@ -23,6 +23,8 @@ public class MediaBrowserServiceCompatConstants {
|
||||
"testOnChildrenChanged_subscribeAndUnsubscribe";
|
||||
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_WITH_NULL_LIST =
|
||||
"onChildrenChanged_withNullChildrenListInLegacyService_convertedToSessionError";
|
||||
|
||||
private MediaBrowserServiceCompatConstants() {}
|
||||
}
|
||||
|
@ -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.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_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_ERROR;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
||||
@ -381,8 +382,20 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChildren_authErrorResult() throws Exception {
|
||||
String testParentId = PARENT_ID_AUTH_EXPIRED_ERROR;
|
||||
public void getChildren_authErrorResult_correctPlaybackStateCompatUpdates() throws Exception {
|
||||
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);
|
||||
CountDownLatch errorLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> parentIdRefOnError = new AtomicReference<>();
|
||||
@ -399,6 +412,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
|
||||
|
||||
assertThat(errorLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(parentIdRefOnError.get()).isEqualTo(testParentId);
|
||||
assertThat(firstPlaybackStateCompatReported.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(lastReportedPlaybackStateCompat.getState())
|
||||
.isEqualTo(PlaybackStateCompat.STATE_ERROR);
|
||||
assertThat(
|
||||
@ -456,12 +470,11 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChildren_nullResult() throws Exception {
|
||||
public void getChildren_errorLibraryResult() throws Exception {
|
||||
String testParentId = PARENT_ID_ERROR;
|
||||
connectAndWait(/* rootHints= */ Bundle.EMPTY);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<String> parentIdRef = new AtomicReference<>();
|
||||
AtomicBoolean onChildrenLoadedWithBundleCalled = new AtomicBoolean();
|
||||
|
||||
browserCompat.subscribe(
|
||||
testParentId,
|
||||
@ -471,16 +484,10 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
|
||||
parentIdRef.set(parentId);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildrenLoaded(String parentId, List<MediaItem> children, Bundle options) {
|
||||
onChildrenLoadedWithBundleCalled.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(parentIdRef.get()).isEqualTo(testParentId);
|
||||
assertThat(onChildrenLoadedWithBundleCalled.get()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -58,16 +58,18 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
|
||||
|
||||
Context context;
|
||||
TestHandler handler;
|
||||
MediaBrowserCompat browserCompat;
|
||||
@Nullable MediaBrowserCompat browserCompat;
|
||||
@Nullable MediaControllerCompat controllerCompat;
|
||||
TestConnectionCallback connectionCallback;
|
||||
@Nullable PlaybackStateCompat lastReportedPlaybackStateCompat;
|
||||
@Nullable CountDownLatch firstPlaybackStateCompatReported;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
handler = threadTestRule.getHandler();
|
||||
connectionCallback = new TestConnectionCallback();
|
||||
firstPlaybackStateCompatReported = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@After
|
||||
@ -131,13 +133,13 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
|
||||
@Override
|
||||
public void onConnected() {
|
||||
super.onConnected();
|
||||
// Make browser's internal handler to be initialized with test thread.
|
||||
controllerCompat = new MediaControllerCompat(context, browserCompat.getSessionToken());
|
||||
controllerCompatCallback =
|
||||
new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackStateCompat state) {
|
||||
lastReportedPlaybackStateCompat = state;
|
||||
firstPlaybackStateCompatReported.countDown();
|
||||
}
|
||||
};
|
||||
controllerCompat.registerCallback(controllerCompatCallback);
|
||||
|
@ -15,11 +15,11 @@
|
||||
*/
|
||||
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.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||
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.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
|
||||
@ -180,7 +180,7 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
||||
.getHandler()
|
||||
.postAndSync(() -> browser.getItem(mediaId))
|
||||
.get(TIMEOUT_MS, MILLISECONDS);
|
||||
assertThat(result.resultCode).isEqualTo(RESULT_ERROR_BAD_VALUE);
|
||||
assertThat(result.resultCode).isEqualTo(ERROR_BAD_VALUE);
|
||||
assertThat(result.value).isNull();
|
||||
}
|
||||
|
||||
@ -246,14 +246,33 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
||||
@Test
|
||||
public void getChildren_nullResult() 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).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();
|
||||
}
|
||||
|
||||
|
@ -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.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_WITH_NULL_LIST;
|
||||
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.TestUtils.TIMEOUT_MS;
|
||||
@ -151,6 +152,25 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
|
||||
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
|
||||
public void getLibraryRoot_correctExtraKeyAndValue() throws Exception {
|
||||
remoteService.setProxyForTest(TEST_GET_LIBRARY_ROOT);
|
||||
|
@ -15,8 +15,9 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SETUP_REQUIRED;
|
||||
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_SETUP_REQUIRED;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_1;
|
||||
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
@ -166,7 +167,7 @@ public class MediaLibrarySessionCallbackTest {
|
||||
@Nullable LibraryParams params) {
|
||||
latch.countDown();
|
||||
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();
|
||||
@ -215,7 +216,7 @@ public class MediaLibrarySessionCallbackTest {
|
||||
int resultCode = browser.subscribe(testParentId, testParams).resultCode;
|
||||
|
||||
assertThat(session.getSubscribedControllers(testParentId)).isEmpty();
|
||||
assertThat(resultCode).isEqualTo(RESULT_ERROR_NOT_SUPPORTED);
|
||||
assertThat(resultCode).isEqualTo(ERROR_NOT_SUPPORTED);
|
||||
assertThat(session.getSubscribedControllers(testParentId)).isEmpty();
|
||||
}
|
||||
|
||||
@ -234,7 +235,7 @@ public class MediaLibrarySessionCallbackTest {
|
||||
public ListenableFuture<LibraryResult<MediaItem>> onGetItem(
|
||||
MediaLibrarySession session, ControllerInfo browser, String mediaId) {
|
||||
return Futures.immediateFuture(
|
||||
LibraryResult.ofError(RESULT_ERROR_SESSION_SETUP_REQUIRED));
|
||||
LibraryResult.ofError(ERROR_SESSION_SETUP_REQUIRED));
|
||||
}
|
||||
})
|
||||
.setId("testOnSubscribe")
|
||||
@ -244,7 +245,7 @@ public class MediaLibrarySessionCallbackTest {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -413,7 +414,7 @@ public class MediaLibrarySessionCallbackTest {
|
||||
/* params= */ null);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(recentItem.resultCode).isEqualTo(LibraryResult.RESULT_ERROR_INVALID_STATE);
|
||||
assertThat(recentItem.resultCode).isEqualTo(ERROR_INVALID_STATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -17,8 +17,8 @@ package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
|
||||
import static androidx.media3.session.MediaTestUtils.createMediaItem;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_PERMISSION_DENIED;
|
||||
import static androidx.media3.session.SessionError.ERROR_INVALID_STATE;
|
||||
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_SUCCESS;
|
||||
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(remoteController.sendCustomCommand(button1.sessionCommand, Bundle.EMPTY).resultCode)
|
||||
.isEqualTo(RESULT_ERROR_PERMISSION_DENIED);
|
||||
.isEqualTo(ERROR_PERMISSION_DENIED);
|
||||
assertThat(remoteController.sendCustomCommand(button2.sessionCommand, Bundle.EMPTY).resultCode)
|
||||
.isEqualTo(RESULT_SUCCESS);
|
||||
}
|
||||
@ -369,7 +369,7 @@ public class MediaSessionCallbackTest {
|
||||
assertThat(controllerInfo.isTrusted()).isFalse();
|
||||
commands.add(command);
|
||||
if (command == Player.COMMAND_PREPARE) {
|
||||
return RESULT_ERROR_INVALID_STATE;
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
@ -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_READY;
|
||||
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.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
||||
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
|
||||
@ -1883,7 +1883,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
commands.add(command);
|
||||
if (command == COMMAND_PLAY_PAUSE) {
|
||||
latchForPause.countDown();
|
||||
return RESULT_ERROR_INVALID_STATE;
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
@ -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.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_WITH_NULL_LIST;
|
||||
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;
|
||||
|
||||
@ -250,6 +251,9 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
|
||||
case TEST_GET_CHILDREN:
|
||||
setProxyForTestGetChildren_correctMetadataExtras();
|
||||
break;
|
||||
case TEST_GET_CHILDREN_WITH_NULL_LIST:
|
||||
setProxyForTestOnChildrenChanged_withNullChildrenListInLegacyService_convertedToSessionError();
|
||||
break;
|
||||
default:
|
||||
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() {
|
||||
setMediaBrowserServiceProxy(
|
||||
new MockMediaBrowserServiceCompat.Proxy() {
|
||||
|
@ -16,12 +16,13 @@
|
||||
package androidx.media3.session;
|
||||
|
||||
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_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_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||
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.MediaBrowserConstants.CUSTOM_ACTION;
|
||||
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.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_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_ERROR;
|
||||
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));
|
||||
default: // fall out
|
||||
}
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||
return Futures.immediateFuture(LibraryResult.ofError(ERROR_BAD_VALUE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -345,8 +347,12 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
}
|
||||
return Futures.immediateFuture(LibraryResult.ofItemList(list, params));
|
||||
} else if (Objects.equals(parentId, PARENT_ID_ERROR)) {
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)) {
|
||||
Bundle errorBundle = new Bundle();
|
||||
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();
|
||||
Intent signInIntent = new Intent("action");
|
||||
int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||
@ -357,12 +363,17 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
bundle.putString(
|
||||
EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT,
|
||||
PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL);
|
||||
return Futures.immediateFuture(
|
||||
return Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)
|
||||
? Futures.immediateFuture(
|
||||
LibraryResult.ofError(
|
||||
LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
||||
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
|
||||
@ -451,7 +462,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
|
||||
} else {
|
||||
// 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));
|
||||
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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user