mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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.
|
all supported API levels.
|
||||||
* Fix bug where `MediaController.getCurrentPosition()` is not advancing
|
* Fix bug where `MediaController.getCurrentPosition()` is not advancing
|
||||||
when connected to a legacy `MediaSessionCompat`.
|
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:
|
* UI:
|
||||||
* Downloads:
|
* Downloads:
|
||||||
* OkHttp Extension:
|
* OkHttp Extension:
|
||||||
|
@ -165,21 +165,6 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
|
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(
|
override fun onGetChildren(
|
||||||
session: MediaLibrarySession,
|
session: MediaLibrarySession,
|
||||||
browser: ControllerInfo,
|
browser: ControllerInfo,
|
||||||
|
@ -187,16 +187,19 @@ public final class MediaBrowser extends MediaController {
|
|||||||
public interface Listener extends MediaController.Listener {
|
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}.
|
* with {@link #subscribe}.
|
||||||
*
|
*
|
||||||
* <p>This method is called when the library service called {@link
|
* <p>This method is called when the app calls {@link
|
||||||
* MediaLibraryService.MediaLibrarySession#notifyChildrenChanged} for the parent.
|
* 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 browser The browser for this event.
|
||||||
* @param parentId The non-empty parent id that you've specified with {@link #subscribe(String,
|
* @param parentId The non-empty parent id that you've specified with {@link #subscribe(String,
|
||||||
* LibraryParams)}.
|
* 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
|
* @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)}.
|
* 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.checkNotEmpty;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
|
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
|
||||||
|
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||||
|
import static androidx.media3.session.LibraryResult.ofVoid;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
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
|
* Called when a {@link MediaBrowser} subscribes to the given parent id by {@link
|
||||||
* MediaBrowser#subscribe(String, LibraryParams)}.
|
* 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
|
* <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
|
* asynchronously. You can also return a {@link LibraryResult} directly by using Guava's
|
||||||
* {@link Futures#immediateFuture(Object)}.
|
* {@link Futures#immediateFuture(Object)}.
|
||||||
*
|
*
|
||||||
* <p>The {@link LibraryResult#params} should be the same as the given {@link LibraryParams
|
* <p>The {@link LibraryResult#params} returned to the caller should be the same as the {@link
|
||||||
* params}.
|
* LibraryParams params} passed into this method.
|
||||||
*
|
|
||||||
* <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>Interoperability: This will be called by {@link
|
* <p>Interoperability: This will be called by {@link
|
||||||
* android.support.v4.media.MediaBrowserCompat#subscribe}, but won't 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,
|
ControllerInfo browser,
|
||||||
String parentId,
|
String parentId,
|
||||||
@Nullable LibraryParams params) {
|
@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)}.
|
* MediaBrowser#unsubscribe(String)}.
|
||||||
*
|
*
|
||||||
* <p>Return a {@link ListenableFuture} to send a {@link LibraryResult} back to the browser
|
* <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
|
* asynchronously. You can also return a {@link LibraryResult} directly by using Guava's
|
||||||
* {@link Futures#immediateFuture(Object)}.
|
* {@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
|
* <p>Interoperability: This will be called by {@link
|
||||||
* android.support.v4.media.MediaBrowserCompat#unsubscribe}, but won't be called by {@link
|
* android.support.v4.media.MediaBrowserCompat#unsubscribe}, but won't be called by {@link
|
||||||
* android.media.browse.MediaBrowser#unsubscribe}.
|
* android.media.browse.MediaBrowser#unsubscribe}.
|
||||||
@ -270,7 +311,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
|||||||
*/
|
*/
|
||||||
default ListenableFuture<LibraryResult<Void>> onUnsubscribe(
|
default ListenableFuture<LibraryResult<Void>> onUnsubscribe(
|
||||||
MediaLibrarySession session, ControllerInfo browser, String parentId) {
|
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
|
* Returns the controllers that are currently subscribed to the given {@code mediaId}.
|
||||||
* parent's children. If the browser isn't subscribing to the parent, nothing will happen.
|
|
||||||
*
|
*
|
||||||
* <p>This only tells the number of child {@link MediaItem media items}. {@link
|
* <p>Use the returned {@linkplain ControllerInfo controller infos} to call {@link
|
||||||
* Callback#onGetChildren} will be called by the browser afterwards to get the list of {@link
|
* #notifyChildrenChanged} in case the children of the media item with the given media ID have
|
||||||
* MediaItem media items}.
|
* 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 browser The browser to notify.
|
||||||
* @param parentId The non-empty id of the parent with changes to its children.
|
* @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}.
|
* @param params The parameters given by {@link Callback#onSubscribe}.
|
||||||
*/
|
*/
|
||||||
public void notifyChildrenChanged(
|
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.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.Util.postOrRun;
|
|
||||||
import static androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED;
|
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_ERROR_SESSION_AUTHENTICATION_EXPIRED;
|
||||||
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||||
@ -32,25 +30,25 @@ import android.content.Context;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import androidx.annotation.GuardedBy;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.collection.ArrayMap;
|
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.common.util.BitmapLoader;
|
import androidx.media3.common.util.BitmapLoader;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||||
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||||
import androidx.media3.session.MediaSession.ControllerCb;
|
import androidx.media3.session.MediaSession.ControllerCb;
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||||
|
import com.google.common.collect.HashMultimap;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -61,12 +59,10 @@ import java.util.concurrent.Future;
|
|||||||
/* package */ class MediaLibrarySessionImpl extends MediaSessionImpl {
|
/* package */ class MediaLibrarySessionImpl extends MediaSessionImpl {
|
||||||
|
|
||||||
private static final String RECENT_LIBRARY_ROOT_MEDIA_ID = "androidx.media3.session.recent.root";
|
private static final String RECENT_LIBRARY_ROOT_MEDIA_ID = "androidx.media3.session.recent.root";
|
||||||
|
|
||||||
private final MediaLibrarySession instance;
|
private final MediaLibrarySession instance;
|
||||||
private final MediaLibrarySession.Callback callback;
|
private final MediaLibrarySession.Callback callback;
|
||||||
|
private final HashMultimap<String, ControllerInfo> parentIdToSubscribedControllers;
|
||||||
@GuardedBy("lock")
|
private final HashMultimap<ControllerCb, String> controllerToSubscribedParentIds;
|
||||||
private final ArrayMap<ControllerCb, Set<String>> subscriptions;
|
|
||||||
|
|
||||||
/** Creates an instance. */
|
/** Creates an instance. */
|
||||||
public MediaLibrarySessionImpl(
|
public MediaLibrarySessionImpl(
|
||||||
@ -93,7 +89,8 @@ import java.util.concurrent.Future;
|
|||||||
playIfSuppressed);
|
playIfSuppressed);
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
subscriptions = new ArrayMap<>();
|
parentIdToSubscribedControllers = HashMultimap.create();
|
||||||
|
controllerToSubscribedParentIds = HashMultimap.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -116,48 +113,6 @@ import java.util.concurrent.Future;
|
|||||||
&& legacyStub.getConnectedControllersManager().isConnected(controller);
|
&& 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(
|
public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRootOnHandler(
|
||||||
ControllerInfo browser, @Nullable LibraryParams params) {
|
ControllerInfo browser, @Nullable LibraryParams params) {
|
||||||
if (params != null && params.isRecent && isSystemUiController(browser)) {
|
if (params != null && params.isRecent && isSystemUiController(browser)) {
|
||||||
@ -185,7 +140,7 @@ import java.util.concurrent.Future;
|
|||||||
maybeUpdateLegacyErrorState(result);
|
maybeUpdateLegacyErrorState(result);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
this::postOrRunOnApplicationHandler);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +183,7 @@ import java.util.concurrent.Future;
|
|||||||
verifyResultItems(result, pageSize);
|
verifyResultItems(result, pageSize);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
this::postOrRunOnApplicationHandler);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,21 +198,16 @@ import java.util.concurrent.Future;
|
|||||||
maybeUpdateLegacyErrorState(result);
|
maybeUpdateLegacyErrorState(result);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
this::postOrRunOnApplicationHandler);
|
||||||
return future;
|
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());
|
|
||||||
synchronized (lock) {
|
ControllerCb controllerCb = checkNotNull(browser.getControllerCb());
|
||||||
@Nullable Set<String> subscription = subscriptions.get(controller);
|
controllerToSubscribedParentIds.put(controllerCb, parentId);
|
||||||
if (subscription == null) {
|
parentIdToSubscribedControllers.put(parentId, browser);
|
||||||
subscription = new HashSet<>();
|
|
||||||
subscriptions.put(controller, subscription);
|
|
||||||
}
|
|
||||||
subscription.add(parentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call callbacks after adding it to the subscription list because library session may want
|
// Call callbacks after adding it to the subscription list because library session may want
|
||||||
// to call notifyChildrenChanged() in the callback.
|
// to call notifyChildrenChanged() in the callback.
|
||||||
@ -270,31 +220,65 @@ import java.util.concurrent.Future;
|
|||||||
instance, resolveControllerInfoForCallback(browser), parentId, params),
|
instance, resolveControllerInfoForCallback(browser), parentId, params),
|
||||||
"onSubscribe must return non-null future");
|
"onSubscribe must return non-null future");
|
||||||
|
|
||||||
// When error happens, remove from the subscription list.
|
|
||||||
future.addListener(
|
future.addListener(
|
||||||
() -> {
|
() -> {
|
||||||
@Nullable LibraryResult<Void> result = tryGetFutureResult(future);
|
@Nullable LibraryResult<Void> result = tryGetFutureResult(future);
|
||||||
if (result == null || result.resultCode != RESULT_SUCCESS) {
|
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;
|
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(
|
public ListenableFuture<LibraryResult<Void>> onUnsubscribeOnHandler(
|
||||||
ControllerInfo browser, String parentId) {
|
ControllerInfo browser, String parentId) {
|
||||||
ListenableFuture<LibraryResult<Void>> future =
|
ListenableFuture<LibraryResult<Void>> future =
|
||||||
callback.onUnsubscribe(instance, resolveControllerInfoForCallback(browser), parentId);
|
callback.onUnsubscribe(instance, resolveControllerInfoForCallback(browser), parentId);
|
||||||
|
|
||||||
future.addListener(
|
future.addListener(
|
||||||
() -> removeSubscription(checkStateNotNull(browser.getControllerCb()), parentId),
|
() -> removeSubscription(browser, parentId), this::postOrRunOnApplicationHandler);
|
||||||
MoreExecutors.directExecutor());
|
|
||||||
|
|
||||||
return future;
|
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(
|
public ListenableFuture<LibraryResult<Void>> onSearchOnHandler(
|
||||||
ControllerInfo browser, String query, @Nullable LibraryParams params) {
|
ControllerInfo browser, String query, @Nullable LibraryParams params) {
|
||||||
ListenableFuture<LibraryResult<Void>> future =
|
ListenableFuture<LibraryResult<Void>> future =
|
||||||
@ -306,7 +290,7 @@ import java.util.concurrent.Future;
|
|||||||
maybeUpdateLegacyErrorState(result);
|
maybeUpdateLegacyErrorState(result);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
this::postOrRunOnApplicationHandler);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,10 +311,33 @@ import java.util.concurrent.Future;
|
|||||||
verifyResultItems(result, pageSize);
|
verifyResultItems(result, pageSize);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Runnable r) -> postOrRun(getApplicationHandler(), r));
|
this::postOrRunOnApplicationHandler);
|
||||||
return future;
|
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
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
protected MediaLibraryServiceLegacyStub getLegacyBrowserService() {
|
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) {
|
private void maybeUpdateLegacyErrorState(LibraryResult<?> result) {
|
||||||
PlayerWrapper playerWrapper = getPlayerWrapper();
|
PlayerWrapper playerWrapper = getPlayerWrapper();
|
||||||
if (result.resultCode == RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED
|
if (result.resultCode == RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED
|
||||||
@ -410,16 +407,14 @@ import java.util.concurrent.Future;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeSubscription(ControllerCb controllerCb, String parentId) {
|
private void removeSubscription(ControllerInfo controllerInfo, String parentId) {
|
||||||
synchronized (lock) {
|
ControllerCb controllerCb = checkNotNull(controllerInfo.getControllerCb());
|
||||||
@Nullable Set<String> subscription = subscriptions.get(controllerCb);
|
parentIdToSubscribedControllers.remove(parentId, controllerInfo);
|
||||||
if (subscription != null) {
|
controllerToSubscribedParentIds.remove(controllerCb, parentId);
|
||||||
subscription.remove(parentId);
|
}
|
||||||
if (subscription.isEmpty()) {
|
|
||||||
subscriptions.remove(controllerCb);
|
private void postOrRunOnApplicationHandler(Runnable runnable) {
|
||||||
}
|
Util.postOrRun(getApplicationHandler(), runnable);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture<LibraryResult<ImmutableList<MediaItem>>>
|
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);
|
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 Uri sessionUri;
|
||||||
private final PlayerInfoChangedHandler onPlayerInfoChangedHandler;
|
private final PlayerInfoChangedHandler onPlayerInfoChangedHandler;
|
||||||
|
@ -56,16 +56,16 @@ public class MediaBrowserConstants {
|
|||||||
public static final List<String> SEARCH_RESULT = new ArrayList<>();
|
public static final List<String> SEARCH_RESULT = new ArrayList<>();
|
||||||
public static final int SEARCH_RESULT_COUNT = 50;
|
public static final int SEARCH_RESULT_COUNT = 50;
|
||||||
|
|
||||||
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL =
|
public static final String SUBSCRIBE_PARENT_ID_1 = "subscribe_parent_id_1";
|
||||||
"subscribe_id_notify_children_changed_to_all";
|
public static final String SUBSCRIBE_PARENT_ID_2 = "subscribe_parent_id_2";
|
||||||
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE =
|
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_MEDIA_ID =
|
||||||
"subscribe_id_notify_children_changed_to_one";
|
"notify_children_changed_media_id";
|
||||||
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID =
|
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_ITEM_COUNT =
|
||||||
"subscribe_id_notify_children_changed_to_all_with_non_subscribed_id";
|
"notify_children_changed_item_count";
|
||||||
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID =
|
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_DELAY_MS =
|
||||||
"subscribe_id_notify_children_changed_to_one_with_non_subscribed_id";
|
"notify_children_changed_delay";
|
||||||
public static final int NOTIFY_CHILDREN_CHANGED_ITEM_COUNT = 101;
|
public static final String EXTRAS_KEY_NOTIFY_CHILDREN_CHANGED_BROADCAST =
|
||||||
public static final Bundle NOTIFY_CHILDREN_CHANGED_EXTRAS = TestUtils.createTestBundle();
|
"notify_children_changed_broadcast";
|
||||||
|
|
||||||
public static final String CUSTOM_ACTION = "customAction";
|
public static final String CUSTOM_ACTION = "customAction";
|
||||||
public static final Bundle CUSTOM_ACTION_EXTRAS = new Bundle();
|
public static final Bundle CUSTOM_ACTION_EXTRAS = new Bundle();
|
||||||
|
@ -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.LibraryResult.RESULT_SUCCESS;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||||
|
import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
|
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
|
||||||
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;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_KEY;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_KEY;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_VALUE;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_VALUE;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_1;
|
||||||
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_PARENT_ID_2;
|
||||||
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.TestUtils.LONG_TIMEOUT_MS;
|
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 androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
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.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@ -431,7 +429,7 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void onChildrenChanged_calledWhenSubscribed() throws Exception {
|
public void onChildrenChanged_calledWhenSubscribed() throws Exception {
|
||||||
// This test uses MediaLibrarySession.notifyChildrenChanged().
|
// 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);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
AtomicReference<String> parentIdRef = new AtomicReference<>();
|
AtomicReference<String> parentIdRef = new AtomicReference<>();
|
||||||
@ -453,7 +451,7 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
|||||||
LibraryResult<Void> result =
|
LibraryResult<Void> result =
|
||||||
threadTestRule
|
threadTestRule
|
||||||
.getHandler()
|
.getHandler()
|
||||||
.postAndSync(() -> browser.subscribe(expectedParentId, null))
|
.postAndSync(() -> browser.subscribe(expectedParentId, /* params= */ null))
|
||||||
.get(TIMEOUT_MS, MILLISECONDS);
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||||
|
|
||||||
@ -461,27 +459,23 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
|||||||
// notifyChildrenChanged() in its onSubscribe() method.
|
// notifyChildrenChanged() in its onSubscribe() method.
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(parentIdRef.get()).isEqualTo(expectedParentId);
|
assertThat(parentIdRef.get()).isEqualTo(expectedParentId);
|
||||||
assertThat(itemCountRef.get()).isEqualTo(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT);
|
assertThat(itemCountRef.get()).isEqualTo(Integer.MAX_VALUE);
|
||||||
MediaTestUtils.assertLibraryParamsEquals(paramsRef.get(), NOTIFY_CHILDREN_CHANGED_EXTRAS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onChildrenChanged_calledWhenSubscribed2() throws Exception {
|
public void onChildrenChanged_calledWhenSubscribedAndWithDelay() throws Exception {
|
||||||
// This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
|
String expectedParentId = SUBSCRIBE_PARENT_ID_2;
|
||||||
String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(2);
|
||||||
AtomicReference<String> parentIdRef = new AtomicReference<>();
|
List<String> parentIds = new ArrayList<>();
|
||||||
AtomicInteger itemCountRef = new AtomicInteger();
|
List<Integer> itemCounts = new ArrayList<>();
|
||||||
AtomicReference<LibraryParams> paramsRef = new AtomicReference<>();
|
|
||||||
MediaBrowser.Listener browserListenerProxy =
|
MediaBrowser.Listener browserListenerProxy =
|
||||||
new MediaBrowser.Listener() {
|
new MediaBrowser.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onChildrenChanged(
|
public void onChildrenChanged(
|
||||||
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
|
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
|
||||||
parentIdRef.set(parentId);
|
parentIds.add(parentId);
|
||||||
itemCountRef.set(itemCount);
|
itemCounts.add(itemCount);
|
||||||
paramsRef.set(params);
|
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -490,75 +484,108 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest {
|
|||||||
LibraryResult<Void> result =
|
LibraryResult<Void> result =
|
||||||
threadTestRule
|
threadTestRule
|
||||||
.getHandler()
|
.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);
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
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(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(parentIdRef.get()).isEqualTo(expectedParentId);
|
assertThat(parentIds).containsExactly(expectedParentId, expectedParentId);
|
||||||
assertThat(itemCountRef.get()).isEqualTo(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT);
|
assertThat(itemCounts).containsExactly(Integer.MAX_VALUE, 12);
|
||||||
MediaTestUtils.assertLibraryParamsEquals(paramsRef.get(), NOTIFY_CHILDREN_CHANGED_EXTRAS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onChildrenChanged_notCalledWhenNotSubscribed() throws Exception {
|
public void onChildrenChanged_notCalledWhenNotSubscribed() throws Exception {
|
||||||
// This test uses MediaLibrarySession.notifyChildrenChanged().
|
String mediaId1 = SUBSCRIBE_PARENT_ID_1;
|
||||||
String subscribedMediaId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
|
String mediaId2 = SUBSCRIBE_PARENT_ID_2;
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
List<String> notifiedParentIds = new ArrayList<>();
|
||||||
|
List<Integer> notifiedItemCounts = new ArrayList<>();
|
||||||
MediaBrowser.Listener browserListenerProxy =
|
CountDownLatch childrenChangedLatch = new CountDownLatch(4);
|
||||||
|
CountDownLatch disconnectLatch = new CountDownLatch(2);
|
||||||
|
MediaBrowser.Listener browserListener =
|
||||||
new MediaBrowser.Listener() {
|
new MediaBrowser.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onChildrenChanged(
|
public void onChildrenChanged(
|
||||||
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
|
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
|
@Override
|
||||||
public void onChildrenChanged(
|
public void onDisconnected(MediaController controller) {
|
||||||
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
|
disconnectLatch.countDown();
|
||||||
latch.countDown();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
MediaBrowser browser1 = createBrowser(/* connectionHints= */ null, browserListener);
|
||||||
MediaBrowser browser = createBrowser(null, browserListenerProxy);
|
MediaBrowser browser2 = createBrowser(/* connectionHints= */ null, browserListener);
|
||||||
LibraryResult<Void> result =
|
// Subscribe both browsers each to a different media IDs and request a second update after a
|
||||||
|
// delay.
|
||||||
|
LibraryResult<Void> subscriptionResult1 =
|
||||||
threadTestRule
|
threadTestRule
|
||||||
.getHandler()
|
.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);
|
.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
|
assertThat(childrenChangedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
// notifyChildrenChanged(ControllerInfo) in its onSubscribe() method, but
|
assertThat(notifiedParentIds)
|
||||||
// with a different media ID.
|
.containsExactly(
|
||||||
// Therefore, onChildrenChanged() should not be called.
|
mediaId1, // callback when subscribing browser1
|
||||||
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
|
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)
|
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.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
@ -100,7 +101,8 @@ public class MediaLibraryServiceTest {
|
|||||||
return session.get();
|
return session.get();
|
||||||
});
|
});
|
||||||
// Create the remote browser to start the service.
|
// 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.
|
// Get the started service instance after creation.
|
||||||
MockMediaLibraryService service =
|
MockMediaLibraryService service =
|
||||||
(MockMediaLibraryService) testServiceRegistry.getServiceInstance();
|
(MockMediaLibraryService) testServiceRegistry.getServiceInstance();
|
||||||
|
@ -15,11 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.session;
|
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 androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaMetadata;
|
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.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@ -77,11 +83,13 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onSubscribe() throws Exception {
|
public void onSubscribeUnsubscribe() throws Exception {
|
||||||
String testParentId = "testSubscribeId";
|
String testParentId = "testSubscribeUnsubscribeId";
|
||||||
LibraryParams testParams = MediaTestUtils.createLibraryParams();
|
LibraryParams testParams = MediaTestUtils.createLibraryParams();
|
||||||
|
CountDownLatch latch = new CountDownLatch(2);
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
AtomicReference<LibraryParams> libraryParamsRef = new AtomicReference<>();
|
||||||
|
List<ControllerInfo> subscribedControllers = new ArrayList<>();
|
||||||
|
List<String> parentIds = new ArrayList<>();
|
||||||
MediaLibrarySession.Callback sessionCallback =
|
MediaLibrarySession.Callback sessionCallback =
|
||||||
new MediaLibrarySession.Callback() {
|
new MediaLibrarySession.Callback() {
|
||||||
@Override
|
@Override
|
||||||
@ -90,24 +98,154 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
ControllerInfo browser,
|
ControllerInfo browser,
|
||||||
String parentId,
|
String parentId,
|
||||||
@Nullable LibraryParams params) {
|
@Nullable LibraryParams params) {
|
||||||
assertThat(parentId).isEqualTo(testParentId);
|
parentIds.add(parentId);
|
||||||
MediaTestUtils.assertLibraryParamsEquals(testParams, params);
|
libraryParamsRef.set(params);
|
||||||
|
subscribedControllers.addAll(session.getSubscribedControllers(parentId));
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
return Futures.immediateFuture(LibraryResult.ofVoid(params));
|
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();
|
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||||
service.attachBaseContext(context);
|
service.attachBaseContext(context);
|
||||||
|
|
||||||
MediaLibrarySession session =
|
MediaLibrarySession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaLibrarySession.Builder(service, player, sessionCallback)
|
new MediaLibrarySession.Builder(service, player, sessionCallback)
|
||||||
.setId("testOnSubscribe")
|
.setId("testOnSubscribe")
|
||||||
.build());
|
.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.subscribe(testParentId, testParams);
|
||||||
|
browser.unsubscribe(testParentId);
|
||||||
|
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
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
|
@Test
|
||||||
@ -115,12 +253,15 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
String testParentId = "testUnsubscribeId";
|
String testParentId = "testUnsubscribeId";
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
List<ControllerInfo> subscribedControllers = new ArrayList<>();
|
||||||
|
List<String> parentIds = new ArrayList<>();
|
||||||
MediaLibrarySession.Callback sessionCallback =
|
MediaLibrarySession.Callback sessionCallback =
|
||||||
new MediaLibrarySession.Callback() {
|
new MediaLibrarySession.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<LibraryResult<Void>> onUnsubscribe(
|
public ListenableFuture<LibraryResult<Void>> onUnsubscribe(
|
||||||
MediaLibrarySession session, ControllerInfo browser, String parentId) {
|
MediaLibrarySession session, ControllerInfo browser, String parentId) {
|
||||||
assertThat(parentId).isEqualTo(testParentId);
|
parentIds.add(parentId);
|
||||||
|
subscribedControllers.addAll(session.getSubscribedControllers(parentId));
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
return Futures.immediateFuture(LibraryResult.ofVoid());
|
return Futures.immediateFuture(LibraryResult.ofVoid());
|
||||||
}
|
}
|
||||||
@ -134,9 +275,14 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
new MediaLibrarySession.Builder(service, player, sessionCallback)
|
new MediaLibrarySession.Builder(service, player, sessionCallback)
|
||||||
.setId("testOnUnsubscribe")
|
.setId("testOnUnsubscribe")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
RemoteMediaBrowser browser =
|
||||||
|
controllerTestRule.createRemoteBrowser(
|
||||||
|
session.getToken(), /* connectionHints= */ Bundle.EMPTY);
|
||||||
browser.unsubscribe(testParentId);
|
browser.unsubscribe(testParentId);
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
assertThat(parentIds).containsExactly(testParentId);
|
||||||
|
// The browser wasn't subscribed.
|
||||||
|
assertThat(subscribedControllers).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -167,7 +313,8 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
new MediaLibrarySession.Builder(service, player, callback)
|
new MediaLibrarySession.Builder(service, player, callback)
|
||||||
.setId("onGetChildren_callForRecentRootNonSystemUiPackageName_notIntercepted")
|
.setId("onGetChildren_callForRecentRootNonSystemUiPackageName_notIntercepted")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
RemoteMediaBrowser browser =
|
||||||
|
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||||
|
|
||||||
LibraryResult<MediaItem> libraryRoot =
|
LibraryResult<MediaItem> libraryRoot =
|
||||||
browser.getLibraryRoot(new LibraryParams.Builder().setRecent(true).build());
|
browser.getLibraryRoot(new LibraryParams.Builder().setRecent(true).build());
|
||||||
@ -212,7 +359,8 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
new MediaLibrarySession.Builder(service, player, callback)
|
new MediaLibrarySession.Builder(service, player, callback)
|
||||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
RemoteMediaBrowser browser =
|
||||||
|
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||||
|
|
||||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||||
browser.getChildren(
|
browser.getChildren(
|
||||||
@ -254,7 +402,8 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
new MediaLibrarySession.Builder(service, player, callback)
|
new MediaLibrarySession.Builder(service, player, callback)
|
||||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
RemoteMediaBrowser browser =
|
||||||
|
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||||
|
|
||||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||||
browser.getChildren(
|
browser.getChildren(
|
||||||
@ -291,7 +440,8 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
new MediaLibrarySession.Builder(service, player, callback)
|
new MediaLibrarySession.Builder(service, player, callback)
|
||||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
RemoteMediaBrowser browser =
|
||||||
|
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||||
|
|
||||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||||
browser.getChildren(
|
browser.getChildren(
|
||||||
@ -329,7 +479,8 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
new MediaLibrarySession.Builder(service, player, callback)
|
new MediaLibrarySession.Builder(service, player, callback)
|
||||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
RemoteMediaBrowser browser =
|
||||||
|
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||||
|
|
||||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||||
browser.getChildren(
|
browser.getChildren(
|
||||||
@ -372,7 +523,8 @@ public class MediaLibrarySessionCallbackTest {
|
|||||||
new MediaLibrarySession.Builder(service, player, callback)
|
new MediaLibrarySession.Builder(service, player, callback)
|
||||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
RemoteMediaBrowser browser =
|
||||||
|
controllerTestRule.createRemoteBrowser(session.getToken(), Bundle.EMPTY);
|
||||||
|
|
||||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||||
browser.getChildren(
|
browser.getChildren(
|
||||||
|
@ -93,10 +93,10 @@ public final class RemoteControllerTestRule extends ExternalResource {
|
|||||||
* Creates {@link RemoteMediaBrowser} from {@link SessionToken} with default options waiting for
|
* Creates {@link RemoteMediaBrowser} from {@link SessionToken} with default options waiting for
|
||||||
* connection.
|
* connection.
|
||||||
*/
|
*/
|
||||||
public RemoteMediaBrowser createRemoteBrowser(SessionToken token) throws RemoteException {
|
public RemoteMediaBrowser createRemoteBrowser(SessionToken token, Bundle connectionHints)
|
||||||
|
throws RemoteException {
|
||||||
RemoteMediaBrowser browser =
|
RemoteMediaBrowser browser =
|
||||||
new RemoteMediaBrowser(
|
new RemoteMediaBrowser(context, token, /* waitForConnection= */ true, connectionHints);
|
||||||
context, token, /* waitForConnection= */ true, /* connectionHints= */ null);
|
|
||||||
controllers.add(browser);
|
controllers.add(browser);
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
@ -16,28 +16,31 @@
|
|||||||
package androidx.media3.session;
|
package androidx.media3.session;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT;
|
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||||
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
|
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
|
||||||
import static androidx.media3.session.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;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
|
||||||
|
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.GET_CHILDREN_RESULT;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT;
|
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_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_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.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;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_AUTH_EXPIRED_ERROR_KEY_ERROR_RESOLUTION_ACTION_LABEL;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_ERROR;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
|
||||||
|
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_EXTRAS;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY;
|
import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY;
|
||||||
@ -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;
|
||||||
import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
|
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.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_PARENT_ID_1;
|
||||||
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_PARENT_ID_2;
|
||||||
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 java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@ -70,6 +71,7 @@ import androidx.media3.common.util.Log;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||||
import androidx.media3.test.session.common.CommonConstants;
|
import androidx.media3.test.session.common.CommonConstants;
|
||||||
|
import androidx.media3.test.session.common.TestHandler;
|
||||||
import androidx.media3.test.session.common.TestUtils;
|
import androidx.media3.test.session.common.TestUtils;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
@ -77,6 +79,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@ -108,8 +111,6 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
.build();
|
.build();
|
||||||
public static final LibraryParams ROOT_PARAMS =
|
public static final LibraryParams ROOT_PARAMS =
|
||||||
new LibraryParams.Builder().setExtras(ROOT_EXTRAS).build();
|
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";
|
private static final String TAG = "MockMediaLibrarySvc2";
|
||||||
|
|
||||||
@ -121,11 +122,11 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
private static LibraryParams expectedParams;
|
private static LibraryParams expectedParams;
|
||||||
|
|
||||||
@Nullable private static byte[] testArtworkData;
|
@Nullable private static byte[] testArtworkData;
|
||||||
|
|
||||||
private final AtomicInteger boundControllerCount;
|
private final AtomicInteger boundControllerCount;
|
||||||
private final ConditionVariable allControllersUnbound;
|
private final ConditionVariable allControllersUnbound;
|
||||||
|
|
||||||
@Nullable MediaLibrarySession session;
|
@Nullable MediaLibrarySession session;
|
||||||
|
@Nullable TestHandler handler;
|
||||||
@Nullable HandlerThread handlerThread;
|
@Nullable HandlerThread handlerThread;
|
||||||
|
|
||||||
public MockMediaLibraryService() {
|
public MockMediaLibraryService() {
|
||||||
@ -159,6 +160,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
super.onCreate();
|
super.onCreate();
|
||||||
handlerThread = new HandlerThread(TAG);
|
handlerThread = new HandlerThread(TAG);
|
||||||
handlerThread.start();
|
handlerThread.start();
|
||||||
|
handler = new TestHandler(handlerThread.getLooper());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 {
|
private class TestLibrarySessionCallback implements MediaLibrarySession.Callback {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -293,8 +305,13 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
@Override
|
@Override
|
||||||
public ListenableFuture<LibraryResult<MediaItem>> onGetItem(
|
public ListenableFuture<LibraryResult<MediaItem>> onGetItem(
|
||||||
MediaLibrarySession session, ControllerInfo browser, String mediaId) {
|
MediaLibrarySession session, ControllerInfo browser, String mediaId) {
|
||||||
|
if (mediaId.startsWith(SUBSCRIBE_PARENT_ID_1)) {
|
||||||
|
return Futures.immediateFuture(
|
||||||
|
LibraryResult.ofItem(createBrowsableMediaItem(mediaId), /* params= */ null));
|
||||||
|
}
|
||||||
switch (mediaId) {
|
switch (mediaId) {
|
||||||
case MEDIA_ID_GET_BROWSABLE_ITEM:
|
case MEDIA_ID_GET_BROWSABLE_ITEM:
|
||||||
|
case SUBSCRIBE_PARENT_ID_2:
|
||||||
return Futures.immediateFuture(
|
return Futures.immediateFuture(
|
||||||
LibraryResult.ofItem(createBrowsableMediaItem(mediaId), /* params= */ null));
|
LibraryResult.ofItem(createBrowsableMediaItem(mediaId), /* params= */ null));
|
||||||
case MEDIA_ID_GET_PLAYABLE_ITEM:
|
case MEDIA_ID_GET_PLAYABLE_ITEM:
|
||||||
@ -306,7 +323,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null));
|
LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null));
|
||||||
default: // fall out
|
default: // fall out
|
||||||
}
|
}
|
||||||
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -318,19 +335,21 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
int pageSize,
|
int pageSize,
|
||||||
@Nullable LibraryParams params) {
|
@Nullable LibraryParams params) {
|
||||||
assertLibraryParams(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(
|
return Futures.immediateFuture(
|
||||||
LibraryResult.ofItemList(
|
LibraryResult.ofItemList(
|
||||||
getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize), params));
|
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);
|
List<MediaItem> list = new ArrayList<>(LONG_LIST_COUNT);
|
||||||
for (int i = 0; i < LONG_LIST_COUNT; i++) {
|
for (int i = 0; i < LONG_LIST_COUNT; i++) {
|
||||||
list.add(createPlayableMediaItem(TestUtils.getMediaIdInFakeTimeline(i)));
|
list.add(createPlayableMediaItem(TestUtils.getMediaIdInFakeTimeline(i)));
|
||||||
}
|
}
|
||||||
return Futures.immediateFuture(LibraryResult.ofItemList(list, params));
|
return Futures.immediateFuture(LibraryResult.ofItemList(list, params));
|
||||||
} else if (PARENT_ID_ERROR.equals(parentId)) {
|
} else if (Objects.equals(parentId, PARENT_ID_ERROR)) {
|
||||||
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE));
|
||||||
} else if (PARENT_ID_AUTH_EXPIRED_ERROR.equals(parentId)) {
|
} else if (Objects.equals(parentId, PARENT_ID_AUTH_EXPIRED_ERROR)) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
Intent signInIntent = new Intent("action");
|
Intent signInIntent = new Intent("action");
|
||||||
int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0;
|
int flags = Util.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||||
@ -346,8 +365,37 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED,
|
||||||
new LibraryParams.Builder().setExtras(bundle).build()));
|
new LibraryParams.Builder().setExtras(bundle).build()));
|
||||||
}
|
}
|
||||||
// Includes the case of PARENT_ID_NO_CHILDREN.
|
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_BAD_VALUE, params));
|
||||||
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), 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
|
@Override
|
||||||
@ -406,46 +454,10 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
|
return Futures.immediateFuture(LibraryResult.ofItemList(ImmutableList.of(), params));
|
||||||
} else {
|
} else {
|
||||||
// SEARCH_QUERY_ERROR will be handled here.
|
// SEARCH_QUERY_ERROR will be handled here.
|
||||||
return Futures.immediateFuture(LibraryResult.ofError(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
|
@Override
|
||||||
public ListenableFuture<SessionResult> onCustomCommand(
|
public ListenableFuture<SessionResult> onCustomCommand(
|
||||||
MediaSession session,
|
MediaSession session,
|
||||||
@ -471,7 +483,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
|
|||||||
private void assertLibraryParams(@Nullable LibraryParams params) {
|
private void assertLibraryParams(@Nullable LibraryParams params) {
|
||||||
synchronized (MockMediaLibraryService.class) {
|
synchronized (MockMediaLibraryService.class) {
|
||||||
if (assertLibraryParams) {
|
if (assertLibraryParams) {
|
||||||
assertLibraryParamsEquals(expectedParams, params);
|
MediaTestUtils.assertLibraryParamsEquals(expectedParams, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user