mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Improve automatic error replication for legacy browsers
This change extends the error replication to a given set of error codes (not only authentication error), but only replicates an error if the caller of the service `Callback` is a legacy controller. It also makes error replication configurable so that apps can opt-out and report errors manually instead, or define the error codes for which replication is enabled. The change also removes the restriction of `sendError` only being available for Media3 controllers. Instead, sending an error to a legacy controller updates the platform playback state in the same way as sending the error to the media notification controller. #cherrypick PiperOrigin-RevId: 648399237
This commit is contained in:
parent
6bf2461f80
commit
70c063905c
12 changed files with 384 additions and 93 deletions
|
|
@ -37,6 +37,11 @@
|
||||||
`AcceptedResultBuilder.setSessionActivivty(PendingIntent)`. Once
|
`AcceptedResultBuilder.setSessionActivivty(PendingIntent)`. Once
|
||||||
connected, the session activity can be updated with
|
connected, the session activity can be updated with
|
||||||
`MediaSession.setSessionActivity(ControllerInfo, PendingIntent)`.
|
`MediaSession.setSessionActivity(ControllerInfo, PendingIntent)`.
|
||||||
|
* Improve error replication of calls to `MediaLibrarySession.Callback`.
|
||||||
|
Error replication can now be configured by using
|
||||||
|
`MediaLibrarySession.Builder.setLibraryErrorReplicationMode()` for
|
||||||
|
chosing the error type or opt-ing out of error replication which is on
|
||||||
|
by default.
|
||||||
* UI:
|
* UI:
|
||||||
* Work around a platform bug causing stretched/cropped video when using
|
* Work around a platform bug causing stretched/cropped video when using
|
||||||
`SurfaceView` inside a Compose `AndroidView` on API 34
|
`SurfaceView` inside a Compose `AndroidView` on API 34
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,11 @@ import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||||
import static androidx.media3.session.LibraryResult.ofVoid;
|
import static androidx.media3.session.LibraryResult.ofVoid;
|
||||||
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
|
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
|
||||||
import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
|
import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED;
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.PARAMETER;
|
||||||
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
@ -29,6 +34,7 @@ import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
|
|
@ -43,6 +49,10 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -118,6 +128,37 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
*/
|
*/
|
||||||
public static final class MediaLibrarySession extends MediaSession {
|
public static final class MediaLibrarySession extends MediaSession {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Library error replication mode. One of {@link #LIBRARY_ERROR_REPLICATION_MODE_NONE}, {@link
|
||||||
|
* #LIBRARY_ERROR_REPLICATION_MODE_FATAL} or {@link #LIBRARY_ERROR_REPLICATION_MODE_NON_FATAL}.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||||
|
@IntDef({
|
||||||
|
LIBRARY_ERROR_REPLICATION_MODE_NONE,
|
||||||
|
LIBRARY_ERROR_REPLICATION_MODE_FATAL,
|
||||||
|
LIBRARY_ERROR_REPLICATION_MODE_NON_FATAL
|
||||||
|
})
|
||||||
|
@UnstableApi
|
||||||
|
@interface LibraryErrorReplicationMode {}
|
||||||
|
|
||||||
|
/** No library service errors are replicated. */
|
||||||
|
@UnstableApi public static final int LIBRARY_ERROR_REPLICATION_MODE_NONE = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@linkplain SessionError Session errors} returned by the {@link MediaLibrarySession.Callback}
|
||||||
|
* as part of a {@link LibraryResult} are replicated to the platform session as a fatal error.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int LIBRARY_ERROR_REPLICATION_MODE_FATAL = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@linkplain SessionError Session errors} returned by the {@link MediaLibrarySession.Callback}
|
||||||
|
* as part of a {@link LibraryResult} are replicated to the platform session as a non-fatal
|
||||||
|
* error.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int LIBRARY_ERROR_REPLICATION_MODE_NON_FATAL = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An extended {@link MediaSession.Callback} for the {@link MediaLibrarySession}.
|
* An extended {@link MediaSession.Callback} for the {@link MediaLibrarySession}.
|
||||||
*
|
*
|
||||||
|
|
@ -393,6 +434,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
// constructor.
|
// constructor.
|
||||||
public static final class Builder extends BuilderBase<MediaLibrarySession, Builder, Callback> {
|
public static final class Builder extends BuilderBase<MediaLibrarySession, Builder, Callback> {
|
||||||
|
|
||||||
|
private @LibraryErrorReplicationMode int libraryErrorReplicationMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a builder for {@link MediaLibrarySession}.
|
* Creates a builder for {@link MediaLibrarySession}.
|
||||||
*
|
*
|
||||||
|
|
@ -407,7 +450,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
// Ideally it's better to make it inner class of service to enforce, but it violates API
|
// Ideally it's better to make it inner class of service to enforce, but it violates API
|
||||||
// guideline that Builders should be the inner class of the building target.
|
// guideline that Builders should be the inner class of the building target.
|
||||||
public Builder(MediaLibraryService service, Player player, Callback callback) {
|
public Builder(MediaLibraryService service, Player player, Callback callback) {
|
||||||
super(service, player, callback);
|
this((Context) service, player, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -421,6 +464,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public Builder(Context context, Player player, Callback callback) {
|
public Builder(Context context, Player player, Callback callback) {
|
||||||
super(context, player, callback);
|
super(context, player, callback);
|
||||||
|
libraryErrorReplicationMode = LIBRARY_ERROR_REPLICATION_MODE_FATAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -560,6 +604,37 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
return super.setPeriodicPositionUpdateEnabled(isEnabled);
|
return super.setPeriodicPositionUpdateEnabled(isEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether library result errors should be replicated to the platform media session's
|
||||||
|
* {@link android.media.session.PlaybackState} as a fatal error, a non-fatal error or not
|
||||||
|
* replicated at all. Replication is only executed for calling legacy browsers ({@link
|
||||||
|
* android.support.v4.media.MediaBrowserCompat} and {@link android.media.browse.MediaBrowser})
|
||||||
|
* to which no error codes can be transmitted as a result of the service call.
|
||||||
|
*
|
||||||
|
* <p>The default is {@link #LIBRARY_ERROR_REPLICATION_MODE_FATAL}.
|
||||||
|
*
|
||||||
|
* <p>{@link MediaLibrarySession.Callback#onGetLibraryRoot} is exempted from replication,
|
||||||
|
* because this method is part of the connection process of a legacy browser.
|
||||||
|
*
|
||||||
|
* <p>The following error codes are replicated:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link SessionError#ERROR_SESSION_AUTHENTICATION_EXPIRED}
|
||||||
|
* <li>{@link SessionError#ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED}
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>See {@link MediaLibrarySession#clearReplicatedLibraryError} also.
|
||||||
|
*
|
||||||
|
* @param libraryErrorReplicationMode The mode to use.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setLibraryErrorReplicationMode(
|
||||||
|
@LibraryErrorReplicationMode int libraryErrorReplicationMode) {
|
||||||
|
this.libraryErrorReplicationMode = libraryErrorReplicationMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@link MediaLibrarySession}.
|
* Builds a {@link MediaLibrarySession}.
|
||||||
*
|
*
|
||||||
|
|
@ -583,7 +658,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
sessionExtras,
|
sessionExtras,
|
||||||
checkNotNull(bitmapLoader),
|
checkNotNull(bitmapLoader),
|
||||||
playIfSuppressed,
|
playIfSuppressed,
|
||||||
isPeriodicPositionUpdateEnabled);
|
isPeriodicPositionUpdateEnabled,
|
||||||
|
libraryErrorReplicationMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,7 +674,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
Bundle sessionExtras,
|
Bundle sessionExtras,
|
||||||
BitmapLoader bitmapLoader,
|
BitmapLoader bitmapLoader,
|
||||||
boolean playIfSuppressed,
|
boolean playIfSuppressed,
|
||||||
boolean isPeriodicPositionUpdateEnabled) {
|
boolean isPeriodicPositionUpdateEnabled,
|
||||||
|
@LibraryErrorReplicationMode int libraryErrorReplicationMode) {
|
||||||
super(
|
super(
|
||||||
context,
|
context,
|
||||||
id,
|
id,
|
||||||
|
|
@ -610,7 +687,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
sessionExtras,
|
sessionExtras,
|
||||||
bitmapLoader,
|
bitmapLoader,
|
||||||
playIfSuppressed,
|
playIfSuppressed,
|
||||||
isPeriodicPositionUpdateEnabled);
|
isPeriodicPositionUpdateEnabled,
|
||||||
|
libraryErrorReplicationMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -625,7 +703,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
Bundle sessionExtras,
|
Bundle sessionExtras,
|
||||||
BitmapLoader bitmapLoader,
|
BitmapLoader bitmapLoader,
|
||||||
boolean playIfSuppressed,
|
boolean playIfSuppressed,
|
||||||
boolean isPeriodicPositionUpdateEnabled) {
|
boolean isPeriodicPositionUpdateEnabled,
|
||||||
|
@LibraryErrorReplicationMode int libraryErrorReplicationMode) {
|
||||||
return new MediaLibrarySessionImpl(
|
return new MediaLibrarySessionImpl(
|
||||||
this,
|
this,
|
||||||
context,
|
context,
|
||||||
|
|
@ -638,7 +717,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
sessionExtras,
|
sessionExtras,
|
||||||
bitmapLoader,
|
bitmapLoader,
|
||||||
playIfSuppressed,
|
playIfSuppressed,
|
||||||
isPeriodicPositionUpdateEnabled);
|
isPeriodicPositionUpdateEnabled,
|
||||||
|
libraryErrorReplicationMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -718,6 +798,25 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||||
.notifySearchResultChanged(
|
.notifySearchResultChanged(
|
||||||
checkNotNull(browser), checkNotEmpty(query), itemCount, params);
|
checkNotNull(browser), checkNotEmpty(query), itemCount, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the replicated library error in the platform session that was set when a {@link
|
||||||
|
* LibraryResult} with an error result code was returned by the {@link
|
||||||
|
* MediaLibrarySession.Callback} that is replicated and if {@linkplain
|
||||||
|
* MediaLibrarySession.Builder#setLibraryErrorReplicationMode(int) legacy session error
|
||||||
|
* replication is not turned off}.
|
||||||
|
*
|
||||||
|
* <p>Note: If a {@link LibraryResult#RESULT_SUCCESS} was returned by a method of {@link
|
||||||
|
* MediaLibrarySession.Callback} that is considered for replication, the error is cleared
|
||||||
|
* automatically by the library.
|
||||||
|
*
|
||||||
|
* <p>Calling this method updates the platform session playback state in case there was a
|
||||||
|
* replicated error set. If no error was set, calling this method is a no-op.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public void clearReplicatedLibraryError() {
|
||||||
|
getImpl().clearReplicatedLibraryError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,9 @@ package androidx.media3.session;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||||
import static androidx.media3.session.MediaConstants.ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT;
|
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
|
||||||
import static androidx.media3.session.SessionError.ERROR_INVALID_STATE;
|
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_NOT_SUPPORTED;
|
||||||
import static androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED;
|
|
||||||
import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
|
import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
@ -43,6 +41,7 @@ import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||||
import androidx.media3.session.MediaSession.ControllerCb;
|
import androidx.media3.session.MediaSession.ControllerCb;
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||||
|
import androidx.media3.session.legacy.PlaybackStateCompat;
|
||||||
import com.google.common.collect.HashMultimap;
|
import com.google.common.collect.HashMultimap;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
@ -66,6 +65,8 @@ import java.util.concurrent.Future;
|
||||||
private final HashMultimap<String, ControllerInfo> parentIdToSubscribedControllers;
|
private final HashMultimap<String, ControllerInfo> parentIdToSubscribedControllers;
|
||||||
private final HashMultimap<ControllerCb, String> controllerToSubscribedParentIds;
|
private final HashMultimap<ControllerCb, String> controllerToSubscribedParentIds;
|
||||||
|
|
||||||
|
private final @MediaLibrarySession.LibraryErrorReplicationMode int libraryErrorReplicationMode;
|
||||||
|
|
||||||
/** Creates an instance. */
|
/** Creates an instance. */
|
||||||
public MediaLibrarySessionImpl(
|
public MediaLibrarySessionImpl(
|
||||||
MediaLibrarySession instance,
|
MediaLibrarySession instance,
|
||||||
|
|
@ -79,7 +80,8 @@ import java.util.concurrent.Future;
|
||||||
Bundle sessionExtras,
|
Bundle sessionExtras,
|
||||||
BitmapLoader bitmapLoader,
|
BitmapLoader bitmapLoader,
|
||||||
boolean playIfSuppressed,
|
boolean playIfSuppressed,
|
||||||
boolean isPeriodicPositionUpdateEnabled) {
|
boolean isPeriodicPositionUpdateEnabled,
|
||||||
|
@MediaLibrarySession.LibraryErrorReplicationMode int libraryErrorReplicationMode) {
|
||||||
super(
|
super(
|
||||||
instance,
|
instance,
|
||||||
context,
|
context,
|
||||||
|
|
@ -95,6 +97,7 @@ import java.util.concurrent.Future;
|
||||||
isPeriodicPositionUpdateEnabled);
|
isPeriodicPositionUpdateEnabled);
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
this.libraryErrorReplicationMode = libraryErrorReplicationMode;
|
||||||
parentIdToSubscribedControllers = HashMultimap.create();
|
parentIdToSubscribedControllers = HashMultimap.create();
|
||||||
controllerToSubscribedParentIds = HashMultimap.create();
|
controllerToSubscribedParentIds = HashMultimap.create();
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +122,14 @@ import java.util.concurrent.Future;
|
||||||
&& legacyStub.getConnectedControllersManager().isConnected(controller);
|
&& legacyStub.getConnectedControllersManager().isConnected(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearReplicatedLibraryError() {
|
||||||
|
PlayerWrapper playerWrapper = getPlayerWrapper();
|
||||||
|
if (playerWrapper.getLegacyError() != null) {
|
||||||
|
playerWrapper.clearLegacyErrorStatus();
|
||||||
|
getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRootOnHandler(
|
public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRootOnHandler(
|
||||||
ControllerInfo browser, @Nullable LibraryParams params) {
|
ControllerInfo browser, @Nullable LibraryParams params) {
|
||||||
if (params != null && params.isRecent && isSystemUiController(browser)) {
|
if (params != null && params.isRecent && isSystemUiController(browser)) {
|
||||||
|
|
@ -137,17 +148,7 @@ import java.util.concurrent.Future;
|
||||||
.build(),
|
.build(),
|
||||||
params));
|
params));
|
||||||
}
|
}
|
||||||
ListenableFuture<LibraryResult<MediaItem>> future =
|
return callback.onGetLibraryRoot(instance, resolveControllerInfoForCallback(browser), params);
|
||||||
callback.onGetLibraryRoot(instance, resolveControllerInfoForCallback(browser), params);
|
|
||||||
future.addListener(
|
|
||||||
() -> {
|
|
||||||
@Nullable LibraryResult<MediaItem> result = tryGetFutureResult(future);
|
|
||||||
if (result != null) {
|
|
||||||
maybeUpdateLegacyErrorState(result);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
this::postOrRunOnApplicationHandler);
|
|
||||||
return future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildrenOnHandler(
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildrenOnHandler(
|
||||||
|
|
@ -185,7 +186,7 @@ import java.util.concurrent.Future;
|
||||||
() -> {
|
() -> {
|
||||||
@Nullable LibraryResult<ImmutableList<MediaItem>> result = tryGetFutureResult(future);
|
@Nullable LibraryResult<ImmutableList<MediaItem>> result = tryGetFutureResult(future);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
maybeUpdateLegacyErrorState(result);
|
maybeUpdateLegacyErrorState(browser, result);
|
||||||
verifyResultItems(result, pageSize);
|
verifyResultItems(result, pageSize);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -201,7 +202,7 @@ import java.util.concurrent.Future;
|
||||||
() -> {
|
() -> {
|
||||||
@Nullable LibraryResult<MediaItem> result = tryGetFutureResult(future);
|
@Nullable LibraryResult<MediaItem> result = tryGetFutureResult(future);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
maybeUpdateLegacyErrorState(result);
|
maybeUpdateLegacyErrorState(browser, result);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this::postOrRunOnApplicationHandler);
|
this::postOrRunOnApplicationHandler);
|
||||||
|
|
@ -291,7 +292,7 @@ import java.util.concurrent.Future;
|
||||||
() -> {
|
() -> {
|
||||||
@Nullable LibraryResult<Void> result = tryGetFutureResult(future);
|
@Nullable LibraryResult<Void> result = tryGetFutureResult(future);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
maybeUpdateLegacyErrorState(result);
|
maybeUpdateLegacyErrorState(browser, result);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this::postOrRunOnApplicationHandler);
|
this::postOrRunOnApplicationHandler);
|
||||||
|
|
@ -311,7 +312,7 @@ import java.util.concurrent.Future;
|
||||||
() -> {
|
() -> {
|
||||||
@Nullable LibraryResult<ImmutableList<MediaItem>> result = tryGetFutureResult(future);
|
@Nullable LibraryResult<ImmutableList<MediaItem>> result = tryGetFutureResult(future);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
maybeUpdateLegacyErrorState(result);
|
maybeUpdateLegacyErrorState(browser, result);
|
||||||
verifyResultItems(result, pageSize);
|
verifyResultItems(result, pageSize);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -369,44 +370,59 @@ import java.util.concurrent.Future;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeUpdateLegacyErrorState(LibraryResult<?> result) {
|
private void maybeUpdateLegacyErrorState(ControllerInfo browser, LibraryResult<?> result) {
|
||||||
|
if (libraryErrorReplicationMode == MediaLibrarySession.LIBRARY_ERROR_REPLICATION_MODE_NONE
|
||||||
|
|| browser.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
PlayerWrapper playerWrapper = getPlayerWrapper();
|
PlayerWrapper playerWrapper = getPlayerWrapper();
|
||||||
if (setLegacyAuthenticationExpiredErrorState(result)) {
|
if (setLegacyErrorState(result)) {
|
||||||
// Sync playback state if legacy error state changed.
|
// Sync playback state if legacy error state changed.
|
||||||
getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat());
|
getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat());
|
||||||
} else if (playerWrapper.getLegacyError() != null && result.resultCode == RESULT_SUCCESS) {
|
} else if (result.resultCode == RESULT_SUCCESS) {
|
||||||
playerWrapper.clearLegacyErrorStatus();
|
clearReplicatedLibraryError();
|
||||||
getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setLegacyAuthenticationExpiredErrorState(LibraryResult<?> result) {
|
private boolean setLegacyErrorState(LibraryResult<?> result) {
|
||||||
PlayerWrapper playerWrapper = getPlayerWrapper();
|
PlayerWrapper playerWrapper = getPlayerWrapper();
|
||||||
PlayerWrapper.LegacyError legacyError = playerWrapper.getLegacyError();
|
if (isReplicationErrorCode(result.resultCode)) {
|
||||||
if (result.resultCode == ERROR_SESSION_AUTHENTICATION_EXPIRED
|
@PlaybackStateCompat.ErrorCode
|
||||||
&& (legacyError == null || legacyError.code != ERROR_SESSION_AUTHENTICATION_EXPIRED)) {
|
int legacyErrorCode = LegacyConversions.convertToLegacyErrorCode(result.resultCode);
|
||||||
// Mapping this error to the legacy error state provides backwards compatibility for the
|
@Nullable PlayerWrapper.LegacyError legacyError = playerWrapper.getLegacyError();
|
||||||
// Automotive OS sign-in.
|
if (legacyError == null || legacyError.code != legacyErrorCode) {
|
||||||
Bundle bundle = Bundle.EMPTY;
|
// Mapping this error to the legacy error state provides backwards compatibility for the
|
||||||
if (result.params != null
|
// documented AAOS error flow:
|
||||||
&& result.params.extras.containsKey(EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT)) {
|
// https://developer.android.com/training/cars/media/automotive-os#-error-handling
|
||||||
// Backwards compatibility for Callbacks before SessionError was introduced.
|
String errorMessage =
|
||||||
bundle = result.params.extras;
|
result.sessionError != null
|
||||||
} else if (result.sessionError != null
|
? result.sessionError.message
|
||||||
&& result.sessionError.extras.containsKey(
|
: SessionError.DEFAULT_ERROR_MESSAGE;
|
||||||
EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT)) {
|
Bundle bundle = Bundle.EMPTY;
|
||||||
bundle = result.sessionError.extras;
|
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) {
|
||||||
|
bundle = result.sessionError.extras;
|
||||||
|
}
|
||||||
|
playerWrapper.setLegacyError(
|
||||||
|
/* isFatal= */ libraryErrorReplicationMode
|
||||||
|
== MediaLibrarySession.LIBRARY_ERROR_REPLICATION_MODE_FATAL,
|
||||||
|
legacyErrorCode,
|
||||||
|
errorMessage,
|
||||||
|
bundle);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
playerWrapper.setLegacyError(
|
|
||||||
/* isFatal= */ true,
|
|
||||||
ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT,
|
|
||||||
getContext().getString(R.string.error_message_authentication_expired),
|
|
||||||
bundle);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isReplicationErrorCode(@LibraryResult.Code int resultCode) {
|
||||||
|
return resultCode == LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED
|
||||||
|
|| resultCode == LibraryResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static <T> T tryGetFutureResult(Future<T> future) {
|
private static <T> T tryGetFutureResult(Future<T> future) {
|
||||||
checkState(future.isDone());
|
checkState(future.isDone());
|
||||||
|
|
|
||||||
|
|
@ -450,7 +450,8 @@ public class MediaSession {
|
||||||
sessionExtras,
|
sessionExtras,
|
||||||
checkNotNull(bitmapLoader),
|
checkNotNull(bitmapLoader),
|
||||||
playIfSuppressed,
|
playIfSuppressed,
|
||||||
isPeriodicPositionUpdateEnabled);
|
isPeriodicPositionUpdateEnabled,
|
||||||
|
MediaLibrarySession.LIBRARY_ERROR_REPLICATION_MODE_NONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -678,7 +679,8 @@ public class MediaSession {
|
||||||
Bundle sessionExtras,
|
Bundle sessionExtras,
|
||||||
BitmapLoader bitmapLoader,
|
BitmapLoader bitmapLoader,
|
||||||
boolean playIfSuppressed,
|
boolean playIfSuppressed,
|
||||||
boolean isPeriodicPositionUpdateEnabled) {
|
boolean isPeriodicPositionUpdateEnabled,
|
||||||
|
@MediaLibrarySession.LibraryErrorReplicationMode int libraryErrorReplicationMode) {
|
||||||
synchronized (STATIC_LOCK) {
|
synchronized (STATIC_LOCK) {
|
||||||
if (SESSION_ID_TO_SESSION_MAP.containsKey(id)) {
|
if (SESSION_ID_TO_SESSION_MAP.containsKey(id)) {
|
||||||
throw new IllegalStateException("Session ID must be unique. ID=" + id);
|
throw new IllegalStateException("Session ID must be unique. ID=" + id);
|
||||||
|
|
@ -697,7 +699,8 @@ public class MediaSession {
|
||||||
sessionExtras,
|
sessionExtras,
|
||||||
bitmapLoader,
|
bitmapLoader,
|
||||||
playIfSuppressed,
|
playIfSuppressed,
|
||||||
isPeriodicPositionUpdateEnabled);
|
isPeriodicPositionUpdateEnabled,
|
||||||
|
libraryErrorReplicationMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ MediaSessionImpl createImpl(
|
/* package */ MediaSessionImpl createImpl(
|
||||||
|
|
@ -711,7 +714,8 @@ public class MediaSession {
|
||||||
Bundle sessionExtras,
|
Bundle sessionExtras,
|
||||||
BitmapLoader bitmapLoader,
|
BitmapLoader bitmapLoader,
|
||||||
boolean playIfSuppressed,
|
boolean playIfSuppressed,
|
||||||
boolean isPeriodicPositionUpdateEnabled) {
|
boolean isPeriodicPositionUpdateEnabled,
|
||||||
|
@MediaLibrarySession.LibraryErrorReplicationMode int libraryErrorReplicationMode) {
|
||||||
return new MediaSessionImpl(
|
return new MediaSessionImpl(
|
||||||
this,
|
this,
|
||||||
context,
|
context,
|
||||||
|
|
@ -1167,12 +1171,9 @@ public class MediaSession {
|
||||||
* <p>This will call {@link MediaController.Listener#onError(MediaController, SessionError)} of
|
* <p>This will call {@link MediaController.Listener#onError(MediaController, SessionError)} of
|
||||||
* the given connected controller.
|
* the given connected controller.
|
||||||
*
|
*
|
||||||
* <p>Use {@linkplain MediaSession#getMediaNotificationControllerInfo()} to set the error of the
|
* <p>When an error is sent to {@linkplain MediaSession#getMediaNotificationControllerInfo()} or a
|
||||||
* {@linkplain android.media.session.PlaybackState playback state} of the legacy platform session.
|
* legacy controller, the error of the {@linkplain android.media.session.PlaybackState playback
|
||||||
*
|
* state} of the platform session is updated accordingly.
|
||||||
* <p>Only Media3 controllers are supported. If an error is attempted to be sent to a controller
|
|
||||||
* with {@link ControllerInfo#getControllerVersion() a controller version} of value {@link
|
|
||||||
* ControllerInfo#LEGACY_CONTROLLER_VERSION}, an {@link IllegalArgumentException} is thrown.
|
|
||||||
*
|
*
|
||||||
* @param controllerInfo The controller to send the error to.
|
* @param controllerInfo The controller to send the error to.
|
||||||
* @param sessionError The session error.
|
* @param sessionError The session error.
|
||||||
|
|
@ -1181,13 +1182,11 @@ public class MediaSession {
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final void sendError(ControllerInfo controllerInfo, SessionError sessionError) {
|
public final void sendError(ControllerInfo controllerInfo, SessionError sessionError) {
|
||||||
checkArgument(
|
|
||||||
controllerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION);
|
|
||||||
impl.sendError(controllerInfo, sessionError);
|
impl.sendError(controllerInfo, sessionError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a non-fatal error to all connected Media3 controllers.
|
* Sends a non-fatal error to all connected controllers.
|
||||||
*
|
*
|
||||||
* <p>See {@link #sendError(ControllerInfo, SessionError)} for sending an error to a specific
|
* <p>See {@link #sendError(ControllerInfo, SessionError)} for sending an error to a specific
|
||||||
* controller only.
|
* controller only.
|
||||||
|
|
|
||||||
|
|
@ -626,25 +626,38 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendError(ControllerInfo controllerInfo, SessionError sessionError) {
|
public void sendError(ControllerInfo controllerInfo, SessionError sessionError) {
|
||||||
if (controllerInfo.getInterfaceVersion() < 4) {
|
if (controllerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION
|
||||||
|
&& controllerInfo.getInterfaceVersion() < 4) {
|
||||||
// IMediaController.onError introduced with interface version 4.
|
// IMediaController.onError introduced with interface version 4.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatchRemoteControllerTaskWithoutReturn(
|
if (isMediaNotificationController(controllerInfo)
|
||||||
controllerInfo, (callback, seq) -> callback.onError(seq, sessionError));
|
|| controllerInfo.getControllerVersion() == ControllerInfo.LEGACY_CONTROLLER_VERSION) {
|
||||||
if (isMediaNotificationController(controllerInfo)) {
|
// Media notification controller or legacy controllers update the platform session.
|
||||||
dispatchRemoteControllerTaskToLegacyStub(
|
dispatchRemoteControllerTaskToLegacyStub(
|
||||||
(callback, seq) -> callback.onError(seq, sessionError));
|
(callback, seq) -> callback.onError(seq, sessionError));
|
||||||
|
} else {
|
||||||
|
// Media3 controller are notified individually.
|
||||||
|
dispatchRemoteControllerTaskWithoutReturn(
|
||||||
|
controllerInfo, (callback, seq) -> callback.onError(seq, sessionError));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendError(SessionError sessionError) {
|
public void sendError(SessionError sessionError) {
|
||||||
// Send error messages only to Media3 controllers.
|
// Send error messages to Media3 controllers.
|
||||||
ImmutableList<ControllerInfo> connectedControllers =
|
ImmutableList<ControllerInfo> connectedControllers =
|
||||||
sessionStub.getConnectedControllersManager().getConnectedControllers();
|
sessionStub.getConnectedControllersManager().getConnectedControllers();
|
||||||
for (int i = 0; i < connectedControllers.size(); i++) {
|
for (int i = 0; i < connectedControllers.size(); i++) {
|
||||||
sendError(connectedControllers.get(i), sessionError);
|
ControllerInfo controllerInfo = connectedControllers.get(i);
|
||||||
|
if (!isMediaNotificationController(controllerInfo)) {
|
||||||
|
// Omit sending to the media notification controller. Instead the error will be dispatched
|
||||||
|
// through the legacy stub below to avoid updating the legacy session multiple times.
|
||||||
|
sendError(controllerInfo, sessionError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Send error messages to legacy controllers.
|
||||||
|
dispatchRemoteControllerTaskToLegacyStub(
|
||||||
|
(callback, seq) -> callback.onError(seq, sessionError));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaSession.ConnectionResult onConnectOnHandler(ControllerInfo controller) {
|
public MediaSession.ConnectionResult onConnectOnHandler(ControllerInfo controller) {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import android.os.Bundle;
|
||||||
public class CommonConstants {
|
public class CommonConstants {
|
||||||
|
|
||||||
public static final String SUPPORT_APP_PACKAGE_NAME = "androidx.media3.test.session";
|
public static final String SUPPORT_APP_PACKAGE_NAME = "androidx.media3.test.session";
|
||||||
|
public static final String MEDIA_CONTROLLER_PACKAGE_NAME_API_21 =
|
||||||
|
"android.media.session.MediaController";
|
||||||
|
|
||||||
public static final ComponentName MEDIA3_SESSION_PROVIDER_SERVICE =
|
public static final ComponentName MEDIA3_SESSION_PROVIDER_SERVICE =
|
||||||
new ComponentName(
|
new ComponentName(
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ public class MediaBrowserConstants {
|
||||||
public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
|
public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
|
||||||
public static final String PARENT_ID_ERROR = "parent_id_error";
|
public static final String PARENT_ID_ERROR = "parent_id_error";
|
||||||
public static final String PARENT_ID_AUTH_EXPIRED_ERROR = "parent_auth_expired_error";
|
public static final String PARENT_ID_AUTH_EXPIRED_ERROR = "parent_auth_expired_error";
|
||||||
|
public static final String PARENT_ID_SKIP_LIMIT_REACHED_ERROR = "parent_skip_limit_reached_error";
|
||||||
public static final String PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED =
|
public static final String PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED =
|
||||||
"parent_auth_expired_error_deprecated";
|
"parent_auth_expired_error_deprecated";
|
||||||
public static final String PARENT_ID_AUTH_EXPIRED_ERROR_NON_FATAL =
|
public static final String PARENT_ID_AUTH_EXPIRED_ERROR_NON_FATAL =
|
||||||
|
|
@ -70,6 +71,8 @@ public class MediaBrowserConstants {
|
||||||
"notify_children_changed_delay";
|
"notify_children_changed_delay";
|
||||||
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_BROADCAST =
|
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_BROADCAST =
|
||||||
"notify_children_changed_broadcast";
|
"notify_children_changed_broadcast";
|
||||||
|
public static final String CONNECTION_HINTS_KEY_LIBRARY_ERROR_REPLICATION_MODE =
|
||||||
|
"error_replication_mode";
|
||||||
|
|
||||||
public static final String CUSTOM_ACTION = "customAction";
|
public static final String CUSTOM_ACTION = "customAction";
|
||||||
public static final Bundle CUSTOM_ACTION_EXTRAS = new Bundle();
|
public static final Bundle CUSTOM_ACTION_EXTRAS = new Bundle();
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ package androidx.media3.session;
|
||||||
import static androidx.media.utils.MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED;
|
import static androidx.media.utils.MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||||
|
import static androidx.media3.session.MediaLibraryService.MediaLibrarySession.LIBRARY_ERROR_REPLICATION_MODE_NONE;
|
||||||
|
import static androidx.media3.session.MediaLibraryService.MediaLibrarySession.LIBRARY_ERROR_REPLICATION_MODE_NON_FATAL;
|
||||||
import static androidx.media3.session.MockMediaLibraryService.CONNECTION_HINTS_CUSTOM_LIBRARY_ROOT;
|
import static androidx.media3.session.MockMediaLibraryService.CONNECTION_HINTS_CUSTOM_LIBRARY_ROOT;
|
||||||
import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle;
|
import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.METADATA_ALBUM_TITLE;
|
import static androidx.media3.test.session.common.CommonConstants.METADATA_ALBUM_TITLE;
|
||||||
|
|
@ -29,6 +31,7 @@ import static androidx.media3.test.session.common.CommonConstants.METADATA_MEDIA
|
||||||
import static androidx.media3.test.session.common.CommonConstants.METADATA_TITLE;
|
import static androidx.media3.test.session.common.CommonConstants.METADATA_TITLE;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
|
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CHILDREN_COUNT;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CHILDREN_COUNT;
|
||||||
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CONNECTION_HINTS_KEY_LIBRARY_ERROR_REPLICATION_MODE;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
|
||||||
|
|
@ -43,6 +46,7 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_I
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
|
||||||
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_SKIP_LIMIT_REACHED_ERROR;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_KEY;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_KEY;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_VALUE;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_VALUE;
|
||||||
|
|
@ -78,6 +82,7 @@ import androidx.media3.test.session.common.TestUtils;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.ext.truth.os.BundleSubject;
|
import androidx.test.ext.truth.os.BundleSubject;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
@ -382,21 +387,44 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getChildren_authErrorResult_correctPlaybackStateCompatUpdates() throws Exception {
|
public void getChildren_errorResultWithDefaultErrorReplication_legacyPlaybackStateWithFatalError()
|
||||||
assertGetChildrenAuthenticationRequired(PARENT_ID_AUTH_EXPIRED_ERROR);
|
throws Exception {
|
||||||
|
connectAndWait(/* rootHints= */ Bundle.EMPTY);
|
||||||
|
subscribeAndAssertServiceCallbackErrorWithAuthErrorReplicated(
|
||||||
|
PARENT_ID_AUTH_EXPIRED_ERROR, /* assertFatalError= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getChildren_authErrorResultDeprecated_correctPlaybackStateCompatUpdates()
|
public void
|
||||||
throws Exception {
|
getChildren_deprecatedErrorResultWithDefaultErrorReplication_legacyPlaybackStateWithFatalError()
|
||||||
|
throws Exception {
|
||||||
|
connectAndWait(/* rootHints= */ Bundle.EMPTY);
|
||||||
// Tests the deprecated approach where apps were expected to pass the error extras back as the
|
// 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
|
// extras of the LibraryParams of the LibraryResult because the SessionError type didn't then
|
||||||
// exist as part of the LibraryResult.
|
// exist as part of the LibraryResult.
|
||||||
assertGetChildrenAuthenticationRequired(PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED);
|
subscribeAndAssertServiceCallbackErrorWithAuthErrorReplicated(
|
||||||
|
PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED, /* assertFatalError= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assertGetChildrenAuthenticationRequired(String authExpiredParentId) throws Exception {
|
@Test
|
||||||
|
public void
|
||||||
|
getChildren_errorResultWithNonFatalErrorReplication_legacyPlaybackStateWithNonFatalError()
|
||||||
|
throws Exception {
|
||||||
|
Bundle connectionHints = new Bundle();
|
||||||
|
connectionHints.putInt(
|
||||||
|
CONNECTION_HINTS_KEY_LIBRARY_ERROR_REPLICATION_MODE,
|
||||||
|
LIBRARY_ERROR_REPLICATION_MODE_NON_FATAL);
|
||||||
|
connectForServiceStartWithConnectionHints(connectionHints);
|
||||||
connectAndWait(/* rootHints= */ Bundle.EMPTY);
|
connectAndWait(/* rootHints= */ Bundle.EMPTY);
|
||||||
|
// 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.
|
||||||
|
subscribeAndAssertServiceCallbackErrorWithAuthErrorReplicated(
|
||||||
|
PARENT_ID_AUTH_EXPIRED_ERROR, /* assertFatalError= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void subscribeAndAssertServiceCallbackErrorWithAuthErrorReplicated(
|
||||||
|
String authExpiredParentId, boolean assertFatalError) throws Exception {
|
||||||
CountDownLatch errorLatch = new CountDownLatch(1);
|
CountDownLatch errorLatch = new CountDownLatch(1);
|
||||||
AtomicReference<String> parentIdRefOnError = new AtomicReference<>();
|
AtomicReference<String> parentIdRefOnError = new AtomicReference<>();
|
||||||
|
|
||||||
|
|
@ -413,10 +441,15 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
|
||||||
assertThat(errorLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(errorLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(parentIdRefOnError.get()).isEqualTo(authExpiredParentId);
|
assertThat(parentIdRefOnError.get()).isEqualTo(authExpiredParentId);
|
||||||
assertThat(firstPlaybackStateCompatReported.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(firstPlaybackStateCompatReported.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(lastReportedPlaybackStateCompat.getState())
|
if (assertFatalError) {
|
||||||
.isEqualTo(PlaybackStateCompat.STATE_ERROR);
|
assertThat(Iterables.getLast(reportedPlaybackStatesCompat).getState())
|
||||||
|
.isEqualTo(PlaybackStateCompat.STATE_ERROR);
|
||||||
|
} else {
|
||||||
|
assertThat(Iterables.getLast(reportedPlaybackStatesCompat).getState())
|
||||||
|
.isNotEqualTo(PlaybackStateCompat.STATE_ERROR);
|
||||||
|
}
|
||||||
assertThat(
|
assertThat(
|
||||||
lastReportedPlaybackStateCompat
|
Iterables.getLast(reportedPlaybackStatesCompat)
|
||||||
.getExtras()
|
.getExtras()
|
||||||
.getString(MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT))
|
.getString(MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT))
|
||||||
.isEqualTo(PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL);
|
.isEqualTo(PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL);
|
||||||
|
|
@ -437,15 +470,65 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
|
||||||
assertThat(successLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(successLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(parentIdRefOnChildrenLoaded.get()).isEqualTo(PARENT_ID);
|
assertThat(parentIdRefOnChildrenLoaded.get()).isEqualTo(PARENT_ID);
|
||||||
// Any successful calls remove the error state,
|
// Any successful calls remove the error state,
|
||||||
assertThat(lastReportedPlaybackStateCompat.getState())
|
assertThat(Iterables.getLast(reportedPlaybackStatesCompat).getState())
|
||||||
.isNotEqualTo(PlaybackStateCompat.STATE_ERROR);
|
.isNotEqualTo(PlaybackStateCompat.STATE_ERROR);
|
||||||
assertThat(
|
assertThat(
|
||||||
lastReportedPlaybackStateCompat
|
Iterables.getLast(reportedPlaybackStatesCompat)
|
||||||
.getExtras()
|
.getExtras()
|
||||||
.getString(MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT))
|
.getString(MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT))
|
||||||
.isNull();
|
.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getChildren_errorResultWithErrorReplicationDisabled_errorNotReplicated()
|
||||||
|
throws Exception {
|
||||||
|
Bundle connectionHints = new Bundle();
|
||||||
|
connectionHints.putInt(
|
||||||
|
CONNECTION_HINTS_KEY_LIBRARY_ERROR_REPLICATION_MODE, LIBRARY_ERROR_REPLICATION_MODE_NONE);
|
||||||
|
connectForServiceStartWithConnectionHints(connectionHints);
|
||||||
|
connectAndWait(/* rootHints= */ Bundle.EMPTY);
|
||||||
|
|
||||||
|
subscribeAndAssertServiceCallbackErrorWithoutErrorReplication(PARENT_ID_AUTH_EXPIRED_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getChildren_skipLimitReachedErrorResultDefaultErrorCodeSet_errorNotReplicated()
|
||||||
|
throws Exception {
|
||||||
|
connectAndWait(/* rootHints= */ Bundle.EMPTY);
|
||||||
|
|
||||||
|
subscribeAndAssertServiceCallbackErrorWithoutErrorReplication(
|
||||||
|
PARENT_ID_SKIP_LIMIT_REACHED_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void subscribeAndAssertServiceCallbackErrorWithoutErrorReplication(String parentId)
|
||||||
|
throws InterruptedException {
|
||||||
|
CountDownLatch errorLatch = new CountDownLatch(1);
|
||||||
|
AtomicReference<String> parentIdRefOnError = new AtomicReference<>();
|
||||||
|
browserCompat.subscribe(
|
||||||
|
parentId,
|
||||||
|
new SubscriptionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError(String parentId) {
|
||||||
|
parentIdRefOnError.set(parentId);
|
||||||
|
errorLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(errorLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
assertThat(parentIdRefOnError.get()).isEqualTo(parentId);
|
||||||
|
assertThat(reportedPlaybackStatesCompat).isEmpty();
|
||||||
|
assertThat(controllerCompat.getPlaybackState().getErrorCode()).isEqualTo(0);
|
||||||
|
assertThat(controllerCompat.getPlaybackState().getErrorMessage()).isNull();
|
||||||
|
|
||||||
|
// Trigger a playback state update to assert we never get a playback state with error reported.
|
||||||
|
controllerCompat.getTransportControls().play();
|
||||||
|
|
||||||
|
assertThat(firstPlaybackStateCompatReported.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
assertThat(reportedPlaybackStatesCompat).isNotEmpty();
|
||||||
|
assertThat(controllerCompat.getPlaybackState().getErrorCode()).isEqualTo(0);
|
||||||
|
assertThat(controllerCompat.getPlaybackState().getErrorMessage()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getChildren_emptyResult() throws Exception {
|
public void getChildren_emptyResult() throws Exception {
|
||||||
String testParentId = PARENT_ID_NO_CHILDREN;
|
String testParentId = PARENT_ID_NO_CHILDREN;
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,18 @@ import android.support.v4.media.MediaBrowserCompat;
|
||||||
import android.support.v4.media.session.MediaControllerCompat;
|
import android.support.v4.media.session.MediaControllerCompat;
|
||||||
import android.support.v4.media.session.PlaybackStateCompat;
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||||
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
||||||
import androidx.media3.test.session.common.TestHandler;
|
import androidx.media3.test.session.common.TestHandler;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
|
@ -59,9 +65,10 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
|
||||||
Context context;
|
Context context;
|
||||||
TestHandler handler;
|
TestHandler handler;
|
||||||
@Nullable MediaBrowserCompat browserCompat;
|
@Nullable MediaBrowserCompat browserCompat;
|
||||||
|
@Nullable MediaController serviceStartController;
|
||||||
@Nullable MediaControllerCompat controllerCompat;
|
@Nullable MediaControllerCompat controllerCompat;
|
||||||
TestConnectionCallback connectionCallback;
|
TestConnectionCallback connectionCallback;
|
||||||
@Nullable PlaybackStateCompat lastReportedPlaybackStateCompat;
|
List<PlaybackStateCompat> reportedPlaybackStatesCompat;
|
||||||
@Nullable CountDownLatch firstPlaybackStateCompatReported;
|
@Nullable CountDownLatch firstPlaybackStateCompatReported;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|
@ -70,20 +77,61 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
|
||||||
handler = threadTestRule.getHandler();
|
handler = threadTestRule.getHandler();
|
||||||
connectionCallback = new TestConnectionCallback();
|
connectionCallback = new TestConnectionCallback();
|
||||||
firstPlaybackStateCompatReported = new CountDownLatch(1);
|
firstPlaybackStateCompatReported = new CountDownLatch(1);
|
||||||
|
reportedPlaybackStatesCompat = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void cleanUp() {
|
public void cleanUp() throws Exception {
|
||||||
if (browserCompat != null) {
|
if (browserCompat != null) {
|
||||||
browserCompat.disconnect();
|
browserCompat.disconnect();
|
||||||
browserCompat = null;
|
browserCompat = null;
|
||||||
}
|
}
|
||||||
|
if (serviceStartController != null) {
|
||||||
|
handler.postAndSync(() -> serviceStartController.release());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ComponentName getServiceComponent() {
|
ComponentName getServiceComponent() {
|
||||||
return MOCK_MEDIA3_SESSION_SERVICE;
|
return MOCK_MEDIA3_SESSION_SERVICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the service by connecting a Media3 controller and passing connection hints. The service
|
||||||
|
* can then use the connection hints to build the session for instance with specific settings.
|
||||||
|
*
|
||||||
|
* <p>Note that a media1 {@link MediaBrowserCompat} can't send connection hints. The root hints of
|
||||||
|
* the legacy browser end up in the {@link MediaLibraryService.LibraryParams} passed to {@link
|
||||||
|
* MediaLibrarySession.Callback#onGetLibraryRoot(MediaLibraryService.MediaLibrarySession,
|
||||||
|
* MediaSession.ControllerInfo, MediaLibraryService.LibraryParams)} as they aren't available
|
||||||
|
* earlier.
|
||||||
|
*/
|
||||||
|
void connectForServiceStartWithConnectionHints(Bundle connectionHints) throws Exception {
|
||||||
|
CountDownLatch latch = new CountDownLatch(/* count= */ 1);
|
||||||
|
handler.postAndSync(
|
||||||
|
() -> {
|
||||||
|
ListenableFuture<MediaController> future =
|
||||||
|
new MediaController.Builder(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
new SessionToken(
|
||||||
|
ApplicationProvider.getApplicationContext(), getServiceComponent()))
|
||||||
|
.setConnectionHints(connectionHints)
|
||||||
|
.buildAsync();
|
||||||
|
future.addListener(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
if (future.isDone()) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
serviceStartController = future.get();
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
});
|
||||||
|
assertThat(latch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
void connectAndWait(Bundle rootHints) throws Exception {
|
void connectAndWait(Bundle rootHints) throws Exception {
|
||||||
handler.postAndSync(
|
handler.postAndSync(
|
||||||
() -> {
|
() -> {
|
||||||
|
|
@ -138,7 +186,7 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
|
||||||
new MediaControllerCompat.Callback() {
|
new MediaControllerCompat.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackStateChanged(PlaybackStateCompat state) {
|
public void onPlaybackStateChanged(PlaybackStateCompat state) {
|
||||||
lastReportedPlaybackStateCompat = state;
|
reportedPlaybackStatesCompat.add(state);
|
||||||
firstPlaybackStateCompatReported.countDown();
|
firstPlaybackStateCompatReported.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||||
import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle;
|
import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle;
|
||||||
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
|
import static androidx.media3.session.SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
|
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
|
||||||
|
|
@ -180,7 +180,7 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
||||||
.getHandler()
|
.getHandler()
|
||||||
.postAndSync(() -> browser.getItem(mediaId))
|
.postAndSync(() -> browser.getItem(mediaId))
|
||||||
.get(TIMEOUT_MS, MILLISECONDS);
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
assertThat(result.resultCode).isEqualTo(ERROR_BAD_VALUE);
|
assertThat(result.resultCode).isEqualTo(ERROR_SESSION_SKIP_LIMIT_REACHED);
|
||||||
assertThat(result.value).isNull();
|
assertThat(result.value).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,13 @@ import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT;
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
|
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
|
||||||
|
import static androidx.media3.session.MediaLibraryService.MediaLibrarySession.LIBRARY_ERROR_REPLICATION_MODE_FATAL;
|
||||||
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
|
import static androidx.media3.session.SessionError.ERROR_BAD_VALUE;
|
||||||
import static androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED;
|
import static androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED;
|
||||||
|
import static androidx.media3.session.SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED;
|
||||||
|
import static androidx.media3.test.session.common.CommonConstants.MEDIA_CONTROLLER_PACKAGE_NAME_API_21;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
||||||
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CONNECTION_HINTS_KEY_LIBRARY_ERROR_REPLICATION_MODE;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
|
||||||
|
|
@ -44,6 +48,7 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_I
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
|
||||||
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_SKIP_LIMIT_REACHED_ERROR;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY;
|
||||||
|
|
@ -202,15 +207,25 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||||
|
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
MockPlayer player =
|
MockPlayer player =
|
||||||
new MockPlayer.Builder().setApplicationLooper(handlerThread.getLooper()).build();
|
new MockPlayer.Builder()
|
||||||
|
.setChangePlayerStateWithTransportControl(true)
|
||||||
|
.setApplicationLooper(handlerThread.getLooper())
|
||||||
|
.build();
|
||||||
|
|
||||||
MediaLibrarySession.Callback callback = registry.getSessionCallback();
|
MediaLibrarySession.Callback callback = registry.getSessionCallback();
|
||||||
|
int libraryErrorReplicationMode =
|
||||||
|
controllerInfo
|
||||||
|
.getConnectionHints()
|
||||||
|
.getInt(
|
||||||
|
CONNECTION_HINTS_KEY_LIBRARY_ERROR_REPLICATION_MODE,
|
||||||
|
LIBRARY_ERROR_REPLICATION_MODE_FATAL);
|
||||||
session =
|
session =
|
||||||
new MediaLibrarySession.Builder(
|
new MediaLibrarySession.Builder(
|
||||||
MockMediaLibraryService.this,
|
MockMediaLibraryService.this,
|
||||||
player,
|
player,
|
||||||
callback != null ? callback : new TestLibrarySessionCallback())
|
callback != null ? callback : new TestLibrarySessionCallback())
|
||||||
.setId(ID)
|
.setId(ID)
|
||||||
|
.setLibraryErrorReplicationMode(libraryErrorReplicationMode)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
return session;
|
return session;
|
||||||
|
|
@ -247,7 +262,8 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||||
@Override
|
@Override
|
||||||
public MediaSession.ConnectionResult onConnect(
|
public MediaSession.ConnectionResult onConnect(
|
||||||
MediaSession session, ControllerInfo controller) {
|
MediaSession session, ControllerInfo controller) {
|
||||||
if (!SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())) {
|
if (!SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())
|
||||||
|
&& !MEDIA_CONTROLLER_PACKAGE_NAME_API_21.equals(controller.getPackageName())) {
|
||||||
return MediaSession.ConnectionResult.reject();
|
return MediaSession.ConnectionResult.reject();
|
||||||
}
|
}
|
||||||
MediaSession.ConnectionResult connectionResult =
|
MediaSession.ConnectionResult connectionResult =
|
||||||
|
|
@ -322,7 +338,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||||
LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null));
|
LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null));
|
||||||
default: // fall out
|
default: // fall out
|
||||||
}
|
}
|
||||||
return Futures.immediateFuture(LibraryResult.ofError(ERROR_BAD_VALUE));
|
return Futures.immediateFuture(LibraryResult.ofError(ERROR_SESSION_SKIP_LIMIT_REACHED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -353,7 +369,8 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||||
return Futures.immediateFuture(
|
return Futures.immediateFuture(
|
||||||
LibraryResult.ofError(new SessionError(ERROR_BAD_VALUE, "error message", errorBundle)));
|
LibraryResult.ofError(new SessionError(ERROR_BAD_VALUE, "error message", errorBundle)));
|
||||||
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)
|
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)
|
||||||
|| Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED)) {
|
|| Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR_DEPRECATED)
|
||||||
|
|| Objects.equals(parentId, PARENT_ID_SKIP_LIMIT_REACHED_ERROR)) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
Intent signInIntent = new Intent("action");
|
Intent signInIntent = new Intent("action");
|
||||||
int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0;
|
int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||||
|
|
@ -364,17 +381,21 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||||
bundle.putString(
|
bundle.putString(
|
||||||
EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT,
|
EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT,
|
||||||
PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL);
|
PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL);
|
||||||
|
@SessionError.Code
|
||||||
|
int errorCode =
|
||||||
|
Objects.equals(parentId, PARENT_ID_SKIP_LIMIT_REACHED_ERROR)
|
||||||
|
? ERROR_SESSION_SKIP_LIMIT_REACHED
|
||||||
|
: ERROR_SESSION_AUTHENTICATION_EXPIRED;
|
||||||
return Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)
|
return Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)
|
||||||
? Futures.immediateFuture(
|
? Futures.immediateFuture(
|
||||||
// error with SessionError
|
// error with SessionError
|
||||||
LibraryResult.ofError(
|
LibraryResult.ofError(
|
||||||
new SessionError(ERROR_SESSION_AUTHENTICATION_EXPIRED, "error message", bundle),
|
new SessionError(errorCode, "error message", bundle),
|
||||||
new LibraryParams.Builder().build()))
|
new LibraryParams.Builder().build()))
|
||||||
: Futures.immediateFuture(
|
: Futures.immediateFuture(
|
||||||
// deprecated error before SessionError was introduced
|
// deprecated error before SessionError was introduced
|
||||||
LibraryResult.ofError(
|
LibraryResult.ofError(
|
||||||
ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
errorCode, new LibraryParams.Builder().setExtras(bundle).build()));
|
||||||
new LibraryParams.Builder().setExtras(bundle).build()));
|
|
||||||
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR_NON_FATAL)) {
|
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR_NON_FATAL)) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
Intent signInIntent = new Intent("action");
|
Intent signInIntent = new Intent("action");
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package androidx.media3.session;
|
package androidx.media3.session;
|
||||||
|
|
||||||
|
import static androidx.media3.test.session.common.CommonConstants.MEDIA_CONTROLLER_PACKAGE_NAME_API_21;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
@ -119,7 +120,8 @@ public class MockMediaSessionService extends MediaSessionService {
|
||||||
@Override
|
@Override
|
||||||
public MediaSession.ConnectionResult onConnect(
|
public MediaSession.ConnectionResult onConnect(
|
||||||
MediaSession session, ControllerInfo controller) {
|
MediaSession session, ControllerInfo controller) {
|
||||||
if (TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())) {
|
if (TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())
|
||||||
|
|| TextUtils.equals(MEDIA_CONTROLLER_PACKAGE_NAME_API_21, controller.getPackageName())) {
|
||||||
return MediaSession.Callback.super.onConnect(session, controller);
|
return MediaSession.Callback.super.onConnect(session, controller);
|
||||||
}
|
}
|
||||||
return MediaSession.ConnectionResult.reject();
|
return MediaSession.ConnectionResult.reject();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue