Add default implementation of Callback.onSubscribe
The library already maintains the subscribed controllers internally. This change adds `MediaLibrarySession.getSubscribedControllers(mediaId)` to access subscribed controllers for a given media ID. To accept a subscription, `MediaLibraryService.Callback.onSubscribe` is required to return `RESULT_SUCCESS`. So far, this isn't the case for the default implementation of the library. This change implements `Callback.onSubscribe` to conditionally provide `RESULT_SUCCESS`. The default calls `Callback.onGetItem(mediaId)` to assess the availability of the media item. If the app retruns `RESULT_SUCCESS` with a browsable item, the subscription is accepted. If receiving a valid item fails, the subscription is rejected. Issue: androidx/media#561 PiperOrigin-RevId: 568925079
This commit is contained in:
parent
1df2210cf4
commit
99086b4007
@ -78,6 +78,13 @@
|
||||
all supported API levels.
|
||||
* Fix bug where `MediaController.getCurrentPosition()` is not advancing
|
||||
when connected to a legacy `MediaSessionCompat`.
|
||||
* Add `MediaLibrarySession.getSubscribedControllers(mediaId)` for
|
||||
convenience.
|
||||
* Override `MediaLibrarySession.Callback.onSubscribe()` to assert the
|
||||
availability of the parent Id for which the controller subscribes. If
|
||||
successful, the subscription is accepted and `notifyChildrenChanged()`
|
||||
is called immediately to inform the browser
|
||||
([#561](https://github.com/androidx/media/issues/561)).
|
||||
* UI:
|
||||
* Downloads:
|
||||
* OkHttp Extension:
|
||||
|
@ -165,21 +165,6 @@ class PlaybackService : MediaLibraryService() {
|
||||
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
|
||||
}
|
||||
|
||||
override fun onSubscribe(
|
||||
session: MediaLibrarySession,
|
||||
browser: ControllerInfo,
|
||||
parentId: String,
|
||||
params: LibraryParams?
|
||||
): ListenableFuture<LibraryResult<Void>> {
|
||||
val children =
|
||||
MediaItemTree.getChildren(parentId)
|
||||
?: return Futures.immediateFuture(
|
||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
|
||||
)
|
||||
session.notifyChildrenChanged(browser, parentId, children.size, params)
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid())
|
||||
}
|
||||
|
||||
override fun onGetChildren(
|
||||
session: MediaLibrarySession,
|
||||
browser: ControllerInfo,
|
||||
|
@ -187,16 +187,19 @@ public final class MediaBrowser extends MediaController {
|
||||
public interface Listener extends MediaController.Listener {
|
||||
|
||||
/**
|
||||
* Called when there's change in the parent's children after you've subscribed to the parent
|
||||
* Called when there's a change in the parent's children after you've subscribed to the parent
|
||||
* with {@link #subscribe}.
|
||||
*
|
||||
* <p>This method is called when the library service called {@link
|
||||
* MediaLibraryService.MediaLibrarySession#notifyChildrenChanged} for the parent.
|
||||
* <p>This method is called when the app calls {@link
|
||||
* MediaLibraryService.MediaLibrarySession#notifyChildrenChanged} for the parent, or it is
|
||||
* called by the library immediately after calling {@link MediaBrowser#subscribe(String,
|
||||
* LibraryParams)}.
|
||||
*
|
||||
* @param browser The browser for this event.
|
||||
* @param parentId The non-empty parent id that you've specified with {@link #subscribe(String,
|
||||
* LibraryParams)}.
|
||||
* @param itemCount The number of children.
|
||||
* @param itemCount The number of children, or {@link Integer#MAX_VALUE} if the number of items
|
||||
* is unknown.
|
||||
* @param params The optional parameters from the library service. Can be differ from the {@code
|
||||
* params} that you've specified with {@link #subscribe(String, LibraryParams)}.
|
||||
*/
|
||||
|
@ -19,6 +19,8 @@ import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotEmpty;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||
import static androidx.media3.session.LibraryResult.ofVoid;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
@ -220,16 +222,28 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
* Called when a {@link MediaBrowser} subscribes to the given parent id by {@link
|
||||
* MediaBrowser#subscribe(String, LibraryParams)}.
|
||||
*
|
||||
* <p>See {@link #getSubscribedControllers(String)} also.
|
||||
*
|
||||
* <p>By default, the library calls {@link Callback#onGetItem(MediaLibrarySession,
|
||||
* ControllerInfo, String)} for the {@code parentId} that the browser requests to subscribe
|
||||
* to. If onGetItem returns {@link LibraryResult#RESULT_SUCCESS} with a {@linkplain
|
||||
* MediaMetadata#isBrowsable browsable item}, the subscription is accepted and {@link
|
||||
* MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, LibraryParams)} is
|
||||
* immediately called with an itemCount of {@link Integer#MAX_VALUE}. In all other cases, the
|
||||
* subscription is rejected and a result value different to {@link
|
||||
* LibraryResult#RESULT_SUCCESS} is returned from this method.
|
||||
*
|
||||
* <p>To implement a different behavior, an app can safely override this method without
|
||||
* calling super. Return a result code {@link LibraryResult#RESULT_SUCCESS} to accept the
|
||||
* subscription, or return a result different to {@link LibraryResult#RESULT_SUCCESS} to
|
||||
* prevent controllers from subscribing.
|
||||
*
|
||||
* <p>Return a {@link ListenableFuture} to send a {@link LibraryResult} back to the browser
|
||||
* asynchronously. You can also return a {@link LibraryResult} directly by using Guava's
|
||||
* {@link Futures#immediateFuture(Object)}.
|
||||
*
|
||||
* <p>The {@link LibraryResult#params} should be the same as the given {@link LibraryParams
|
||||
* params}.
|
||||
*
|
||||
* <p>It's your responsibility to keep subscriptions and call {@link
|
||||
* MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, LibraryParams)} when
|
||||
* the children of the parent are changed until it's {@link #onUnsubscribe unsubscribed}.
|
||||
* <p>The {@link LibraryResult#params} returned to the caller should be the same as the {@link
|
||||
* LibraryParams params} passed into this method.
|
||||
*
|
||||
* <p>Interoperability: This will be called by {@link
|
||||
* android.support.v4.media.MediaBrowserCompat#subscribe}, but won't be called by {@link
|
||||
@ -247,17 +261,44 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
ControllerInfo browser,
|
||||
String parentId,
|
||||
@Nullable LibraryParams params) {
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED));
|
||||
return Util.transformFutureAsync(
|
||||
onGetItem(session, browser, parentId),
|
||||
result -> {
|
||||
if (result.resultCode != RESULT_SUCCESS
|
||||
|| result.value == null
|
||||
|| result.value.mediaMetadata.isBrowsable == null
|
||||
|| !result.value.mediaMetadata.isBrowsable) {
|
||||
// Reject subscription if no browsable item for the parent media ID is returned.
|
||||
return Futures.immediateFuture(
|
||||
LibraryResult.ofError(
|
||||
result.resultCode != RESULT_SUCCESS
|
||||
? result.resultCode
|
||||
: LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
if (browser.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) {
|
||||
// For legacy browsers, android.service.media.MediaBrowserService already calls
|
||||
// MediaLibraryServiceLegacyStub.onLoadChildren() to send the current media items
|
||||
// to the browser callback. So we call back Media3 browsers only.
|
||||
session.notifyChildrenChanged(
|
||||
browser, parentId, /* itemCount= */ Integer.MAX_VALUE, params);
|
||||
}
|
||||
return Futures.immediateFuture(ofVoid());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a {@link MediaBrowser} unsubscribes from the given parent id by {@link
|
||||
* Called when a {@link MediaBrowser} unsubscribes from the given parent ID by {@link
|
||||
* MediaBrowser#unsubscribe(String)}.
|
||||
*
|
||||
* <p>Return a {@link ListenableFuture} to send a {@link LibraryResult} back to the browser
|
||||
* asynchronously. You can also return a {@link LibraryResult} directly by using Guava's
|
||||
* {@link Futures#immediateFuture(Object)}.
|
||||
*
|
||||
* <p>Apps normally don't need to implement this method, because the library maintains the
|
||||
* subscribed controllers internally and an app can use {@link
|
||||
* #getSubscribedControllers(String)} to get subscribed controllers for which to call {@link
|
||||
* #notifyChildrenChanged}.
|
||||
*
|
||||
* <p>Interoperability: This will be called by {@link
|
||||
* android.support.v4.media.MediaBrowserCompat#unsubscribe}, but won't be called by {@link
|
||||
* android.media.browse.MediaBrowser#unsubscribe}.
|
||||
@ -270,7 +311,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
*/
|
||||
default ListenableFuture<LibraryResult<Void>> onUnsubscribe(
|
||||
MediaLibrarySession session, ControllerInfo browser, String parentId) {
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED));
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -554,16 +595,31 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies a browser that is {@link Callback#onSubscribe subscribing} to the change to a
|
||||
* parent's children. If the browser isn't subscribing to the parent, nothing will happen.
|
||||
* Returns the controllers that are currently subscribed to the given {@code mediaId}.
|
||||
*
|
||||
* <p>This only tells the number of child {@link MediaItem media items}. {@link
|
||||
* Callback#onGetChildren} will be called by the browser afterwards to get the list of {@link
|
||||
* MediaItem media items}.
|
||||
* <p>Use the returned {@linkplain ControllerInfo controller infos} to call {@link
|
||||
* #notifyChildrenChanged} in case the children of the media item with the given media ID have
|
||||
* changed and the connected controller should fetch them again.
|
||||
*
|
||||
* <p>Note that calling {@link #notifyChildrenChanged} for a controller that didn't subscribe to
|
||||
* the media ID results in a no-op.
|
||||
*
|
||||
* @param mediaId The ID of the media item for which to get subscribed controllers.
|
||||
* @return A list with the subscribed controllers, may be empty.
|
||||
*/
|
||||
@UnstableApi
|
||||
public ImmutableList<ControllerInfo> getSubscribedControllers(String mediaId) {
|
||||
return getImpl().getSubscribedControllers(mediaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies a browser that is {@link Callback#onSubscribe subscribed} to a browsable media item
|
||||
* that the children of the item have changed. This method is also called immediately after
|
||||
* subscribing was successful.
|
||||
*
|
||||
* @param browser The browser to notify.
|
||||
* @param parentId The non-empty id of the parent with changes to its children.
|
||||
* @param itemCount The number of children.
|
||||
* @param itemCount The number of children, or {@link Integer#MAX_VALUE} if unknown.
|
||||
* @param params The parameters given by {@link Callback#onSubscribe}.
|
||||
*/
|
||||
public void notifyChildrenChanged(
|
||||
|
@ -17,8 +17,6 @@ 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.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.postOrRun;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||
@ -32,25 +30,25 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.ArrayMap;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.BitmapLoader;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||
import androidx.media3.session.MediaSession.ControllerCb;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@ -61,12 +59,10 @@ import java.util.concurrent.Future;
|
||||
/* package */ class MediaLibrarySessionImpl extends MediaSessionImpl {
|
||||
|
||||
private static final String RECENT_LIBRARY_ROOT_MEDIA_ID = "androidx.media3.session.recent.root";
|
||||
|
||||
private final MediaLibrarySession instance;
|
||||
private final MediaLibrarySession.Callback callback;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private final ArrayMap<ControllerCb, Set<String>> subscriptions;
|
||||
private final HashMultimap<String, ControllerInfo> parentIdToSubscribedControllers;
|
||||
private final HashMultimap<ControllerCb, String> controllerToSubscribedParentIds;
|
||||
|
||||
/** Creates an instance. */
|
||||
public MediaLibrarySessionImpl(
|
||||
@ -93,7 +89,8 @@ import java.util.concurrent.Future;
|
||||
playIfSuppressed);
|
||||
this.instance = instance;
|
||||
this.callback = callback;
|
||||
subscriptions = new ArrayMap<>();
|
||||
parentIdToSubscribedControllers = HashMultimap.create();
|
||||
controllerToSubscribedParentIds = HashMultimap.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -116,48 +113,6 @@ import java.util.concurrent.Future;
|
||||
&& legacyStub.getConnectedControllersManager().isConnected(controller);
|
||||
}
|
||||
|
||||
public void notifyChildrenChanged(
|
||||
String parentId, int itemCount, @Nullable LibraryParams params) {
|
||||
dispatchRemoteControllerTaskWithoutReturn(
|
||||
(callback, seq) -> {
|
||||
if (isSubscribed(callback, parentId)) {
|
||||
callback.onChildrenChanged(seq, parentId, itemCount, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void notifyChildrenChanged(
|
||||
ControllerInfo browser, String parentId, int itemCount, @Nullable LibraryParams params) {
|
||||
if (isMediaNotificationControllerConnected() && isMediaNotificationController(browser)) {
|
||||
ControllerInfo systemUiBrowser = getSystemUiControllerInfo();
|
||||
if (systemUiBrowser == null) {
|
||||
return;
|
||||
}
|
||||
browser = systemUiBrowser;
|
||||
}
|
||||
dispatchRemoteControllerTaskWithoutReturn(
|
||||
browser,
|
||||
(callback, seq) -> {
|
||||
if (!isSubscribed(callback, parentId)) {
|
||||
return;
|
||||
}
|
||||
callback.onChildrenChanged(seq, parentId, itemCount, params);
|
||||
});
|
||||
}
|
||||
|
||||
public void notifySearchResultChanged(
|
||||
ControllerInfo browser, String query, int itemCount, @Nullable LibraryParams params) {
|
||||
if (isMediaNotificationControllerConnected() && isMediaNotificationController(browser)) {
|
||||
ControllerInfo systemUiBrowser = getSystemUiControllerInfo();
|
||||
if (systemUiBrowser == null) {
|
||||
return;
|
||||
}
|
||||
browser = systemUiBrowser;
|
||||
}
|
||||
dispatchRemoteControllerTaskWithoutReturn(
|
||||
browser, (callback, seq) -> callback.onSearchResultChanged(seq, query, itemCount, params));
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRootOnHandler(
|
||||
ControllerInfo browser, @Nullable LibraryParams params) {
|
||||
if (params != null && params.isRecent && isSystemUiController(browser)) {
|
||||
@ -185,7 +140,7 @@ import java.util.concurrent.Future;
|
||||
maybeUpdateLegacyErrorState(result);
|
||||
}
|
||||
},
|
||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
||||
this::postOrRunOnApplicationHandler);
|
||||
return future;
|
||||
}
|
||||
|
||||
@ -228,7 +183,7 @@ import java.util.concurrent.Future;
|
||||
verifyResultItems(result, pageSize);
|
||||
}
|
||||
},
|
||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
||||
this::postOrRunOnApplicationHandler);
|
||||
return future;
|
||||
}
|
||||
|
||||
@ -243,21 +198,16 @@ import java.util.concurrent.Future;
|
||||
maybeUpdateLegacyErrorState(result);
|
||||
}
|
||||
},
|
||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
||||
this::postOrRunOnApplicationHandler);
|
||||
return future;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<Void>> onSubscribeOnHandler(
|
||||
ControllerInfo browser, String parentId, @Nullable LibraryParams params) {
|
||||
ControllerCb controller = checkStateNotNull(browser.getControllerCb());
|
||||
synchronized (lock) {
|
||||
@Nullable Set<String> subscription = subscriptions.get(controller);
|
||||
if (subscription == null) {
|
||||
subscription = new HashSet<>();
|
||||
subscriptions.put(controller, subscription);
|
||||
}
|
||||
subscription.add(parentId);
|
||||
}
|
||||
|
||||
ControllerCb controllerCb = checkNotNull(browser.getControllerCb());
|
||||
controllerToSubscribedParentIds.put(controllerCb, parentId);
|
||||
parentIdToSubscribedControllers.put(parentId, browser);
|
||||
|
||||
// Call callbacks after adding it to the subscription list because library session may want
|
||||
// to call notifyChildrenChanged() in the callback.
|
||||
@ -270,31 +220,65 @@ import java.util.concurrent.Future;
|
||||
instance, resolveControllerInfoForCallback(browser), parentId, params),
|
||||
"onSubscribe must return non-null future");
|
||||
|
||||
// When error happens, remove from the subscription list.
|
||||
future.addListener(
|
||||
() -> {
|
||||
@Nullable LibraryResult<Void> result = tryGetFutureResult(future);
|
||||
if (result == null || result.resultCode != RESULT_SUCCESS) {
|
||||
removeSubscription(controller, parentId);
|
||||
// Remove subscription in case of an error.
|
||||
removeSubscription(browser, parentId);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
this::postOrRunOnApplicationHandler);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
public ImmutableList<ControllerInfo> getSubscribedControllers(String mediaId) {
|
||||
return ImmutableList.copyOf(parentIdToSubscribedControllers.get(mediaId));
|
||||
}
|
||||
|
||||
private boolean isSubscribed(ControllerCb controllerCb, String parentId) {
|
||||
return controllerToSubscribedParentIds.containsEntry(controllerCb, parentId);
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<Void>> onUnsubscribeOnHandler(
|
||||
ControllerInfo browser, String parentId) {
|
||||
ListenableFuture<LibraryResult<Void>> future =
|
||||
callback.onUnsubscribe(instance, resolveControllerInfoForCallback(browser), parentId);
|
||||
|
||||
future.addListener(
|
||||
() -> removeSubscription(checkStateNotNull(browser.getControllerCb()), parentId),
|
||||
MoreExecutors.directExecutor());
|
||||
|
||||
() -> removeSubscription(browser, parentId), this::postOrRunOnApplicationHandler);
|
||||
return future;
|
||||
}
|
||||
|
||||
public void notifyChildrenChanged(
|
||||
String parentId, int itemCount, @Nullable LibraryParams params) {
|
||||
dispatchRemoteControllerTaskWithoutReturn(
|
||||
(callback, seq) -> {
|
||||
if (isSubscribed(callback, parentId)) {
|
||||
callback.onChildrenChanged(seq, parentId, itemCount, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void notifyChildrenChanged(
|
||||
ControllerInfo browser, String parentId, int itemCount, @Nullable LibraryParams params) {
|
||||
if (isMediaNotificationControllerConnected() && isMediaNotificationController(browser)) {
|
||||
ControllerInfo systemUiBrowser = getSystemUiControllerInfo();
|
||||
if (systemUiBrowser == null) {
|
||||
return;
|
||||
}
|
||||
browser = systemUiBrowser;
|
||||
}
|
||||
dispatchRemoteControllerTaskWithoutReturn(
|
||||
browser,
|
||||
(callback, seq) -> {
|
||||
if (!isSubscribed(callback, parentId)) {
|
||||
return;
|
||||
}
|
||||
callback.onChildrenChanged(seq, parentId, itemCount, params);
|
||||
});
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<Void>> onSearchOnHandler(
|
||||
ControllerInfo browser, String query, @Nullable LibraryParams params) {
|
||||
ListenableFuture<LibraryResult<Void>> future =
|
||||
@ -306,7 +290,7 @@ import java.util.concurrent.Future;
|
||||
maybeUpdateLegacyErrorState(result);
|
||||
}
|
||||
},
|
||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
||||
this::postOrRunOnApplicationHandler);
|
||||
return future;
|
||||
}
|
||||
|
||||
@ -327,10 +311,33 @@ import java.util.concurrent.Future;
|
||||
verifyResultItems(result, pageSize);
|
||||
}
|
||||
},
|
||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
||||
this::postOrRunOnApplicationHandler);
|
||||
return future;
|
||||
}
|
||||
|
||||
public void notifySearchResultChanged(
|
||||
ControllerInfo browser, String query, int itemCount, @Nullable LibraryParams params) {
|
||||
if (isMediaNotificationControllerConnected() && isMediaNotificationController(browser)) {
|
||||
ControllerInfo systemUiBrowser = getSystemUiControllerInfo();
|
||||
if (systemUiBrowser == null) {
|
||||
return;
|
||||
}
|
||||
browser = systemUiBrowser;
|
||||
}
|
||||
dispatchRemoteControllerTaskWithoutReturn(
|
||||
browser, (callback, seq) -> callback.onSearchResultChanged(seq, query, itemCount, params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectedOnHandler(ControllerInfo controller) {
|
||||
ControllerCb controllerCb = checkNotNull(controller.getControllerCb());
|
||||
Set<String> subscriptions = controllerToSubscribedParentIds.get(controllerCb);
|
||||
for (String parentId : ImmutableSet.copyOf(subscriptions)) {
|
||||
removeSubscription(controller, parentId);
|
||||
}
|
||||
super.onDisconnectedOnHandler(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected MediaLibraryServiceLegacyStub getLegacyBrowserService() {
|
||||
@ -358,16 +365,6 @@ import java.util.concurrent.Future;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSubscribed(ControllerCb callback, String parentId) {
|
||||
synchronized (lock) {
|
||||
@Nullable Set<String> subscriptions = this.subscriptions.get(callback);
|
||||
if (subscriptions == null || !subscriptions.contains(parentId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void maybeUpdateLegacyErrorState(LibraryResult<?> result) {
|
||||
PlayerWrapper playerWrapper = getPlayerWrapper();
|
||||
if (result.resultCode == RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED
|
||||
@ -410,16 +407,14 @@ import java.util.concurrent.Future;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSubscription(ControllerCb controllerCb, String parentId) {
|
||||
synchronized (lock) {
|
||||
@Nullable Set<String> subscription = subscriptions.get(controllerCb);
|
||||
if (subscription != null) {
|
||||
subscription.remove(parentId);
|
||||
if (subscription.isEmpty()) {
|
||||
subscriptions.remove(controllerCb);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void removeSubscription(ControllerInfo controllerInfo, String parentId) {
|
||||
ControllerCb controllerCb = checkNotNull(controllerInfo.getControllerCb());
|
||||
parentIdToSubscribedControllers.remove(parentId, controllerInfo);
|
||||
controllerToSubscribedParentIds.remove(controllerCb, parentId);
|
||||
}
|
||||
|
||||
private void postOrRunOnApplicationHandler(Runnable runnable) {
|
||||
Util.postOrRun(getApplicationHandler(), runnable);
|
||||
}
|
||||
|
||||
private ListenableFuture<LibraryResult<ImmutableList<MediaItem>>>
|
||||
|
@ -97,7 +97,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
private static final SessionResult RESULT_WHEN_CLOSED = new SessionResult(RESULT_INFO_SKIPPED);
|
||||
|
||||
protected final Object lock = new Object();
|
||||
private final Object lock = new Object();
|
||||
|
||||
private final Uri sessionUri;
|
||||
private final PlayerInfoChangedHandler onPlayerInfoChangedHandler;
|
||||
|
@ -56,16 +56,16 @@ public class MediaBrowserConstants {
|
||||
public static final List<String> SEARCH_RESULT = new ArrayList<>();
|
||||
public static final int SEARCH_RESULT_COUNT = 50;
|
||||
|
||||
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL =
|
||||
"subscribe_id_notify_children_changed_to_all";
|
||||
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE =
|
||||
"subscribe_id_notify_children_changed_to_one";
|
||||
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID =
|
||||
"subscribe_id_notify_children_changed_to_all_with_non_subscribed_id";
|
||||
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID =
|
||||
"subscribe_id_notify_children_changed_to_one_with_non_subscribed_id";
|
||||
public static final int NOTIFY_CHILDREN_CHANGED_ITEM_COUNT = 101;
|
||||
public static final Bundle NOTIFY_CHILDREN_CHANGED_EXTRAS = TestUtils.createTestBundle();
|
||||
public static final String SUBSCRIBE_PARENT_ID_1 = "subscribe_parent_id_1";
|
||||
public static final String SUBSCRIBE_PARENT_ID_2 = "subscribe_parent_id_2";
|
||||
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_MEDIA_ID =
|
||||
"notify_children_changed_media_id";
|
||||
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_ITEM_COUNT =
|
||||
"notify_children_changed_item_count";
|
||||
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_DELAY_MS =
|
||||
"notify_children_changed_delay";
|
||||
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_BROADCAST =
|
||||
"notify_children_changed_broadcast";
|
||||
|
||||
public static final String CUSTOM_ACTION = "customAction";
|
||||
public static final Bundle CUSTOM_ACTION_EXTRAS = new Bundle();
|
||||
|
@ -19,22 +19,18 @@ import static androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||
import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle;
|
||||
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
|
||||
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.PARENT_ID;
|
||||
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_VALUE;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
|
||||
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_PARENT_ID_1;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_2;
|
||||
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
|
||||
import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
|
||||
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
@ -48,6 +44,8 @@ import androidx.media3.test.session.common.TestUtils;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@ -431,7 +429,7 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
||||
@Test
|
||||
public void onChildrenChanged_calledWhenSubscribed() throws Exception {
|
||||
// This test uses MediaLibrarySession.notifyChildrenChanged().
|
||||
String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
|
||||
String expectedParentId = SUBSCRIBE_PARENT_ID_1;
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<String> parentIdRef = new AtomicReference<>();
|
||||
@ -453,7 +451,7 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
||||
LibraryResult<Void> result =
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(() -> browser.subscribe(expectedParentId, null))
|
||||
.postAndSync(() -> browser.subscribe(expectedParentId, /* params= */ null))
|
||||
.get(TIMEOUT_MS, MILLISECONDS);
|
||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
|
||||
@ -461,27 +459,23 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
||||
// notifyChildrenChanged() in its onSubscribe() method.
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(parentIdRef.get()).isEqualTo(expectedParentId);
|
||||
assertThat(itemCountRef.get()).isEqualTo(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT);
|
||||
MediaTestUtils.assertLibraryParamsEquals(paramsRef.get(), NOTIFY_CHILDREN_CHANGED_EXTRAS);
|
||||
assertThat(itemCountRef.get()).isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onChildrenChanged_calledWhenSubscribed2() throws Exception {
|
||||
// This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
|
||||
String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
|
||||
public void onChildrenChanged_calledWhenSubscribedAndWithDelay() throws Exception {
|
||||
String expectedParentId = SUBSCRIBE_PARENT_ID_2;
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<String> parentIdRef = new AtomicReference<>();
|
||||
AtomicInteger itemCountRef = new AtomicInteger();
|
||||
AtomicReference<LibraryParams> paramsRef = new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
List<String> parentIds = new ArrayList<>();
|
||||
List<Integer> itemCounts = new ArrayList<>();
|
||||
MediaBrowser.Listener browserListenerProxy =
|
||||
new MediaBrowser.Listener() {
|
||||
@Override
|
||||
public void onChildrenChanged(
|
||||
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
|
||||
parentIdRef.set(parentId);
|
||||
itemCountRef.set(itemCount);
|
||||
paramsRef.set(params);
|
||||
parentIds.add(parentId);
|
||||
itemCounts.add(itemCount);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
@ -490,75 +484,108 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
||||
LibraryResult<Void> result =
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(() -> browser.subscribe(expectedParentId, null))
|
||||
.postAndSync(
|
||||
() -> {
|
||||
// Bundle to request to call onChildrenChanged() after a given delay.
|
||||
Bundle requestNotifyChildren =
|
||||
createNotifyChildrenChangedBundle(
|
||||
expectedParentId,
|
||||
/* itemCount= */ 12,
|
||||
/* delayMs= */ 250L,
|
||||
/* broadcast= */ false);
|
||||
return browser.subscribe(
|
||||
expectedParentId,
|
||||
new LibraryParams.Builder().setExtras(requestNotifyChildren).build());
|
||||
})
|
||||
.get(TIMEOUT_MS, MILLISECONDS);
|
||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
|
||||
// The MediaLibrarySession in MockMediaLibraryService is supposed to call
|
||||
// notifyChildrenChanged(ControllerInfo) in its onSubscribe() method.
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(parentIdRef.get()).isEqualTo(expectedParentId);
|
||||
assertThat(itemCountRef.get()).isEqualTo(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT);
|
||||
MediaTestUtils.assertLibraryParamsEquals(paramsRef.get(), NOTIFY_CHILDREN_CHANGED_EXTRAS);
|
||||
assertThat(parentIds).containsExactly(expectedParentId, expectedParentId);
|
||||
assertThat(itemCounts).containsExactly(Integer.MAX_VALUE, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onChildrenChanged_notCalledWhenNotSubscribed() throws Exception {
|
||||
// This test uses MediaLibrarySession.notifyChildrenChanged().
|
||||
String subscribedMediaId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
MediaBrowser.Listener browserListenerProxy =
|
||||
String mediaId1 = SUBSCRIBE_PARENT_ID_1;
|
||||
String mediaId2 = SUBSCRIBE_PARENT_ID_2;
|
||||
List<String> notifiedParentIds = new ArrayList<>();
|
||||
List<Integer> notifiedItemCounts = new ArrayList<>();
|
||||
CountDownLatch childrenChangedLatch = new CountDownLatch(4);
|
||||
CountDownLatch disconnectLatch = new CountDownLatch(2);
|
||||
MediaBrowser.Listener browserListener =
|
||||
new MediaBrowser.Listener() {
|
||||
@Override
|
||||
public void onChildrenChanged(
|
||||
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
|
||||
latch.countDown();
|
||||
notifiedParentIds.add(parentId);
|
||||
notifiedItemCounts.add(itemCount);
|
||||
childrenChangedLatch.countDown();
|
||||
}
|
||||
};
|
||||
|
||||
MediaBrowser browser = createBrowser(null, browserListenerProxy);
|
||||
LibraryResult<Void> result =
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(() -> browser.subscribe(subscribedMediaId, null))
|
||||
.get(TIMEOUT_MS, MILLISECONDS);
|
||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
|
||||
// The MediaLibrarySession in MockMediaLibraryService is supposed to call
|
||||
// notifyChildrenChanged() in its onSubscribe() method, but with a different media ID.
|
||||
// Therefore, onChildrenChanged() should not be called.
|
||||
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onChildrenChanged_notCalledWhenNotSubscribed2() throws Exception {
|
||||
// This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
|
||||
String subscribedMediaId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
MediaBrowser.Listener browserListenerProxy =
|
||||
new MediaBrowser.Listener() {
|
||||
@Override
|
||||
public void onChildrenChanged(
|
||||
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
|
||||
latch.countDown();
|
||||
public void onDisconnected(MediaController controller) {
|
||||
disconnectLatch.countDown();
|
||||
}
|
||||
};
|
||||
|
||||
MediaBrowser browser = createBrowser(null, browserListenerProxy);
|
||||
LibraryResult<Void> result =
|
||||
MediaBrowser browser1 = createBrowser(/* connectionHints= */ null, browserListener);
|
||||
MediaBrowser browser2 = createBrowser(/* connectionHints= */ null, browserListener);
|
||||
// Subscribe both browsers each to a different media IDs and request a second update after a
|
||||
// delay.
|
||||
LibraryResult<Void> subscriptionResult1 =
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(() -> browser.subscribe(subscribedMediaId, null))
|
||||
.postAndSync(
|
||||
() -> {
|
||||
Bundle requestNotifyChildren =
|
||||
createNotifyChildrenChangedBundle(
|
||||
mediaId1,
|
||||
/* itemCount= */ 123,
|
||||
/* delayMs= */ 200L,
|
||||
/* broadcast= */ true);
|
||||
return browser1.subscribe(
|
||||
mediaId1,
|
||||
new LibraryParams.Builder().setExtras(requestNotifyChildren).build());
|
||||
})
|
||||
.get(TIMEOUT_MS, MILLISECONDS);
|
||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
assertThat(subscriptionResult1.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
LibraryResult<Void> result2 =
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
Bundle requestNotifyChildren =
|
||||
createNotifyChildrenChangedBundle(
|
||||
mediaId2,
|
||||
/* itemCount= */ 567,
|
||||
/* delayMs= */ 200L,
|
||||
/* broadcast= */ true);
|
||||
return browser2.subscribe(
|
||||
mediaId2,
|
||||
new LibraryParams.Builder().setExtras(requestNotifyChildren).build());
|
||||
})
|
||||
.get(TIMEOUT_MS, MILLISECONDS);
|
||||
assertThat(result2.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
|
||||
// The MediaLibrarySession in MockMediaLibraryService is supposed to call
|
||||
// notifyChildrenChanged(ControllerInfo) in its onSubscribe() method, but
|
||||
// with a different media ID.
|
||||
// Therefore, onChildrenChanged() should not be called.
|
||||
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
|
||||
assertThat(childrenChangedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(notifiedParentIds)
|
||||
.containsExactly(
|
||||
mediaId1, // callback when subscribing browser1
|
||||
mediaId2, // callback when subscribing browser2
|
||||
mediaId1, // callback on first delayed notification
|
||||
mediaId2) // callback on second delayed notification
|
||||
.inOrder();
|
||||
assertThat(notifiedItemCounts)
|
||||
.containsExactly(Integer.MAX_VALUE, Integer.MAX_VALUE, 123, 567)
|
||||
.inOrder();
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
browser1.release();
|
||||
browser2.release();
|
||||
});
|
||||
assertThat(disconnectLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
}
|
||||
|
||||
private void setExpectedLibraryParam(MediaBrowser browser, LibraryParams params)
|
||||
|
@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.Player;
|
||||
@ -100,7 +101,8 @@ public class MediaLibraryServiceTest {
|
||||
return session.get();
|
||||
});
|
||||
// Create the remote browser to start the service.
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(token);
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(token, /* connectionHints= */ Bundle.EMPTY);
|
||||
// Get the started service instance after creation.
|
||||
MockMediaLibraryService service =
|
||||
(MockMediaLibraryService) testServiceRegistry.getServiceInstance();
|
||||
|
@ -15,11 +15,15 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SETUP_REQUIRED;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_1;
|
||||
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
@ -37,7 +41,9 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
@ -77,11 +83,13 @@ public class MediaLibrarySessionCallbackTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSubscribe() throws Exception {
|
||||
String testParentId = "testSubscribeId";
|
||||
public void onSubscribeUnsubscribe() throws Exception {
|
||||
String testParentId = "testSubscribeUnsubscribeId";
|
||||
LibraryParams testParams = MediaTestUtils.createLibraryParams();
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<LibraryParams> libraryParamsRef = new AtomicReference<>();
|
||||
List<ControllerInfo> subscribedControllers = new ArrayList<>();
|
||||
List<String> parentIds = new ArrayList<>();
|
||||
MediaLibrarySession.Callback sessionCallback =
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
@ -90,24 +98,154 @@ public class MediaLibrarySessionCallbackTest {
|
||||
ControllerInfo browser,
|
||||
String parentId,
|
||||
@Nullable LibraryParams params) {
|
||||
assertThat(parentId).isEqualTo(testParentId);
|
||||
MediaTestUtils.assertLibraryParamsEquals(testParams, params);
|
||||
parentIds.add(parentId);
|
||||
libraryParamsRef.set(params);
|
||||
subscribedControllers.addAll(session.getSubscribedControllers(parentId));
|
||||
latch.countDown();
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid(params));
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<Void>> onUnsubscribe(
|
||||
MediaLibrarySession session, ControllerInfo browser, String parentId) {
|
||||
parentIds.add(parentId);
|
||||
subscribedControllers.addAll(session.getSubscribedControllers(parentId));
|
||||
latch.countDown();
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid());
|
||||
}
|
||||
};
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
|
||||
MediaLibrarySession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaLibrarySession.Builder(service, player, sessionCallback)
|
||||
.setId("testOnSubscribe")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
Bundle connectionHints = new Bundle();
|
||||
connectionHints.putBoolean("onSubscribeTestBrowser", true);
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), connectionHints);
|
||||
|
||||
browser.subscribe(testParentId, testParams);
|
||||
browser.unsubscribe(testParentId);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(parentIds).containsExactly(testParentId, testParentId);
|
||||
MediaTestUtils.assertLibraryParamsEquals(testParams, libraryParamsRef.get());
|
||||
assertThat(subscribedControllers).hasSize(2);
|
||||
assertThat(
|
||||
subscribedControllers
|
||||
.get(0)
|
||||
.getConnectionHints()
|
||||
.getBoolean("onSubscribeTestBrowser", /* defaultValue= */ false))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
subscribedControllers
|
||||
.get(1)
|
||||
.getConnectionHints()
|
||||
.getBoolean("onSubscribeTestBrowser", /* defaultValue= */ false))
|
||||
.isTrue();
|
||||
// After unsubscribing the list of subscribed controllers is empty.
|
||||
assertThat(session.getSubscribedControllers(testParentId)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSubscribe_returnsNonSuccessResult_subscribedControllerNotRegistered()
|
||||
throws Exception {
|
||||
String testParentId = "onSubscribe_returnsNoSuccessResult_subscribedControllerNotRegistered";
|
||||
LibraryParams testParams = MediaTestUtils.createLibraryParams();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
List<ControllerInfo> subscribedControllers = new ArrayList<>();
|
||||
MediaLibrarySession.Callback sessionCallback =
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<Void>> onSubscribe(
|
||||
MediaLibrarySession session,
|
||||
ControllerInfo browser,
|
||||
String parentId,
|
||||
@Nullable LibraryParams params) {
|
||||
latch.countDown();
|
||||
subscribedControllers.addAll(session.getSubscribedControllers(parentId));
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED));
|
||||
}
|
||||
};
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
MediaLibrarySession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaLibrarySession.Builder(service, player, sessionCallback)
|
||||
.setId("testOnSubscribe")
|
||||
.build());
|
||||
Bundle connectionHints = new Bundle();
|
||||
connectionHints.putBoolean("onSubscribeTestBrowser", true);
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), connectionHints);
|
||||
|
||||
browser.subscribe(testParentId, testParams);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
// Inside the callback the subscribed controller is available even when not returning
|
||||
// `RESULT_SUCCESS`. It will be removed after the result has been received.
|
||||
assertThat(subscribedControllers).hasSize(1);
|
||||
assertThat(
|
||||
subscribedControllers
|
||||
.get(0)
|
||||
.getConnectionHints()
|
||||
.getBoolean("onSubscribeTestBrowser", /* defaultValue= */ false))
|
||||
.isTrue();
|
||||
// After subscribing the list of subscribed controllers is empty, because the callback returns a
|
||||
// result different to `RESULT_SUCCESS`.
|
||||
assertThat(session.getSubscribedControllers(testParentId)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSubscribe_onGetItemNotImplemented_errorNotSupported() throws Exception {
|
||||
String testParentId = SUBSCRIBE_PARENT_ID_1;
|
||||
LibraryParams testParams = MediaTestUtils.createLibraryParams();
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
MediaLibrarySession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaLibrarySession.Builder(service, player, new MediaLibrarySession.Callback() {})
|
||||
.setId("testOnSubscribe")
|
||||
.build());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||
|
||||
int resultCode = browser.subscribe(testParentId, testParams).resultCode;
|
||||
|
||||
assertThat(session.getSubscribedControllers(testParentId)).isEmpty();
|
||||
assertThat(resultCode).isEqualTo(RESULT_ERROR_NOT_SUPPORTED);
|
||||
assertThat(session.getSubscribedControllers(testParentId)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSubscribe_onGetItemNotSucceeded_correctErrorCodeReported() throws Exception {
|
||||
LibraryParams testParams = MediaTestUtils.createLibraryParams();
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
MediaLibrarySession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaLibrarySession.Builder(
|
||||
service,
|
||||
player,
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<MediaItem>> onGetItem(
|
||||
MediaLibrarySession session, ControllerInfo browser, String mediaId) {
|
||||
return Futures.immediateFuture(
|
||||
LibraryResult.ofError(RESULT_ERROR_SESSION_SETUP_REQUIRED));
|
||||
}
|
||||
})
|
||||
.setId("testOnSubscribe")
|
||||
.build());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||
|
||||
int resultCode = browser.subscribe(SUBSCRIBE_PARENT_ID_1, testParams).resultCode;
|
||||
|
||||
assertThat(resultCode).isEqualTo(RESULT_ERROR_SESSION_SETUP_REQUIRED);
|
||||
assertThat(session.getSubscribedControllers(SUBSCRIBE_PARENT_ID_1)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -115,12 +253,15 @@ public class MediaLibrarySessionCallbackTest {
|
||||
String testParentId = "testUnsubscribeId";
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
List<ControllerInfo> subscribedControllers = new ArrayList<>();
|
||||
List<String> parentIds = new ArrayList<>();
|
||||
MediaLibrarySession.Callback sessionCallback =
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<Void>> onUnsubscribe(
|
||||
MediaLibrarySession session, ControllerInfo browser, String parentId) {
|
||||
assertThat(parentId).isEqualTo(testParentId);
|
||||
parentIds.add(parentId);
|
||||
subscribedControllers.addAll(session.getSubscribedControllers(parentId));
|
||||
latch.countDown();
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid());
|
||||
}
|
||||
@ -134,9 +275,14 @@ public class MediaLibrarySessionCallbackTest {
|
||||
new MediaLibrarySession.Builder(service, player, sessionCallback)
|
||||
.setId("testOnUnsubscribe")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(
|
||||
session.getToken(), /* connectionHints= */ Bundle.EMPTY);
|
||||
browser.unsubscribe(testParentId);
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(parentIds).containsExactly(testParentId);
|
||||
// The browser wasn't subscribed.
|
||||
assertThat(subscribedControllers).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -167,7 +313,8 @@ public class MediaLibrarySessionCallbackTest {
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_callForRecentRootNonSystemUiPackageName_notIntercepted")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||
|
||||
LibraryResult<MediaItem> libraryRoot =
|
||||
browser.getLibraryRoot(new LibraryParams.Builder().setRecent(true).build());
|
||||
@ -212,7 +359,8 @@ public class MediaLibrarySessionCallbackTest {
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
@ -254,7 +402,8 @@ public class MediaLibrarySessionCallbackTest {
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
@ -291,7 +440,8 @@ public class MediaLibrarySessionCallbackTest {
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
@ -329,7 +479,8 @@ public class MediaLibrarySessionCallbackTest {
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
@ -372,7 +523,8 @@ public class MediaLibrarySessionCallbackTest {
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
RemoteMediaBrowser browser =
|
||||
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
|
@ -93,10 +93,10 @@ public final class RemoteControllerTestRule extends ExternalResource {
|
||||
* Creates {@link RemoteMediaBrowser} from {@link SessionToken} with default options waiting for
|
||||
* connection.
|
||||
*/
|
||||
public RemoteMediaBrowser createRemoteBrowser(SessionToken token) throws RemoteException {
|
||||
public RemoteMediaBrowser createRemoteBrowser(SessionToken token, Bundle connectionHints)
|
||||
throws RemoteException {
|
||||
RemoteMediaBrowser browser =
|
||||
new RemoteMediaBrowser(
|
||||
context, token, /* waitForConnection= */ true, /* connectionHints= */ null);
|
||||
new RemoteMediaBrowser(context, token, /* waitForConnection= */ true, connectionHints);
|
||||
controllers.add(browser);
|
||||
return browser;
|
||||
}
|
||||
|
@ -16,28 +16,31 @@
|
||||
package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
|
||||
import static androidx.media3.session.MediaTestUtils.assertLibraryParamsEquals;
|
||||
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_BROADCAST;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_DELAY_MS;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_MEDIA_ID;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.MEDIA_ID_GET_BROWSABLE_ITEM;
|
||||
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.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.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_LONG_LIST;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
|
||||
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_SUPPORTS_BROWSABLE_CHILDREN_ONLY;
|
||||
@ -48,10 +51,8 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_Q
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_RESULT;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_TIME_IN_MS;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
|
||||
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_PARENT_ID_1;
|
||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_2;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
@ -70,6 +71,7 @@ import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.test.session.common.CommonConstants;
|
||||
import androidx.media3.test.session.common.TestHandler;
|
||||
import androidx.media3.test.session.common.TestUtils;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
@ -77,6 +79,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -108,8 +111,6 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
.build();
|
||||
public static final LibraryParams ROOT_PARAMS =
|
||||
new LibraryParams.Builder().setExtras(ROOT_EXTRAS).build();
|
||||
private static final LibraryParams NOTIFY_CHILDREN_CHANGED_PARAMS =
|
||||
new LibraryParams.Builder().setExtras(NOTIFY_CHILDREN_CHANGED_EXTRAS).build();
|
||||
|
||||
private static final String TAG = "MockMediaLibrarySvc2";
|
||||
|
||||
@ -121,11 +122,11 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
private static LibraryParams expectedParams;
|
||||
|
||||
@Nullable private static byte[] testArtworkData;
|
||||
|
||||
private final AtomicInteger boundControllerCount;
|
||||
private final ConditionVariable allControllersUnbound;
|
||||
|
||||
@Nullable MediaLibrarySession session;
|
||||
@Nullable TestHandler handler;
|
||||
@Nullable HandlerThread handlerThread;
|
||||
|
||||
public MockMediaLibraryService() {
|
||||
@ -159,6 +160,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
super.onCreate();
|
||||
handlerThread = new HandlerThread(TAG);
|
||||
handlerThread.start();
|
||||
handler = new TestHandler(handlerThread.getLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -231,6 +233,16 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
}
|
||||
}
|
||||
|
||||
public static Bundle createNotifyChildrenChangedBundle(
|
||||
String mediaId, int itemCount, long delayMs, boolean broadcast) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_MEDIA_ID, mediaId);
|
||||
bundle.putInt(EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, itemCount);
|
||||
bundle.putLong(EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_DELAY_MS, delayMs);
|
||||
bundle.putBoolean(EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_BROADCAST, broadcast);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private class TestLibrarySessionCallback implements MediaLibrarySession.Callback {
|
||||
|
||||
@Override
|
||||
@ -293,8 +305,13 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<MediaItem>> onGetItem(
|
||||
MediaLibrarySession session, ControllerInfo browser, String mediaId) {
|
||||
if (mediaId.startsWith(SUBSCRIBE_PARENT_ID_1)) {
|
||||
return Futures.immediateFuture(
|
||||
LibraryResult.ofItem(createBrowsableMediaItem(mediaId), /* params= */ null));
|
||||
}
|
||||
switch (mediaId) {
|
||||
case MEDIA_ID_GET_BROWSABLE_ITEM:
|
||||
case SUBSCRIBE_PARENT_ID_2:
|
||||
return Futures.immediateFuture(
|
||||
LibraryResult.ofItem(createBrowsableMediaItem(mediaId), /* params= */ null));
|
||||
case MEDIA_ID_GET_PLAYABLE_ITEM:
|
||||
@ -306,7 +323,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null));
|
||||
default: // fall out
|
||||
}
|
||||
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -318,19 +335,21 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
int pageSize,
|
||||
@Nullable LibraryParams params) {
|
||||
assertLibraryParams(params);
|
||||
if (PARENT_ID.equals(parentId)) {
|
||||
if (Objects.equals(parentId, PARENT_ID_NO_CHILDREN)) {
|
||||
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
|
||||
} else if (Objects.equals(parentId, PARENT_ID)) {
|
||||
return Futures.immediateFuture(
|
||||
LibraryResult.ofItemList(
|
||||
getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize), params));
|
||||
} else if (PARENT_ID_LONG_LIST.equals(parentId)) {
|
||||
} else if (Objects.equals(parentId, PARENT_ID_LONG_LIST)) {
|
||||
List<MediaItem> list = new ArrayList<>(LONG_LIST_COUNT);
|
||||
for (int i = 0; i < LONG_LIST_COUNT; i++) {
|
||||
list.add(createPlayableMediaItem(TestUtils.getMediaIdInFakeTimeline(i)));
|
||||
}
|
||||
return Futures.immediateFuture(LibraryResult.ofItemList(list, params));
|
||||
} else if (PARENT_ID_ERROR.equals(parentId)) {
|
||||
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
} else if (PARENT_ID_AUTH_EXPIRED_ERROR.equals(parentId)) {
|
||||
} else if (Objects.equals(parentId, PARENT_ID_ERROR)) {
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)) {
|
||||
Bundle bundle = new Bundle();
|
||||
Intent signInIntent = new Intent("action");
|
||||
int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||
@ -346,8 +365,37 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
||||
new LibraryParams.Builder().setExtras(bundle).build()));
|
||||
}
|
||||
// Includes the case of PARENT_ID_NO_CHILDREN.
|
||||
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE, params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<Void>> onSubscribe(
|
||||
MediaLibrarySession session,
|
||||
ControllerInfo browser,
|
||||
String parentId,
|
||||
@Nullable LibraryParams params) {
|
||||
if (params != null) {
|
||||
String mediaId = params.extras.getString(EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_MEDIA_ID, null);
|
||||
long delayMs = params.extras.getLong(EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_DELAY_MS, 0L);
|
||||
if (mediaId != null && delayMs > 0) {
|
||||
int itemCount =
|
||||
params.extras.getInt(
|
||||
EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, Integer.MAX_VALUE);
|
||||
boolean broadcast =
|
||||
params.extras.getBoolean(EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_BROADCAST, false);
|
||||
// Post a delayed update as requested.
|
||||
handler.postDelayed(
|
||||
() -> {
|
||||
if (broadcast) {
|
||||
session.notifyChildrenChanged(mediaId, itemCount, params);
|
||||
} else {
|
||||
session.notifyChildrenChanged(browser, mediaId, itemCount, params);
|
||||
}
|
||||
},
|
||||
delayMs);
|
||||
}
|
||||
}
|
||||
return MediaLibrarySession.Callback.super.onSubscribe(session, browser, parentId, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -406,46 +454,10 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
|
||||
} else {
|
||||
// SEARCH_QUERY_ERROR will be handled here.
|
||||
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<Void>> onSubscribe(
|
||||
MediaLibrarySession session,
|
||||
ControllerInfo browser,
|
||||
String parentId,
|
||||
LibraryParams params) {
|
||||
assertLibraryParams(params);
|
||||
String unsubscribedId = "unsubscribedId";
|
||||
switch (parentId) {
|
||||
case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL:
|
||||
MockMediaLibraryService.this.session.notifyChildrenChanged(
|
||||
parentId, NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, NOTIFY_CHILDREN_CHANGED_PARAMS);
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid(params));
|
||||
case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE:
|
||||
MockMediaLibraryService.this.session.notifyChildrenChanged(
|
||||
browser,
|
||||
parentId,
|
||||
NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
|
||||
NOTIFY_CHILDREN_CHANGED_PARAMS);
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid(params));
|
||||
case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID:
|
||||
MockMediaLibraryService.this.session.notifyChildrenChanged(
|
||||
unsubscribedId, NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, NOTIFY_CHILDREN_CHANGED_PARAMS);
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid(params));
|
||||
case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID:
|
||||
MockMediaLibraryService.this.session.notifyChildrenChanged(
|
||||
browser,
|
||||
unsubscribedId,
|
||||
NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
|
||||
NOTIFY_CHILDREN_CHANGED_PARAMS);
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid(params));
|
||||
default: // fall out
|
||||
}
|
||||
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<SessionResult> onCustomCommand(
|
||||
MediaSession session,
|
||||
@ -471,7 +483,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
||||
private void assertLibraryParams(@Nullable LibraryParams params) {
|
||||
synchronized (MockMediaLibraryService.class) {
|
||||
if (assertLibraryParams) {
|
||||
assertLibraryParamsEquals(expectedParams, params);
|
||||
MediaTestUtils.assertLibraryParamsEquals(expectedParams, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user