mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Update error state of legacy playback state if authentication fails
This change adds the ability to update the error code of the PlaybackStateCompat in cases we need this for backwards compatibility. It is applied in the least intrusive way because normally, return values of a service method should not change the state of the `PlaybackStateCompat`, just because it has nothing to do with the playback state but rather with the state of the `MediaLibrarySession`. For this reason only the error code `RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED` is taken into account while all other error codes are not mapped to the `PlaybackStateCompat'. PiperOrigin-RevId: 438038852
This commit is contained in:
parent
e699765df5
commit
3ac7e0e84e
@ -218,11 +218,24 @@ public final class LibraryResult<V> implements Bundleable {
|
|||||||
* @param errorCode The error code.
|
* @param errorCode The error code.
|
||||||
*/
|
*/
|
||||||
public static <V> LibraryResult<V> ofError(@Code int errorCode) {
|
public static <V> LibraryResult<V> ofError(@Code int errorCode) {
|
||||||
|
return ofError(errorCode, /* params= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance with an unsuccessful {@link Code result code} and {@link LibraryParams} to
|
||||||
|
* describe the error.
|
||||||
|
*
|
||||||
|
* <p>{@code errorCode} must not be {@link #RESULT_SUCCESS}.
|
||||||
|
*
|
||||||
|
* @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);
|
checkArgument(errorCode != RESULT_SUCCESS);
|
||||||
return new LibraryResult<>(
|
return new LibraryResult<>(
|
||||||
errorCode,
|
/* resultCode= */ errorCode,
|
||||||
SystemClock.elapsedRealtime(),
|
SystemClock.elapsedRealtime(),
|
||||||
/* params= */ null,
|
/* params= */ params,
|
||||||
/* value= */ null,
|
/* value= */ null,
|
||||||
VALUE_TYPE_ERROR);
|
VALUE_TYPE_ERROR);
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,33 @@ public final class MediaConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String MEDIA_URI_QUERY_URI = "uri";
|
public static final String MEDIA_URI_QUERY_URI = "uri";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The extras key for the localized error resolution string.
|
||||||
|
*
|
||||||
|
* <p>See {@link
|
||||||
|
* androidx.media.utils.MediaConstants#PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL}.
|
||||||
|
*/
|
||||||
|
public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT =
|
||||||
|
"android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
|
||||||
|
/**
|
||||||
|
* The extras key for the error resolution intent.
|
||||||
|
*
|
||||||
|
* <p>See {@link
|
||||||
|
* androidx.media.utils.MediaConstants#PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT}.
|
||||||
|
*/
|
||||||
|
public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT =
|
||||||
|
"android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
|
||||||
|
|
||||||
|
/** The legacy status code for successful execution. */
|
||||||
|
public static final int STATUS_CODE_SUCCESS_COMPAT = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The legacy error code for expired authentication.
|
||||||
|
*
|
||||||
|
* <p>See {@code PlaybackStateCompat#ERROR_CODE_AUTHENTICATION_EXPIRED}.
|
||||||
|
*/
|
||||||
|
public static final int ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT = 3;
|
||||||
|
|
||||||
/* package */ static final String SESSION_COMMAND_ON_EXTRAS_CHANGED =
|
/* package */ static final String SESSION_COMMAND_ON_EXTRAS_CHANGED =
|
||||||
"androidx.media3.session.SESSION_COMMAND_ON_EXTRAS_CHANGED";
|
"androidx.media3.session.SESSION_COMMAND_ON_EXTRAS_CHANGED";
|
||||||
/* package */ static final String SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED =
|
/* package */ static final String SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED =
|
||||||
|
@ -18,7 +18,10 @@ 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.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED;
|
||||||
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||||
|
import static androidx.media3.session.MediaConstants.ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT;
|
||||||
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -118,19 +121,17 @@ import java.util.concurrent.Future;
|
|||||||
|
|
||||||
public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRootOnHandler(
|
public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRootOnHandler(
|
||||||
ControllerInfo browser, @Nullable LibraryParams params) {
|
ControllerInfo browser, @Nullable LibraryParams params) {
|
||||||
// onGetLibraryRoot is defined to return a non-null result but it's implemented by applications,
|
ListenableFuture<LibraryResult<MediaItem>> future =
|
||||||
// so we explicitly null-check the result to fail early if an app accidentally returns null.
|
callback.onGetLibraryRoot(instance, browser, params);
|
||||||
return checkNotNull(
|
future.addListener(
|
||||||
callback.onGetLibraryRoot(instance, browser, params),
|
() -> {
|
||||||
"onGetLibraryRoot must return non-null future");
|
@Nullable LibraryResult<MediaItem> result = tryGetFutureResult(future);
|
||||||
|
if (result != null) {
|
||||||
|
maybeUpdateLegacyErrorState(result);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
public ListenableFuture<LibraryResult<MediaItem>> onGetItemOnHandler(
|
MoreExecutors.directExecutor());
|
||||||
ControllerInfo browser, String mediaId) {
|
return future;
|
||||||
// onGetItem is defined to return a non-null result but it's implemented by applications,
|
|
||||||
// so we explicitly null-check the result to fail early if an app accidentally returns null.
|
|
||||||
return checkNotNull(
|
|
||||||
callback.onGetItem(instance, browser, mediaId), "onGetItem must return non-null future");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildrenOnHandler(
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildrenOnHandler(
|
||||||
@ -139,16 +140,13 @@ import java.util.concurrent.Future;
|
|||||||
int page,
|
int page,
|
||||||
int pageSize,
|
int pageSize,
|
||||||
@Nullable LibraryParams params) {
|
@Nullable LibraryParams params) {
|
||||||
// onGetChildren is defined to return a non-null result but it's implemented by applications,
|
|
||||||
// so we explicitly null-check the result to fail early if an app accidentally returns null.
|
|
||||||
ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future =
|
ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future =
|
||||||
checkNotNull(
|
callback.onGetChildren(instance, browser, parentId, page, pageSize, params);
|
||||||
callback.onGetChildren(instance, browser, parentId, page, pageSize, params),
|
|
||||||
"onGetChildren must return non-null future");
|
|
||||||
future.addListener(
|
future.addListener(
|
||||||
() -> {
|
() -> {
|
||||||
@Nullable LibraryResult<ImmutableList<MediaItem>> result = tryGetFutureResult(future);
|
@Nullable LibraryResult<ImmutableList<MediaItem>> result = tryGetFutureResult(future);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
maybeUpdateLegacyErrorState(result);
|
||||||
verifyResultItems(result, pageSize);
|
verifyResultItems(result, pageSize);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -156,6 +154,21 @@ import java.util.concurrent.Future;
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<LibraryResult<MediaItem>> onGetItemOnHandler(
|
||||||
|
ControllerInfo browser, String mediaId) {
|
||||||
|
ListenableFuture<LibraryResult<MediaItem>> future =
|
||||||
|
callback.onGetItem(instance, browser, mediaId);
|
||||||
|
future.addListener(
|
||||||
|
() -> {
|
||||||
|
@Nullable LibraryResult<MediaItem> result = tryGetFutureResult(future);
|
||||||
|
if (result != null) {
|
||||||
|
maybeUpdateLegacyErrorState(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<Void>> onSubscribeOnHandler(
|
public ListenableFuture<LibraryResult<Void>> onSubscribeOnHandler(
|
||||||
ControllerInfo browser, String parentId, @Nullable LibraryParams params) {
|
ControllerInfo browser, String parentId, @Nullable LibraryParams params) {
|
||||||
ControllerCb controller = checkStateNotNull(browser.getControllerCb());
|
ControllerCb controller = checkStateNotNull(browser.getControllerCb());
|
||||||
@ -193,12 +206,8 @@ import java.util.concurrent.Future;
|
|||||||
|
|
||||||
public ListenableFuture<LibraryResult<Void>> onUnsubscribeOnHandler(
|
public ListenableFuture<LibraryResult<Void>> onUnsubscribeOnHandler(
|
||||||
ControllerInfo browser, String parentId) {
|
ControllerInfo browser, String parentId) {
|
||||||
// onUnsubscribe is defined to return a non-null result but it's implemented by applications,
|
|
||||||
// so we explicitly null-check the result to fail early if an app accidentally returns null.
|
|
||||||
ListenableFuture<LibraryResult<Void>> future =
|
ListenableFuture<LibraryResult<Void>> future =
|
||||||
checkNotNull(
|
callback.onUnsubscribe(instance, browser, parentId);
|
||||||
callback.onUnsubscribe(instance, browser, parentId),
|
|
||||||
"onUnsubscribe must return non-null future");
|
|
||||||
|
|
||||||
future.addListener(
|
future.addListener(
|
||||||
() -> removeSubscription(checkStateNotNull(browser.getControllerCb()), parentId),
|
() -> removeSubscription(checkStateNotNull(browser.getControllerCb()), parentId),
|
||||||
@ -209,11 +218,17 @@ import java.util.concurrent.Future;
|
|||||||
|
|
||||||
public ListenableFuture<LibraryResult<Void>> onSearchOnHandler(
|
public ListenableFuture<LibraryResult<Void>> onSearchOnHandler(
|
||||||
ControllerInfo browser, String query, @Nullable LibraryParams params) {
|
ControllerInfo browser, String query, @Nullable LibraryParams params) {
|
||||||
// onSearch is defined to return a non-null result but it's implemented by applications,
|
ListenableFuture<LibraryResult<Void>> future =
|
||||||
// so we explicitly null-check the result to fail early if an app accidentally returns null.
|
callback.onSearch(instance, browser, query, params);
|
||||||
return checkNotNull(
|
future.addListener(
|
||||||
callback.onSearch(instance, browser, query, params),
|
() -> {
|
||||||
"onSearch must return non-null future");
|
@Nullable LibraryResult<Void> result = tryGetFutureResult(future);
|
||||||
|
if (result != null) {
|
||||||
|
maybeUpdateLegacyErrorState(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetSearchResultOnHandler(
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetSearchResultOnHandler(
|
||||||
@ -222,17 +237,13 @@ import java.util.concurrent.Future;
|
|||||||
int page,
|
int page,
|
||||||
int pageSize,
|
int pageSize,
|
||||||
@Nullable LibraryParams params) {
|
@Nullable LibraryParams params) {
|
||||||
// onGetSearchResult is defined to return a non-null result but it's implemented by
|
|
||||||
// applications, so we explicitly null-check the result to fail early if an app accidentally
|
|
||||||
// returns null.
|
|
||||||
ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future =
|
ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future =
|
||||||
checkNotNull(
|
callback.onGetSearchResult(instance, browser, query, page, pageSize, params);
|
||||||
callback.onGetSearchResult(instance, browser, query, page, pageSize, params),
|
|
||||||
"onGetSearchResult must return non-null future");
|
|
||||||
future.addListener(
|
future.addListener(
|
||||||
() -> {
|
() -> {
|
||||||
@Nullable LibraryResult<ImmutableList<MediaItem>> result = tryGetFutureResult(future);
|
@Nullable LibraryResult<ImmutableList<MediaItem>> result = tryGetFutureResult(future);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
maybeUpdateLegacyErrorState(result);
|
||||||
verifyResultItems(result, pageSize);
|
verifyResultItems(result, pageSize);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -277,6 +288,27 @@ import java.util.concurrent.Future;
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
playerWrapper.clearLegacyErrorStatus();
|
||||||
|
getSessionCompat().setPlaybackState(playerWrapper.createPlaybackStateCompat());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static <T> T tryGetFutureResult(Future<T> future) {
|
private static <T> T tryGetFutureResult(Future<T> future) {
|
||||||
checkState(future.isDone());
|
checkState(future.isDone());
|
||||||
|
@ -15,10 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.session;
|
package androidx.media3.session;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Util.postOrRun;
|
import static androidx.media3.common.util.Util.postOrRun;
|
||||||
|
import static androidx.media3.session.MediaConstants.STATUS_CODE_SUCCESS_COMPAT;
|
||||||
|
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
@ -52,8 +55,46 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
/* package */ class PlayerWrapper extends ForwardingPlayer {
|
/* package */ class PlayerWrapper extends ForwardingPlayer {
|
||||||
|
|
||||||
|
private int legacyStatusCode;
|
||||||
|
@Nullable private String legacyErrorMessage;
|
||||||
|
@Nullable private Bundle legacyErrorExtras;
|
||||||
|
|
||||||
public PlayerWrapper(Player player) {
|
public PlayerWrapper(Player player) {
|
||||||
super(player);
|
super(player);
|
||||||
|
legacyStatusCode = STATUS_CODE_SUCCESS_COMPAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the legacy error code.
|
||||||
|
*
|
||||||
|
* <p>This sets the legacy {@link PlaybackStateCompat} to {@link PlaybackStateCompat#STATE_ERROR}
|
||||||
|
* and calls {@link PlaybackStateCompat.Builder#setErrorMessage(int, CharSequence)} and {@link
|
||||||
|
* PlaybackStateCompat.Builder#setExtras(Bundle)} with the given arguments.
|
||||||
|
*
|
||||||
|
* <p>Use {@link #clearLegacyErrorStatus()} to clear the error state and to resume to the actual
|
||||||
|
* playback state reflecting the player.
|
||||||
|
*
|
||||||
|
* @param errorCode The legacy error code.
|
||||||
|
* @param errorMessage The legacy error message.
|
||||||
|
* @param extras The extras.
|
||||||
|
*/
|
||||||
|
public void setLegacyErrorStatus(int errorCode, String errorMessage, Bundle extras) {
|
||||||
|
checkState(errorCode != STATUS_CODE_SUCCESS_COMPAT);
|
||||||
|
legacyStatusCode = errorCode;
|
||||||
|
legacyErrorMessage = errorMessage;
|
||||||
|
legacyErrorExtras = extras;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the legacy status code. */
|
||||||
|
public int getLegacyStatusCode() {
|
||||||
|
return legacyStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears the legacy error status. */
|
||||||
|
public void clearLegacyErrorStatus() {
|
||||||
|
legacyStatusCode = STATUS_CODE_SUCCESS_COMPAT;
|
||||||
|
legacyErrorMessage = null;
|
||||||
|
legacyErrorExtras = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -702,6 +743,19 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PlaybackStateCompat createPlaybackStateCompat() {
|
public PlaybackStateCompat createPlaybackStateCompat() {
|
||||||
|
if (legacyStatusCode != STATUS_CODE_SUCCESS_COMPAT) {
|
||||||
|
return new PlaybackStateCompat.Builder()
|
||||||
|
.setState(
|
||||||
|
PlaybackStateCompat.STATE_ERROR,
|
||||||
|
/* position= */ PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN,
|
||||||
|
/* playbackSpeed= */ 0,
|
||||||
|
/* updateTime= */ SystemClock.elapsedRealtime())
|
||||||
|
.setActions(0)
|
||||||
|
.setBufferedPosition(0)
|
||||||
|
.setErrorMessage(legacyStatusCode, checkNotNull(legacyErrorMessage))
|
||||||
|
.setExtras(checkNotNull(legacyErrorExtras))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
@Nullable PlaybackException playerError = getPlayerError();
|
@Nullable PlaybackException playerError = getPlayerError();
|
||||||
int state =
|
int state =
|
||||||
MediaUtils.convertToPlaybackStateCompatState(
|
MediaUtils.convertToPlaybackStateCompatState(
|
||||||
|
@ -28,4 +28,5 @@
|
|||||||
<string name="media3_controls_seek_back_description">Seek back</string>
|
<string name="media3_controls_seek_back_description">Seek back</string>
|
||||||
<!-- Accessibility description for a 'seek forward' or 'fast-forward' button on a media notification. [CHAR LIMIT=NONE] -->
|
<!-- Accessibility description for a 'seek forward' or 'fast-forward' button on a media notification. [CHAR LIMIT=NONE] -->
|
||||||
<string name="media3_controls_seek_forward_description">Seek forward</string>
|
<string name="media3_controls_seek_forward_description">Seek forward</string>
|
||||||
|
<string name="authentication_required">Authentication required</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -36,6 +36,9 @@ public class MediaBrowserConstants {
|
|||||||
public static final String PARENT_ID_LONG_LIST = "parent_id_long_list";
|
public static final String PARENT_ID_LONG_LIST = "parent_id_long_list";
|
||||||
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_KEY_ERROR_RESOLUTION_ACTION_LABEL =
|
||||||
|
"parent_auth_expired_error_label";
|
||||||
|
|
||||||
public static final List<String> GET_CHILDREN_RESULT = new ArrayList<>();
|
public static final List<String> GET_CHILDREN_RESULT = new ArrayList<>();
|
||||||
public static final int CHILDREN_COUNT = 100;
|
public static final int CHILDREN_COUNT = 100;
|
||||||
|
@ -32,6 +32,8 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID
|
|||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM_WITH_METADATA;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM_WITH_METADATA;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_PLAYABLE_ITEM;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_PLAYABLE_ITEM;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID;
|
||||||
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR;
|
||||||
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
|
||||||
@ -59,6 +61,7 @@ import android.support.v4.media.MediaBrowserCompat.MediaItem;
|
|||||||
import android.support.v4.media.MediaBrowserCompat.SearchCallback;
|
import android.support.v4.media.MediaBrowserCompat.SearchCallback;
|
||||||
import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
|
import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
|
||||||
import android.support.v4.media.MediaDescriptionCompat;
|
import android.support.v4.media.MediaDescriptionCompat;
|
||||||
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import androidx.media3.test.session.common.TestUtils;
|
import androidx.media3.test.session.common.TestUtils;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@ -301,6 +304,47 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
|
|||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getChildren_authErrorResult() throws InterruptedException {
|
||||||
|
String testParentId = PARENT_ID_AUTH_EXPIRED_ERROR;
|
||||||
|
connectAndWait();
|
||||||
|
CountDownLatch errorLatch = new CountDownLatch(1);
|
||||||
|
browserCompat.subscribe(
|
||||||
|
testParentId,
|
||||||
|
new SubscriptionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError(String parentId) {
|
||||||
|
assertThat(parentId).isEqualTo(testParentId);
|
||||||
|
errorLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThat(errorLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
assertThat(lastReportedPlaybackStateCompat).isNotNull();
|
||||||
|
assertThat(lastReportedPlaybackStateCompat.getState())
|
||||||
|
.isEqualTo(PlaybackStateCompat.STATE_ERROR);
|
||||||
|
assertThat(
|
||||||
|
lastReportedPlaybackStateCompat
|
||||||
|
.getExtras()
|
||||||
|
.getString(MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT))
|
||||||
|
.isEqualTo(PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL);
|
||||||
|
|
||||||
|
CountDownLatch successLatch = new CountDownLatch(1);
|
||||||
|
browserCompat.subscribe(
|
||||||
|
PARENT_ID,
|
||||||
|
new SubscriptionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onChildrenLoaded(String parentId, List<MediaItem> children) {
|
||||||
|
assertThat(parentId).isEqualTo(PARENT_ID);
|
||||||
|
successLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThat(successLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
// Any successful calls remove the error state,
|
||||||
|
assertThat(lastReportedPlaybackStateCompat.getState())
|
||||||
|
.isNotEqualTo(PlaybackStateCompat.STATE_ERROR);
|
||||||
|
assertThat(lastReportedPlaybackStateCompat.getExtras()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getChildren_emptyResult() throws InterruptedException {
|
public void getChildren_emptyResult() throws InterruptedException {
|
||||||
String testParentId = PARENT_ID_NO_CHILDREN;
|
String testParentId = PARENT_ID_NO_CHILDREN;
|
||||||
|
@ -25,6 +25,8 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.v4.media.MediaBrowserCompat;
|
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 androidx.annotation.Nullable;
|
||||||
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;
|
||||||
@ -56,7 +58,9 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
|
|||||||
Context context;
|
Context context;
|
||||||
TestHandler handler;
|
TestHandler handler;
|
||||||
MediaBrowserCompat browserCompat;
|
MediaBrowserCompat browserCompat;
|
||||||
|
@Nullable MediaControllerCompat controllerCompat;
|
||||||
TestConnectionCallback connectionCallback;
|
TestConnectionCallback connectionCallback;
|
||||||
|
@Nullable PlaybackStateCompat lastReportedPlaybackStateCompat;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
@ -117,6 +121,8 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
|
|||||||
public final CountDownLatch suspendedLatch = new CountDownLatch(1);
|
public final CountDownLatch suspendedLatch = new CountDownLatch(1);
|
||||||
public final CountDownLatch failedLatch = new CountDownLatch(1);
|
public final CountDownLatch failedLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@Nullable MediaControllerCompat.Callback controllerCompatCallback;
|
||||||
|
|
||||||
TestConnectionCallback() {
|
TestConnectionCallback() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -124,19 +130,38 @@ public class MediaBrowserCompatWithMediaSessionServiceTest {
|
|||||||
@Override
|
@Override
|
||||||
public void onConnected() {
|
public void onConnected() {
|
||||||
super.onConnected();
|
super.onConnected();
|
||||||
|
// Make browser's internal handler to be initialized with test thread.
|
||||||
|
controllerCompat = new MediaControllerCompat(context, browserCompat.getSessionToken());
|
||||||
|
controllerCompatCallback =
|
||||||
|
new MediaControllerCompat.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onPlaybackStateChanged(PlaybackStateCompat state) {
|
||||||
|
lastReportedPlaybackStateCompat = state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
controllerCompat.registerCallback(controllerCompatCallback);
|
||||||
connectedLatch.countDown();
|
connectedLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionSuspended() {
|
public void onConnectionSuspended() {
|
||||||
super.onConnectionSuspended();
|
super.onConnectionSuspended();
|
||||||
|
unregisterControllerCallback();
|
||||||
suspendedLatch.countDown();
|
suspendedLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionFailed() {
|
public void onConnectionFailed() {
|
||||||
super.onConnectionFailed();
|
super.onConnectionFailed();
|
||||||
|
unregisterControllerCallback();
|
||||||
failedLatch.countDown();
|
failedLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void unregisterControllerCallback() {
|
||||||
|
if (controllerCompat != null && controllerCompatCallback != null) {
|
||||||
|
controllerCompat.unregisterCallback(controllerCompatCallback);
|
||||||
|
}
|
||||||
|
controllerCompatCallback = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
package androidx.media3.session;
|
package androidx.media3.session;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.session.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.MediaTestUtils.assertLibraryParamsEquals;
|
import static androidx.media3.session.MediaTestUtils.assertLibraryParamsEquals;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION;
|
||||||
@ -30,6 +32,8 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID
|
|||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID;
|
||||||
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR;
|
||||||
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS;
|
||||||
@ -47,8 +51,10 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIB
|
|||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
@ -232,6 +238,21 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
return Futures.immediateFuture(LibraryResult.ofItemList(list, params));
|
return Futures.immediateFuture(LibraryResult.ofItemList(list, params));
|
||||||
} else if (PARENT_ID_ERROR.equals(parentId)) {
|
} else if (PARENT_ID_ERROR.equals(parentId)) {
|
||||||
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||||
|
} else if (PARENT_ID_AUTH_EXPIRED_ERROR.equals(parentId)) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
Intent signInIntent = new Intent("action");
|
||||||
|
int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||||
|
bundle.putParcelable(
|
||||||
|
EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT,
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
getApplicationContext(), /* requestCode= */ 0, signInIntent, flags));
|
||||||
|
bundle.putString(
|
||||||
|
EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT,
|
||||||
|
PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL);
|
||||||
|
return Futures.immediateFuture(
|
||||||
|
LibraryResult.ofError(
|
||||||
|
LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
||||||
|
new LibraryParams.Builder().setExtras(bundle).build()));
|
||||||
}
|
}
|
||||||
// Includes the case of PARENT_ID_NO_CHILDREN.
|
// Includes the case of PARENT_ID_NO_CHILDREN.
|
||||||
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
|
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user