From cbc0ee369f9b00f29da2a38055c5cddf2f18e86e Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 10 Oct 2024 03:08:49 -0700 Subject: [PATCH] Use connection hints when connecting to MediaBrowserService Minor improvement to allow an Media3 browser to pass extras when connecting the initial browser in `MediaControllerImplLegacy`. Before this change an empty bundle was sent. After this change the connection hints of the `Media3 browser is used as root hints of the initial browser that connects when the Media3 browser is built in `MediaBrowser.buildAsync`. #cherrypick PiperOrigin-RevId: 684372552 --- RELEASENOTES.md | 4 +++ .../androidx/media3/session/MediaBrowser.java | 2 +- .../session/MediaBrowserImplLegacy.java | 3 +- .../media3/session/MediaController.java | 8 +++++- .../session/MediaControllerImplBase.java | 5 ++++ .../session/MediaControllerImplLegacy.java | 13 ++++++++- ...enerWithMediaBrowserServiceCompatTest.java | 28 +++++++++++++++++++ .../MockMediaBrowserServiceCompat.java | 17 ++++++++++- 8 files changed, 75 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 62a7ac7ebb..73422d3f67 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -112,6 +112,10 @@ * Fix bug where a Media3 controller was sometimes unable to let a session app start a foreground service after requesting `play()`. * Restrict `CommandButton.Builder.setIconUri` to only accept content Uris. + * Pass connection hints of a Media3 browser to the initial + `MediaBrowserCompat` when connecting to a legacy `MediaBrowserCompat`. + The service can receive the connection hints passed in as root hints + with the first call to `onGetRoot()`. * UI: * Make the stretched/cropped video in `PlayerView`-in-Compose-`AndroidView` workaround opt-in, due to issues diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaBrowser.java b/libraries/session/src/main/java/androidx/media3/session/MediaBrowser.java index a5f968a387..88037ea9bc 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaBrowser.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaBrowser.java @@ -292,7 +292,7 @@ public final class MediaBrowser extends MediaController { if (token.isLegacySession()) { impl = new MediaBrowserImplLegacy( - context, this, token, applicationLooper, checkNotNull(bitmapLoader)); + context, this, token, connectionHints, applicationLooper, checkNotNull(bitmapLoader)); } else { impl = new MediaBrowserImplBase(context, this, token, connectionHints, applicationLooper); } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java index b37d9676c0..3c9ad16da1 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java @@ -62,9 +62,10 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; Context context, @UnderInitialization MediaBrowser instance, SessionToken token, + Bundle connectionHints, Looper applicationLooper, BitmapLoader bitmapLoader) { - super(context, instance, token, applicationLooper, bitmapLoader); + super(context, instance, token, connectionHints, applicationLooper, bitmapLoader); this.instance = instance; commandButtonsForMediaItems = ImmutableMap.of(); } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaController.java b/libraries/session/src/main/java/androidx/media3/session/MediaController.java index 8719ddeca7..8b56611944 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaController.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaController.java @@ -568,7 +568,7 @@ public class MediaController implements Player { @Nullable BitmapLoader bitmapLoader) { if (token.isLegacySession()) { return new MediaControllerImplLegacy( - context, this, token, applicationLooper, checkNotNull(bitmapLoader)); + context, this, token, connectionHints, applicationLooper, checkNotNull(bitmapLoader)); } else { return new MediaControllerImplBase(context, this, token, connectionHints, applicationLooper); } @@ -2073,6 +2073,10 @@ public class MediaController implements Player { return impl.getBinder(); } + /* package */ Bundle getConnectionHints() { + return impl.getConnectionHints(); + } + private void verifyApplicationThread() { checkState(Looper.myLooper() == getApplicationLooper(), WRONG_THREAD_ERROR_MESSAGE); } @@ -2081,6 +2085,8 @@ public class MediaController implements Player { void connect(@UnderInitialization MediaControllerImpl this); + Bundle getConnectionHints(); + void addListener(Player.Listener listener); void removeListener(Player.Listener listener); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java index 126cf87f72..dc75a339ea 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java @@ -215,6 +215,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; } } + @Override + public Bundle getConnectionHints() { + return connectionHints; + } + @Override public void addListener(Listener listener) { listeners.add(listener); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java index 43e2c72cfb..f3730d85cc 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java @@ -102,6 +102,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; private final ControllerCompatCallback controllerCompatCallback; private final BitmapLoader bitmapLoader; private final ImmutableList commandButtonsForMediaItems; + private final Bundle connectionHints; @Nullable private MediaControllerCompat controllerCompat; @Nullable private MediaBrowserCompat browserCompat; @@ -117,6 +118,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; Context context, @UnderInitialization MediaController instance, SessionToken token, + Bundle connectionHints, Looper applicationLooper, BitmapLoader bitmapLoader) { // Initialize default values. @@ -134,6 +136,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; this.instance = instance; controllerCompatCallback = new ControllerCompatCallback(applicationLooper); this.token = token; + this.connectionHints = connectionHints; this.bitmapLoader = bitmapLoader; currentPositionMs = C.TIME_UNSET; lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET; @@ -154,6 +157,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; } } + @Override + public Bundle getConnectionHints() { + return connectionHints; + } + @Override public void addListener(Listener listener) { listeners.add(listener); @@ -1410,7 +1418,10 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; // Create it on the application looper to respect that. browserCompat = new MediaBrowserCompat( - context, token.getComponentName(), new ConnectionCallback(), null); + context, + token.getComponentName(), + new ConnectionCallback(), + instance.getConnectionHints()); browserCompat.connect(); }); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java index eacb435187..6c09d9b11b 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java @@ -19,6 +19,7 @@ import static androidx.media3.session.LibraryResult.RESULT_SUCCESS; import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS; import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY; +import static androidx.media3.session.MockMediaBrowserServiceCompat.EXTRAS_KEY_SEND_ROOT_HINTS_AS_SESSION_EXTRAS; import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA_BROWSER_SERVICE_COMPAT; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID; import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_KEY; @@ -136,6 +137,33 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest { assertThat(thrown).hasCauseThat().isInstanceOf(SecurityException.class); } + @Test + public void connect_useConnectionHints_connectionHintsPassedToLegacyServerOnGetRootAsRootHints() + throws Exception { + Bundle connectionHints = new Bundle(); + connectionHints.putBoolean(EXTRAS_KEY_SEND_ROOT_HINTS_AS_SESSION_EXTRAS, true); + CountDownLatch latch = new CountDownLatch(/* count= */ 1); + AtomicReference extrasRef = new AtomicReference<>(); + createBrowser( + connectionHints, + /* maxCommandsForMediaItems= */ 0, + /* listener= */ new MediaBrowser.Listener() { + @Override + public void onExtrasChanged(MediaController controller, Bundle extras) { + extrasRef.set(extras); + latch.countDown(); + } + }); + + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat( + extrasRef + .get() + .getBoolean( + EXTRAS_KEY_SEND_ROOT_HINTS_AS_SESSION_EXTRAS, /* defaultValue= */ false)) + .isTrue(); + } + @Test public void getLibraryRoot_browseActionsAvailable() throws Exception { remoteService.setProxyForTest(TEST_MEDIA_ITEMS_WITH_BROWSE_ACTIONS); diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java index 6b1e3118d0..db7912ded3 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java @@ -40,7 +40,9 @@ import static java.lang.Math.min; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.MediaBrowserCompat.MediaItem; @@ -68,6 +70,13 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat { */ public static final ImmutableList MEDIA_ITEMS = createMediaItems(); + /** + * Key in the browser root hints to request a confirmation of the call to {@link + * #onGetRoot(String, int, Bundle)}. + */ + public static final String EXTRAS_KEY_SEND_ROOT_HINTS_AS_SESSION_EXTRAS = + "confirm_on_get_root_with_custom_action"; + private static final String TAG = "MockMBSCompat"; private static final Object lock = new Object(); @@ -166,12 +175,18 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat { // Test only -- reject any other request. return null; } + if (rootHints.getBoolean(EXTRAS_KEY_SEND_ROOT_HINTS_AS_SESSION_EXTRAS, false)) { + // Send delayed because the Media3 browser is in the process of connecting at this point and + // won't receive listener callbacks before being connected. + new Handler(Looper.myLooper()) + .postDelayed(() -> sessionCompat.setExtras(rootHints), /* delayMillis= */ 100L); + } synchronized (lock) { if (isProxyOverridesMethod("onGetRoot")) { return serviceProxy.onGetRoot(clientPackageName, clientUid, rootHints); } } - return new BrowserRoot("stub", null); + return new BrowserRoot("stub", /* extras= */ rootHints); } @Override